Merge branch 'rock-paper-scissors' into rock-paper-scissors-ro
This commit is contained in:
commit
41f11bd63c
60 changed files with 3163 additions and 209 deletions
|
|
@ -11,7 +11,8 @@ add_library( graphene_app
|
|||
${EGENESIS_HEADERS}
|
||||
)
|
||||
|
||||
target_link_libraries( graphene_app graphene_market_history graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities )
|
||||
# need to link graphene_debug_witness because plugins aren't sufficiently isolated #246
|
||||
target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness )
|
||||
target_include_directories( graphene_app
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include" )
|
||||
|
|
|
|||
|
|
@ -96,6 +96,12 @@ namespace graphene { namespace app {
|
|||
{
|
||||
_crypto_api = std::make_shared< crypto_api >();
|
||||
}
|
||||
else if( api_name == "debug_api" )
|
||||
{
|
||||
// can only enable this API if the plugin was loaded
|
||||
if( _app.get_plugin( "debug_witness" ) )
|
||||
_debug_api = std::make_shared< graphene::debug_witness::debug_api >( std::ref(_app) );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -212,6 +218,12 @@ namespace graphene { namespace app {
|
|||
return *_crypto_api;
|
||||
}
|
||||
|
||||
fc::api<graphene::debug_witness::debug_api> login_api::debug() const
|
||||
{
|
||||
FC_ASSERT(_debug_api);
|
||||
return *_debug_api;
|
||||
}
|
||||
|
||||
#if 0
|
||||
vector<account_id_type> get_relevant_accounts( const object* obj )
|
||||
{
|
||||
|
|
@ -391,25 +403,58 @@ namespace graphene { namespace app {
|
|||
return result;
|
||||
}
|
||||
|
||||
vector<operation_history_object> history_api::get_account_history(account_id_type account, operation_history_id_type stop, unsigned limit, operation_history_id_type start) const
|
||||
vector<operation_history_object> history_api::get_account_history( account_id_type account,
|
||||
operation_history_id_type stop,
|
||||
unsigned limit,
|
||||
operation_history_id_type start ) const
|
||||
{
|
||||
FC_ASSERT(_app.chain_database());
|
||||
FC_ASSERT( _app.chain_database() );
|
||||
const auto& db = *_app.chain_database();
|
||||
FC_ASSERT( limit <= 100 );
|
||||
vector<operation_history_object> result;
|
||||
const auto& stats = account(db).statistics(db);
|
||||
if( stats.most_recent_op == account_transaction_history_id_type() ) return result;
|
||||
const account_transaction_history_object* node = &stats.most_recent_op(db);
|
||||
if( start == operation_history_id_type() )
|
||||
start = node->operation_id;
|
||||
|
||||
while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit)
|
||||
{
|
||||
if( node->operation_id.instance.value <= start.instance.value )
|
||||
result.push_back( node->operation_id(db) );
|
||||
if( node->next == account_transaction_history_id_type() )
|
||||
node = nullptr;
|
||||
else node = &node->next(db);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<operation_history_object> history_api::get_relative_account_history( account_id_type account,
|
||||
uint32_t stop,
|
||||
unsigned limit,
|
||||
uint32_t start) const
|
||||
{
|
||||
FC_ASSERT( _app.chain_database() );
|
||||
const auto& db = *_app.chain_database();
|
||||
FC_ASSERT(limit <= 100);
|
||||
vector<operation_history_object> result;
|
||||
const auto& stats = account(db).statistics(db);
|
||||
if(stats.most_recent_op == account_transaction_history_id_type()) return result;
|
||||
const account_transaction_history_object* node = &stats.most_recent_op(db);
|
||||
if(start == operation_history_id_type())
|
||||
start = node->id;
|
||||
while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit)
|
||||
if( start == 0 )
|
||||
start = account(db).statistics(db).total_ops;
|
||||
else start = min( account(db).statistics(db).total_ops, start );
|
||||
const auto& hist_idx = db.get_index_type<account_transaction_history_index>();
|
||||
const auto& by_seq_idx = hist_idx.indices().get<by_seq>();
|
||||
|
||||
auto itr = by_seq_idx.upper_bound( boost::make_tuple( account, start ) );
|
||||
auto itr_stop = by_seq_idx.lower_bound( boost::make_tuple( account, stop ) );
|
||||
--itr;
|
||||
|
||||
while ( itr != itr_stop && result.size() < limit )
|
||||
{
|
||||
if(node->id.instance() <= start.instance.value)
|
||||
result.push_back(node->operation_id(db));
|
||||
if(node->next == account_transaction_history_id_type())
|
||||
node = nullptr;
|
||||
else node = db.find(node->next);
|
||||
result.push_back( itr->operation_id(db) );
|
||||
--itr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -210,7 +210,9 @@ namespace detail {
|
|||
if( !_options->count("rpc-endpoint") )
|
||||
return;
|
||||
|
||||
_websocket_server = std::make_shared<fc::http::websocket_server>();
|
||||
bool enable_deflate_compression = _options->count("enable-permessage-deflate") != 0;
|
||||
|
||||
_websocket_server = std::make_shared<fc::http::websocket_server>(enable_deflate_compression);
|
||||
|
||||
_websocket_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){
|
||||
auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(*c);
|
||||
|
|
@ -237,7 +239,8 @@ namespace detail {
|
|||
}
|
||||
|
||||
string password = _options->count("server-pem-password") ? _options->at("server-pem-password").as<string>() : "";
|
||||
_websocket_tls_server = std::make_shared<fc::http::websocket_tls_server>( _options->at("server-pem").as<string>(), password );
|
||||
bool enable_deflate_compression = _options->count("enable-permessage-deflate") != 0;
|
||||
_websocket_tls_server = std::make_shared<fc::http::websocket_tls_server>( _options->at("server-pem").as<string>(), password, enable_deflate_compression );
|
||||
|
||||
_websocket_tls_server->on_connection([&]( const fc::http::websocket_connection_ptr& c ){
|
||||
auto wsc = std::make_shared<fc::rpc::websocket_api_connection>(*c);
|
||||
|
|
@ -931,6 +934,8 @@ void application::set_program_options(boost::program_options::options_descriptio
|
|||
("checkpoint,c", bpo::value<vector<string>>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints.")
|
||||
("rpc-endpoint", bpo::value<string>()->implicit_value("127.0.0.1:8090"), "Endpoint for websocket RPC to listen on")
|
||||
("rpc-tls-endpoint", bpo::value<string>()->implicit_value("127.0.0.1:8089"), "Endpoint for TLS websocket RPC to listen on")
|
||||
("enable-permessage-deflate", "Enable support for per-message deflate compression in the websocket servers "
|
||||
"(--rpc-endpoint and --rpc-tls-endpoint), disabled by default")
|
||||
("server-pem,p", bpo::value<string>()->implicit_value("server.pem"), "The TLS certificate file for this server")
|
||||
("server-pem-password,P", bpo::value<string>()->implicit_value(""), "Password for this certificate")
|
||||
("genesis-json", bpo::value<boost::filesystem::path>(), "File to read Genesis State from")
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <graphene/app/database_api.hpp>
|
||||
#include <graphene/chain/get_config.hpp>
|
||||
#include <graphene/chain/tournament_object.hpp>
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
|
||||
#include <fc/bloom_filter.hpp>
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
|
|
@ -32,6 +33,8 @@
|
|||
#include <fc/crypto/hex.hpp>
|
||||
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <boost/rational.hpp>
|
||||
#include <boost/multiprecision/cpp_int.hpp>
|
||||
|
||||
#include <cctype>
|
||||
|
||||
|
|
@ -608,6 +611,12 @@ std::map<std::string, full_account> database_api_impl::get_full_accounts( const
|
|||
[&acnt] (const call_order_object& call) {
|
||||
acnt.call_orders.emplace_back(call);
|
||||
});
|
||||
|
||||
auto pending_payouts_range =
|
||||
_db.get_index_type<pending_dividend_payout_balance_for_holder_object_index>().indices().get<by_account_dividend_payout>().equal_range(boost::make_tuple(account->id));
|
||||
|
||||
std::copy(pending_payouts_range.first, pending_payouts_range.second, std::back_inserter(acnt.pending_dividend_payments));
|
||||
|
||||
results[account_name_or_id] = acnt;
|
||||
}
|
||||
return results;
|
||||
|
|
@ -1031,56 +1040,37 @@ market_ticker database_api_impl::get_ticker( const string& base, const string& q
|
|||
try {
|
||||
if( base_id > quote_id ) std::swap(base_id, quote_id);
|
||||
|
||||
const auto& bidx = _db.get_index_type<bucket_index>();
|
||||
const auto& by_key_idx = bidx.indices().get<by_key>();
|
||||
uint32_t bucket_size = 86400;
|
||||
uint32_t day = 86400;
|
||||
auto now = fc::time_point_sec( fc::time_point::now() );
|
||||
|
||||
auto itr = by_key_idx.lower_bound( bucket_key( base_id, quote_id, bucket_size,
|
||||
now - bucket_size ) );
|
||||
|
||||
auto orders = get_order_book( base, quote, 1 );
|
||||
auto trades = get_trade_history( base, quote, now, fc::time_point_sec( now.sec_since_epoch() - day ), 100 );
|
||||
|
||||
if( itr != by_key_idx.end() && itr->key.base == base_id && itr->key.quote == quote_id && itr->key.seconds == bucket_size )
|
||||
result.latest = trades[0].price;
|
||||
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
auto trades = get_trade_history( base, quote, now, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
|
||||
result.base_volume += t.value;
|
||||
result.quote_volume += t.amount;
|
||||
}
|
||||
|
||||
if (assets[0]->id == base_id)
|
||||
{
|
||||
result.latest = trades[0].price;
|
||||
result.percent_change = ( result.latest / ( price_to_real( itr->open_quote, assets[1]->precision ) / price_to_real( itr->open_base, assets[0]->precision ) ) - 1 ) * 100;
|
||||
//result.lowest_ask = price_to_real( itr->low_quote, assets[1]->precision ) / price_to_real( itr->low_base, assets[0]->precision );
|
||||
//result.highest_bid = price_to_real( itr->high_quote, assets[1]->precision ) / price_to_real( itr->high_base, assets[0]->precision );
|
||||
result.lowest_ask = orders.asks[0].first;
|
||||
result.highest_bid = orders.bids[0].first;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.latest = trades[0].price;
|
||||
result.percent_change = ( result.latest / ( price_to_real( itr->open_base, assets[1]->precision ) / price_to_real( itr->open_quote, assets[0]->precision ) ) - 1) * 100;
|
||||
//result.lowest_ask = price_to_real( itr->low_base, assets[1]->precision ) / price_to_real( itr->low_quote, assets[0]->precision );
|
||||
//result.highest_bid = price_to_real( itr->high_base, assets[1]->precision ) / price_to_real( itr->high_quote, assets[0]->precision );
|
||||
result.lowest_ask = orders.bids[0].first;
|
||||
result.highest_bid = orders.asks[0].first;
|
||||
}
|
||||
while (trades.size() == 100)
|
||||
{
|
||||
trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - day ), 100 );
|
||||
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
result.base_volume += t.amount;
|
||||
result.quote_volume += t.value;
|
||||
result.base_volume += t.value;
|
||||
result.quote_volume += t.amount;
|
||||
}
|
||||
}
|
||||
|
||||
while (trades.size() == 100)
|
||||
{
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
result.base_volume += t.amount;
|
||||
result.quote_volume += t.value;
|
||||
}
|
||||
|
||||
trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
|
||||
}
|
||||
trades = get_trade_history( base, quote, trades.back().date, fc::time_point_sec(), 1 );
|
||||
result.percent_change = trades.size() > 0 ? ( ( result.latest / trades.back().price ) - 1 ) * 100 : 0;
|
||||
|
||||
//if (assets[0]->id == base_id)
|
||||
{
|
||||
result.lowest_ask = orders.asks[0].price;
|
||||
result.highest_bid = orders.bids[0].price;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -1117,19 +1107,19 @@ market_volume database_api_impl::get_24_volume( const string& base, const string
|
|||
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
result.base_volume += t.amount;
|
||||
result.quote_volume += t.value;
|
||||
result.base_volume += t.value;
|
||||
result.quote_volume += t.amount;
|
||||
}
|
||||
|
||||
while (trades.size() == 100)
|
||||
{
|
||||
trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
|
||||
|
||||
for ( market_trade t: trades )
|
||||
{
|
||||
result.base_volume += t.amount;
|
||||
result.quote_volume += t.value;
|
||||
result.base_volume += t.value;
|
||||
result.quote_volume += t.amount;
|
||||
}
|
||||
|
||||
trades = get_trade_history( base, quote, trades[99].date, fc::time_point_sec( now.sec_since_epoch() - bucket_size ), 100 );
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -1143,6 +1133,7 @@ order_book database_api::get_order_book( const string& base, const string& quote
|
|||
|
||||
order_book database_api_impl::get_order_book( const string& base, const string& quote, unsigned limit )const
|
||||
{
|
||||
using boost::multiprecision::uint128_t;
|
||||
FC_ASSERT( limit <= 50 );
|
||||
|
||||
order_book result;
|
||||
|
|
@ -1162,21 +1153,28 @@ order_book database_api_impl::get_order_book( const string& base, const string&
|
|||
auto price_to_real = [&]( const price& p )
|
||||
{
|
||||
if( p.base.asset_id == base_id )
|
||||
return asset_to_real( p.quote, assets[1]->precision ) / asset_to_real( p.base, assets[0]->precision );
|
||||
return asset_to_real( p.base, assets[0]->precision ) / asset_to_real( p.quote, assets[1]->precision );
|
||||
else
|
||||
return asset_to_real( p.base, assets[1]->precision ) / asset_to_real( p.quote, assets[0]->precision );
|
||||
return asset_to_real( p.quote, assets[0]->precision ) / asset_to_real( p.base, assets[1]->precision );
|
||||
};
|
||||
|
||||
for( const auto& o : orders ) {
|
||||
for( const auto& o : orders )
|
||||
{
|
||||
if( o.sell_price.base.asset_id == base_id )
|
||||
{
|
||||
result.asks.push_back( std::make_pair( price_to_real(o.sell_price),
|
||||
asset_to_real(o.sell_price.base, assets[0]->precision)) );
|
||||
order ord;
|
||||
ord.price = price_to_real( o.sell_price );
|
||||
ord.quote = asset_to_real( share_type( ( uint128_t( o.for_sale.value ) * o.sell_price.quote.amount.value ) / o.sell_price.base.amount.value ), assets[1]->precision );
|
||||
ord.base = asset_to_real( o.for_sale, assets[0]->precision );
|
||||
result.bids.push_back( ord );
|
||||
}
|
||||
else
|
||||
{
|
||||
result.bids.push_back( std::make_pair( price_to_real(o.sell_price),
|
||||
asset_to_real(o.sell_price.quote, assets[0]->precision ) ) );
|
||||
order ord;
|
||||
ord.price = price_to_real( o.sell_price );
|
||||
ord.quote = asset_to_real( o.for_sale, assets[1]->precision );
|
||||
ord.base = asset_to_real( share_type( ( uint64_t( o.for_sale.value ) * o.sell_price.quote.amount.value ) / o.sell_price.base.amount.value ), assets[0]->precision );
|
||||
result.asks.push_back( ord );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1231,13 +1229,13 @@ vector<market_trade> database_api_impl::get_trade_history( const string& base,
|
|||
|
||||
if( assets[0]->id == itr->op.receives.asset_id )
|
||||
{
|
||||
trade.amount = price_to_real( itr->op.receives.amount, assets[0]->precision );
|
||||
trade.value = price_to_real( itr->op.pays.amount, assets[1]->precision );
|
||||
trade.amount = price_to_real( itr->op.pays.amount, assets[1]->precision );
|
||||
trade.value = price_to_real( itr->op.receives.amount, assets[0]->precision );
|
||||
}
|
||||
else
|
||||
{
|
||||
trade.amount = price_to_real( itr->op.pays.amount, assets[0]->precision );
|
||||
trade.value = price_to_real( itr->op.receives.amount, assets[1]->precision );
|
||||
trade.amount = price_to_real( itr->op.receives.amount, assets[1]->precision );
|
||||
trade.value = price_to_real( itr->op.pays.amount, assets[0]->precision );
|
||||
}
|
||||
|
||||
trade.date = itr->time;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,12 @@ struct get_impacted_account_visitor
|
|||
}
|
||||
|
||||
void operator()( const asset_update_bitasset_operation& op ) {}
|
||||
void operator()( const asset_update_dividend_operation& op ) {}
|
||||
void operator()( const asset_dividend_distribution_operation& op )
|
||||
{
|
||||
_impacted.insert( op.account_id );
|
||||
}
|
||||
|
||||
void operator()( const asset_update_feed_producers_operation& op ) {}
|
||||
|
||||
void operator()( const asset_issue_operation& op )
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
#include <graphene/market_history/market_history_plugin.hpp>
|
||||
|
||||
#include <graphene/debug_witness/debug_api.hpp>
|
||||
|
||||
#include <graphene/net/node.hpp>
|
||||
|
||||
#include <fc/api.hpp>
|
||||
|
|
@ -91,6 +93,22 @@ namespace graphene { namespace app {
|
|||
operation_history_id_type stop = operation_history_id_type(),
|
||||
unsigned limit = 100,
|
||||
operation_history_id_type start = operation_history_id_type())const;
|
||||
/**
|
||||
* @breif Get operations relevant to the specified account referenced
|
||||
* by an event numbering specific to the account. The current number of operations
|
||||
* for the account can be found in the account statistics (or use 0 for start).
|
||||
* @param account The account whose history should be queried
|
||||
* @param stop Sequence number of earliest operation. 0 is default and will
|
||||
* query 'limit' number of operations.
|
||||
* @param limit Maximum number of operations to retrieve (must not exceed 100)
|
||||
* @param start Sequence number of the most recent operation to retrieve.
|
||||
* 0 is default, which will start querying from the most recent operation.
|
||||
* @return A list of operations performed by account, ordered from most recent to oldest.
|
||||
*/
|
||||
vector<operation_history_object> get_relative_account_history( account_id_type account,
|
||||
uint32_t stop = 0,
|
||||
unsigned limit = 100,
|
||||
uint32_t start = 0) const;
|
||||
|
||||
vector<order_history_object> get_fill_order_history( asset_id_type a, asset_id_type b, uint32_t limit )const;
|
||||
vector<bucket_object> get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds,
|
||||
|
|
@ -265,6 +283,8 @@ namespace graphene { namespace app {
|
|||
fc::api<network_node_api> network_node()const;
|
||||
/// @brief Retrieve the cryptography API
|
||||
fc::api<crypto_api> crypto()const;
|
||||
/// @brief Retrieve the debug API (if available)
|
||||
fc::api<graphene::debug_witness::debug_api> debug()const;
|
||||
|
||||
private:
|
||||
/// @brief Called to enable an API, not reflected.
|
||||
|
|
@ -276,6 +296,7 @@ namespace graphene { namespace app {
|
|||
optional< fc::api<network_node_api> > _network_node_api;
|
||||
optional< fc::api<history_api> > _history_api;
|
||||
optional< fc::api<crypto_api> > _crypto_api;
|
||||
optional< fc::api<graphene::debug_witness::debug_api> > _debug_api;
|
||||
};
|
||||
|
||||
}} // graphene::app
|
||||
|
|
@ -291,6 +312,7 @@ FC_REFLECT( graphene::app::verify_range_proof_rewind_result,
|
|||
|
||||
FC_API(graphene::app::history_api,
|
||||
(get_account_history)
|
||||
(get_relative_account_history)
|
||||
(get_fill_order_history)
|
||||
(get_market_history)
|
||||
(get_market_history_buckets)
|
||||
|
|
@ -326,4 +348,5 @@ FC_API(graphene::app::login_api,
|
|||
(history)
|
||||
(network_node)
|
||||
(crypto)
|
||||
(debug)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -65,40 +65,47 @@ using namespace std;
|
|||
|
||||
class database_api_impl;
|
||||
|
||||
struct order
|
||||
{
|
||||
double price;
|
||||
double quote;
|
||||
double base;
|
||||
};
|
||||
|
||||
struct order_book
|
||||
{
|
||||
string base;
|
||||
string quote;
|
||||
vector< pair<double,double> > bids;
|
||||
vector< pair<double,double> > asks;
|
||||
string base;
|
||||
string quote;
|
||||
vector< order > bids;
|
||||
vector< order > asks;
|
||||
};
|
||||
|
||||
struct market_ticker
|
||||
{
|
||||
string base;
|
||||
string quote;
|
||||
double latest;
|
||||
double lowest_ask;
|
||||
double highest_bid;
|
||||
double percent_change;
|
||||
double base_volume;
|
||||
double quote_volume;
|
||||
string base;
|
||||
string quote;
|
||||
double latest;
|
||||
double lowest_ask;
|
||||
double highest_bid;
|
||||
double percent_change;
|
||||
double base_volume;
|
||||
double quote_volume;
|
||||
};
|
||||
|
||||
struct market_volume
|
||||
{
|
||||
string base;
|
||||
string quote;
|
||||
double base_volume;
|
||||
double quote_volume;
|
||||
string base;
|
||||
string quote;
|
||||
double base_volume;
|
||||
double quote_volume;
|
||||
};
|
||||
|
||||
struct market_trade
|
||||
{
|
||||
fc::time_point_sec date;
|
||||
double price;
|
||||
double amount;
|
||||
double value;
|
||||
fc::time_point_sec date;
|
||||
double price;
|
||||
double amount;
|
||||
double value;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -117,7 +124,7 @@ class database_api
|
|||
/////////////
|
||||
// Objects //
|
||||
/////////////
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get the objects corresponding to the provided IDs
|
||||
* @param ids IDs of the objects to retrieve
|
||||
|
|
@ -203,7 +210,7 @@ class database_api
|
|||
//////////
|
||||
// Keys //
|
||||
//////////
|
||||
|
||||
|
||||
vector<vector<account_id_type>> get_key_references( vector<public_key_type> key )const;
|
||||
|
||||
//////////////
|
||||
|
|
@ -466,9 +473,9 @@ class database_api
|
|||
*/
|
||||
map<string, committee_member_id_type> lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const;
|
||||
|
||||
|
||||
/// WORKERS
|
||||
|
||||
|
||||
/// WORKERS
|
||||
|
||||
/**
|
||||
* Return the worker objects associated with this account.
|
||||
*/
|
||||
|
|
@ -516,7 +523,7 @@ class database_api
|
|||
bool verify_authority( const signed_transaction& trx )const;
|
||||
|
||||
/**
|
||||
* @return true if the signers have enough authority to authorize an account
|
||||
* @return true if the signers have enough authority to authorize an account
|
||||
*/
|
||||
bool verify_account_authority( const string& name_or_id, const flat_set<public_key_type>& signers )const;
|
||||
|
||||
|
|
@ -567,6 +574,8 @@ class database_api
|
|||
};
|
||||
|
||||
} }
|
||||
|
||||
FC_REFLECT( graphene::app::order, (price)(quote)(base) );
|
||||
FC_REFLECT( graphene::app::order_book, (base)(quote)(bids)(asks) );
|
||||
FC_REFLECT( graphene::app::market_ticker, (base)(quote)(latest)(lowest_ask)(highest_bid)(percent_change)(base_volume)(quote_volume) );
|
||||
FC_REFLECT( graphene::app::market_volume, (base)(quote)(base_volume)(quote_volume) );
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ namespace graphene { namespace app {
|
|||
vector<limit_order_object> limit_orders;
|
||||
vector<call_order_object> call_orders;
|
||||
vector<proposal_object> proposals;
|
||||
vector<pending_dividend_payout_balance_for_holder_object> pending_dividend_payments;
|
||||
};
|
||||
|
||||
} }
|
||||
|
|
@ -61,4 +62,5 @@ FC_REFLECT( graphene::app::full_account,
|
|||
(limit_orders)
|
||||
(call_orders)
|
||||
(proposals)
|
||||
(pending_dividend_payments)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
#include <graphene/chain/internal_exceptions.hpp>
|
||||
#include <graphene/chain/special_authority.hpp>
|
||||
#include <graphene/chain/special_authority_object.hpp>
|
||||
#include <graphene/chain/worker_object.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -54,6 +55,43 @@ void verify_authority_accounts( const database& db, const authority& a )
|
|||
}
|
||||
}
|
||||
|
||||
void verify_account_votes( const database& db, const account_options& options )
|
||||
{
|
||||
// ensure account's votes satisfy requirements
|
||||
// NB only the part of vote checking that requires chain state is here,
|
||||
// the rest occurs in account_options::validate()
|
||||
|
||||
const auto& gpo = db.get_global_properties();
|
||||
const auto& chain_params = gpo.parameters;
|
||||
|
||||
FC_ASSERT( options.num_witness <= chain_params.maximum_witness_count,
|
||||
"Voted for more witnesses than currently allowed (${c})", ("c", chain_params.maximum_witness_count) );
|
||||
FC_ASSERT( options.num_committee <= chain_params.maximum_committee_count,
|
||||
"Voted for more committee members than currently allowed (${c})", ("c", chain_params.maximum_committee_count) );
|
||||
|
||||
uint32_t max_vote_id = gpo.next_available_vote_id;
|
||||
bool has_worker_votes = false;
|
||||
for( auto id : options.votes )
|
||||
{
|
||||
FC_ASSERT( id < max_vote_id );
|
||||
has_worker_votes |= (id.type() == vote_id_type::worker);
|
||||
}
|
||||
|
||||
if( has_worker_votes && (db.head_block_time() >= HARDFORK_607_TIME) )
|
||||
{
|
||||
const auto& against_worker_idx = db.get_index_type<worker_index>().indices().get<by_vote_against>();
|
||||
for( auto id : options.votes )
|
||||
{
|
||||
if( id.type() == vote_id_type::worker )
|
||||
{
|
||||
FC_ASSERT( against_worker_idx.find( id ) == against_worker_idx.end() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void_result account_create_evaluator::do_evaluate( const account_create_operation& op )
|
||||
{ try {
|
||||
database& d = db();
|
||||
|
|
@ -62,14 +100,18 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio
|
|||
FC_ASSERT( !op.extensions.value.owner_special_authority.valid() );
|
||||
FC_ASSERT( !op.extensions.value.active_special_authority.valid() );
|
||||
}
|
||||
if( d.head_block_time() < HARDFORK_599_TIME )
|
||||
{
|
||||
FC_ASSERT( !op.extensions.value.null_ext.valid() );
|
||||
FC_ASSERT( !op.extensions.value.owner_special_authority.valid() );
|
||||
FC_ASSERT( !op.extensions.value.active_special_authority.valid() );
|
||||
FC_ASSERT( !op.extensions.value.buyback_options.valid() );
|
||||
}
|
||||
|
||||
FC_ASSERT( d.find_object(op.options.voting_account), "Invalid proxy account specified." );
|
||||
FC_ASSERT( fee_paying_account->is_lifetime_member(), "Only Lifetime members may register an account." );
|
||||
FC_ASSERT( op.referrer(d).is_member(d.head_block_time()), "The referrer must be either a lifetime or annual subscriber." );
|
||||
|
||||
const auto& global_props = d.get_global_properties();
|
||||
const auto& chain_params = global_props.parameters;
|
||||
|
||||
try
|
||||
{
|
||||
verify_authority_accounts( d, op.owner );
|
||||
|
|
@ -84,27 +126,7 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio
|
|||
evaluate_special_authority( d, *op.extensions.value.active_special_authority );
|
||||
if( op.extensions.value.buyback_options.valid() )
|
||||
evaluate_buyback_account_options( d, *op.extensions.value.buyback_options );
|
||||
|
||||
uint32_t max_vote_id = global_props.next_available_vote_id;
|
||||
|
||||
FC_ASSERT( op.options.num_witness <= chain_params.maximum_witness_count,
|
||||
"Voted for more witnesses than currently allowed (${c})", ("c", chain_params.maximum_witness_count) );
|
||||
|
||||
FC_ASSERT( op.options.num_committee <= chain_params.maximum_committee_count,
|
||||
"Voted for more committee members than currently allowed (${c})", ("c", chain_params.maximum_committee_count) );
|
||||
|
||||
safe<uint32_t> counts[vote_id_type::VOTE_TYPE_COUNT];
|
||||
for( auto id : op.options.votes )
|
||||
{
|
||||
FC_ASSERT( id < max_vote_id );
|
||||
counts[id.type()]++;
|
||||
}
|
||||
FC_ASSERT(counts[vote_id_type::witness] <= op.options.num_witness,
|
||||
"",
|
||||
("count", counts[vote_id_type::witness])("num", op.options.num_witness));
|
||||
FC_ASSERT(counts[vote_id_type::committee] <= op.options.num_committee,
|
||||
"",
|
||||
("count", counts[vote_id_type::committee])("num", op.options.num_committee));
|
||||
verify_account_votes( d, op.options );
|
||||
|
||||
auto& acnt_indx = d.get_index_type<account_index>();
|
||||
if( op.name.size() )
|
||||
|
|
@ -223,8 +245,12 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio
|
|||
FC_ASSERT( !o.extensions.value.owner_special_authority.valid() );
|
||||
FC_ASSERT( !o.extensions.value.active_special_authority.valid() );
|
||||
}
|
||||
|
||||
const auto& chain_params = d.get_global_properties().parameters;
|
||||
if( d.head_block_time() < HARDFORK_599_TIME )
|
||||
{
|
||||
FC_ASSERT( !o.extensions.value.null_ext.valid() );
|
||||
FC_ASSERT( !o.extensions.value.owner_special_authority.valid() );
|
||||
FC_ASSERT( !o.extensions.value.active_special_authority.valid() );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -241,16 +267,8 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio
|
|||
|
||||
acnt = &o.account(d);
|
||||
|
||||
if( o.new_options )
|
||||
{
|
||||
FC_ASSERT( o.new_options->num_witness <= chain_params.maximum_witness_count );
|
||||
FC_ASSERT( o.new_options->num_committee <= chain_params.maximum_committee_count );
|
||||
uint32_t max_vote_id = d.get_global_properties().next_available_vote_id;
|
||||
for( auto id : o.new_options->votes )
|
||||
{
|
||||
FC_ASSERT( id < max_vote_id );
|
||||
}
|
||||
}
|
||||
if( o.new_options.valid() )
|
||||
verify_account_votes( d, *o.new_options );
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
|
|
@ -371,11 +389,13 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator:
|
|||
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage;
|
||||
} else if( a.is_annual_member(d.head_block_time()) ) {
|
||||
// Renew an annual subscription that's still in effect.
|
||||
FC_ASSERT( d.head_block_time() <= HARDFORK_613_TIME );
|
||||
FC_ASSERT(a.membership_expiration_date - d.head_block_time() < fc::days(3650),
|
||||
"May not extend annual membership more than a decade into the future.");
|
||||
a.membership_expiration_date += fc::days(365);
|
||||
} else {
|
||||
// Upgrade from basic account.
|
||||
FC_ASSERT( d.head_block_time() <= HARDFORK_613_TIME );
|
||||
a.statistics(d).process_fees(a, d);
|
||||
assert(a.is_basic_account(d.head_block_time()));
|
||||
a.referrer = a.get_id();
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
#include <functional>
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op )
|
||||
|
|
@ -366,6 +368,70 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse
|
|||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
|
||||
void_result asset_update_dividend_evaluator::do_evaluate(const asset_update_dividend_operation& o)
|
||||
{ try {
|
||||
database& d = db();
|
||||
|
||||
const asset_object& a = o.asset_to_update(d);
|
||||
asset_to_update = &a;
|
||||
|
||||
FC_ASSERT( o.issuer == a.issuer, "", ("o.issuer", o.issuer)("a.issuer", a.issuer) );
|
||||
auto& params = db().get_global_properties().parameters;
|
||||
if (o.new_options.payout_interval &&
|
||||
*o.new_options.payout_interval < params.maintenance_interval)
|
||||
FC_THROW("New payout interval may not be less than the maintenance interval",
|
||||
("new_payout_interval", o.new_options.payout_interval)("maintenance_interval", params.maintenance_interval));
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||
|
||||
void_result asset_update_dividend_evaluator::do_apply( const asset_update_dividend_operation& op )
|
||||
{ try {
|
||||
database& d = db();
|
||||
if (!asset_to_update->dividend_data_id)
|
||||
{
|
||||
// this was not a dividend-paying asset, we're converting it to a dividend-paying asset
|
||||
std::string dividend_distribution_account_name(boost::to_lower_copy(asset_to_update->symbol) + "-dividend-distribution");
|
||||
|
||||
const auto& new_acnt_object = db().create<account_object>( [&]( account_object& obj ){
|
||||
obj.registrar = op.issuer;
|
||||
obj.referrer = op.issuer;
|
||||
obj.lifetime_referrer = op.issuer(db()).lifetime_referrer;
|
||||
|
||||
auto& params = db().get_global_properties().parameters;
|
||||
obj.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;
|
||||
obj.lifetime_referrer_fee_percentage = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE;
|
||||
obj.referrer_rewards_percentage = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE;
|
||||
|
||||
obj.name = dividend_distribution_account_name;
|
||||
obj.owner.weight_threshold = 1;
|
||||
obj.active.weight_threshold = 1;
|
||||
obj.statistics = db().create<account_statistics_object>([&](account_statistics_object& s){s.owner = obj.id;}).id;
|
||||
});
|
||||
|
||||
const asset_dividend_data_object& dividend_data = d.create<asset_dividend_data_object>( [&]( asset_dividend_data_object& dividend_data_obj ) {
|
||||
dividend_data_obj.options = op.new_options;
|
||||
dividend_data_obj.dividend_distribution_account = new_acnt_object.id;
|
||||
});
|
||||
|
||||
d.modify(*asset_to_update, [&](asset_object& a) {
|
||||
a.dividend_data_id = dividend_data.id;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
const asset_dividend_data_object& dividend_data = asset_to_update->dividend_data(d);
|
||||
d.modify(dividend_data, [&]( asset_dividend_data_object& dividend_data_obj ) {
|
||||
dividend_data_obj.options = op.new_options;
|
||||
// whenever new options are set, clear out the scheduled payout/distribution times
|
||||
// this will reset and cause the next distribution to happen at the next maintenance
|
||||
// interval and a payout at the next_payout_time
|
||||
dividend_data_obj.last_scheduled_payout_time.reset();
|
||||
dividend_data_obj.last_scheduled_distribution_time.reset();
|
||||
});
|
||||
}
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_evaluator::operation_type& o)
|
||||
{ try {
|
||||
database& d = db();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@
|
|||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/evaluator.hpp>
|
||||
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
bool database::is_known_block( const block_id_type& id )const
|
||||
|
|
@ -549,6 +551,8 @@ void database::_apply_block( const signed_block& next_block )
|
|||
// to be called for header validation?
|
||||
update_maintenance_flag( maint_needed );
|
||||
update_witness_schedule();
|
||||
if( !_node_property_object.debug_updates.empty() )
|
||||
apply_debug_updates();
|
||||
|
||||
// notify observers that the block has been applied
|
||||
applied_block( next_block ); //emit
|
||||
|
|
|
|||
|
|
@ -95,4 +95,107 @@ void database::debug_dump()
|
|||
*/
|
||||
}
|
||||
|
||||
void debug_apply_update( database& db, const fc::variant_object& vo )
|
||||
{
|
||||
static const uint8_t
|
||||
db_action_nil = 0,
|
||||
db_action_create = 1,
|
||||
db_action_write = 2,
|
||||
db_action_update = 3,
|
||||
db_action_delete = 4;
|
||||
|
||||
// "_action" : "create" object must not exist, unspecified fields take defaults
|
||||
// "_action" : "write" object may exist, is replaced entirely, unspecified fields take defaults
|
||||
// "_action" : "update" object must exist, unspecified fields don't change
|
||||
// "_action" : "delete" object must exist, will be deleted
|
||||
|
||||
// if _action is unspecified:
|
||||
// - delete if object contains only ID field
|
||||
// - otherwise, write
|
||||
|
||||
object_id_type oid;
|
||||
uint8_t action = db_action_nil;
|
||||
auto it_id = vo.find("id");
|
||||
FC_ASSERT( it_id != vo.end() );
|
||||
|
||||
from_variant( it_id->value(), oid );
|
||||
action = ( vo.size() == 1 ) ? db_action_delete : db_action_write;
|
||||
|
||||
from_variant( vo["id"], oid );
|
||||
if( vo.size() == 1 )
|
||||
action = db_action_delete;
|
||||
auto it_action = vo.find("_action" );
|
||||
if( it_action != vo.end() )
|
||||
{
|
||||
const std::string& str_action = it_action->value().get_string();
|
||||
if( str_action == "create" )
|
||||
action = db_action_create;
|
||||
else if( str_action == "write" )
|
||||
action = db_action_write;
|
||||
else if( str_action == "update" )
|
||||
action = db_action_update;
|
||||
else if( str_action == "delete" )
|
||||
action = db_action_delete;
|
||||
}
|
||||
|
||||
auto& idx = db.get_index( oid );
|
||||
|
||||
switch( action )
|
||||
{
|
||||
case db_action_create:
|
||||
/*
|
||||
idx.create( [&]( object& obj )
|
||||
{
|
||||
idx.object_from_variant( vo, obj );
|
||||
} );
|
||||
*/
|
||||
FC_ASSERT( false );
|
||||
break;
|
||||
case db_action_write:
|
||||
db.modify( db.get_object( oid ), [&]( object& obj )
|
||||
{
|
||||
idx.object_default( obj );
|
||||
idx.object_from_variant( vo, obj );
|
||||
} );
|
||||
break;
|
||||
case db_action_update:
|
||||
db.modify( db.get_object( oid ), [&]( object& obj )
|
||||
{
|
||||
idx.object_from_variant( vo, obj );
|
||||
} );
|
||||
break;
|
||||
case db_action_delete:
|
||||
db.remove( db.get_object( oid ) );
|
||||
break;
|
||||
default:
|
||||
FC_ASSERT( false );
|
||||
}
|
||||
}
|
||||
|
||||
void database::apply_debug_updates()
|
||||
{
|
||||
block_id_type head_id = head_block_id();
|
||||
auto it = _node_property_object.debug_updates.find( head_id );
|
||||
if( it == _node_property_object.debug_updates.end() )
|
||||
return;
|
||||
for( const fc::variant_object& update : it->second )
|
||||
debug_apply_update( *this, update );
|
||||
}
|
||||
|
||||
void database::debug_update( const fc::variant_object& update )
|
||||
{
|
||||
block_id_type head_id = head_block_id();
|
||||
auto it = _node_property_object.debug_updates.find( head_id );
|
||||
if( it == _node_property_object.debug_updates.end() )
|
||||
it = _node_property_object.debug_updates.emplace( head_id, std::vector< fc::variant_object >() ).first;
|
||||
it->second.emplace_back( update );
|
||||
|
||||
optional<signed_block> head_block = fetch_block_by_id( head_id );
|
||||
FC_ASSERT( head_block.valid() );
|
||||
|
||||
// What the last block does has been changed by adding to node_property_object, so we have to re-apply it
|
||||
pop_block();
|
||||
push_block( *head_block );
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ void database::initialize_evaluators()
|
|||
register_evaluator<asset_reserve_evaluator>();
|
||||
register_evaluator<asset_update_evaluator>();
|
||||
register_evaluator<asset_update_bitasset_evaluator>();
|
||||
register_evaluator<asset_update_dividend_evaluator>();
|
||||
register_evaluator<asset_update_feed_producers_evaluator>();
|
||||
register_evaluator<asset_settle_evaluator>();
|
||||
register_evaluator<asset_global_settle_evaluator>();
|
||||
|
|
@ -217,6 +218,7 @@ void database::initialize_indexes()
|
|||
add_index< primary_index<transaction_index > >();
|
||||
add_index< primary_index<account_balance_index > >();
|
||||
add_index< primary_index<asset_bitasset_data_index > >();
|
||||
add_index< primary_index<asset_dividend_data_object_index > >();
|
||||
add_index< primary_index<simple_index<global_property_object >> >();
|
||||
add_index< primary_index<simple_index<dynamic_global_property_object >> >();
|
||||
add_index< primary_index<simple_index<account_statistics_object >> >();
|
||||
|
|
@ -229,6 +231,8 @@ void database::initialize_indexes()
|
|||
add_index< primary_index< buyback_index > >();
|
||||
|
||||
add_index< primary_index< simple_index< fba_accumulator_object > > >();
|
||||
add_index< primary_index<pending_dividend_payout_balance_for_holder_object_index > >();
|
||||
add_index< primary_index<total_distributed_dividend_balance_object_index > >();
|
||||
}
|
||||
|
||||
void database::init_genesis(const genesis_state_type& genesis_state)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/fba_accumulator_id.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
|
|
@ -102,11 +103,12 @@ void database::update_worker_votes()
|
|||
{
|
||||
auto& idx = get_index_type<worker_index>();
|
||||
auto itr = idx.indices().get<by_account>().begin();
|
||||
bool allow_negative_votes = (head_block_time() < HARDFORK_607_TIME);
|
||||
while( itr != idx.indices().get<by_account>().end() )
|
||||
{
|
||||
modify( *itr, [&]( worker_object& obj ){
|
||||
obj.total_votes_for = _vote_tally_buffer[obj.vote_for];
|
||||
obj.total_votes_against = _vote_tally_buffer[obj.vote_against];
|
||||
obj.total_votes_against = allow_negative_votes ? _vote_tally_buffer[obj.vote_against] : 0;
|
||||
});
|
||||
++itr;
|
||||
}
|
||||
|
|
@ -685,6 +687,487 @@ void create_buyback_orders( database& db )
|
|||
return;
|
||||
}
|
||||
|
||||
void deprecate_annual_members( database& db )
|
||||
{
|
||||
const auto& account_idx = db.get_index_type<account_index>().indices().get<by_id>();
|
||||
fc::time_point_sec now = db.head_block_time();
|
||||
for( const account_object& acct : account_idx )
|
||||
{
|
||||
try
|
||||
{
|
||||
transaction_evaluation_state upgrade_context(&db);
|
||||
upgrade_context.skip_fee_schedule_check = true;
|
||||
|
||||
if( acct.is_annual_member( now ) )
|
||||
{
|
||||
account_upgrade_operation upgrade_vop;
|
||||
upgrade_vop.fee = asset( 0, asset_id_type() );
|
||||
upgrade_vop.account_to_upgrade = acct.id;
|
||||
upgrade_vop.upgrade_to_lifetime_member = true;
|
||||
db.apply_operation( upgrade_context, upgrade_vop );
|
||||
}
|
||||
}
|
||||
catch( const fc::exception& e )
|
||||
{
|
||||
// we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account
|
||||
wlog( "Skipping annual member deprecate processing for account ${a} (${an}) at block ${n}; exception was ${e}",
|
||||
("a", acct.id)("an", acct.name)("n", db.head_block_num())("e", e.to_detail_string()) );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedules payouts from a dividend distribution account to the current holders of the
|
||||
// dividend-paying asset. This takes any deposits made to the dividend distribution account
|
||||
// since the last time it was called, and distributes them to the current owners of the
|
||||
// dividend-paying asset according to the amount they own.
|
||||
void schedule_pending_dividend_balances(database& db,
|
||||
const asset_object& dividend_holder_asset_obj,
|
||||
const asset_dividend_data_object& dividend_data,
|
||||
const fc::time_point_sec& current_head_block_time,
|
||||
const account_balance_index& balance_index,
|
||||
const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index,
|
||||
const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index)
|
||||
{
|
||||
dlog("Processing dividend payments for dividend holder asset type ${holder_asset} at time ${t}",
|
||||
("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time()));
|
||||
auto current_distribution_account_balance_range =
|
||||
balance_index.indices().get<by_account_asset>().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
|
||||
auto previous_distribution_account_balance_range =
|
||||
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||
// the current range is now all current balances for the distribution account, sorted by asset_type
|
||||
// the previous range is now all previous balances for this account, sorted by asset type
|
||||
|
||||
const auto& gpo = db.get_global_properties();
|
||||
|
||||
// get the list of accounts that hold nonzero balances of the dividend asset
|
||||
auto holder_balances_begin =
|
||||
balance_index.indices().get<by_asset_balance>().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||
auto holder_balances_end =
|
||||
balance_index.indices().get<by_asset_balance>().upper_bound(boost::make_tuple(dividend_holder_asset_obj.id, share_type()));
|
||||
uint32_t holder_account_count = std::distance(holder_balances_begin, holder_balances_end);
|
||||
uint64_t distribution_base_fee = gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_base_fee;
|
||||
uint32_t distribution_fee_per_holder = gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_fee_per_holder;
|
||||
// the fee, in BTS, for distributing each asset in the account
|
||||
uint64_t total_fee_per_asset_in_core = distribution_base_fee + holder_account_count * (uint64_t)distribution_fee_per_holder;
|
||||
|
||||
auto current_distribution_account_balance_iter = current_distribution_account_balance_range.first;
|
||||
auto previous_distribution_account_balance_iter = previous_distribution_account_balance_range.first;
|
||||
dlog("Current balances in distribution account: ${current}, Previous balances: ${previous}",
|
||||
("current", std::distance(current_distribution_account_balance_range.first, current_distribution_account_balance_range.second))
|
||||
("previous", std::distance(previous_distribution_account_balance_range.first, previous_distribution_account_balance_range.second)));
|
||||
|
||||
// when we pay out the dividends to the holders, we need to know the total balance of the dividend asset in all
|
||||
// accounts other than the distribution account (it would be silly to distribute dividends back to
|
||||
// the distribution account)
|
||||
share_type total_balance_of_dividend_asset;
|
||||
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end))
|
||||
if (holder_balance_object.owner != dividend_data.dividend_distribution_account)
|
||||
total_balance_of_dividend_asset += holder_balance_object.balance;
|
||||
|
||||
|
||||
// loop through all of the assets currently or previously held in the distribution account
|
||||
while (current_distribution_account_balance_iter != current_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter != previous_distribution_account_balance_range.second)
|
||||
{
|
||||
try
|
||||
{
|
||||
// First, figure out how much the balance on this asset has changed since the last sharing out
|
||||
share_type current_balance;
|
||||
share_type previous_balance;
|
||||
asset_id_type payout_asset_type;
|
||||
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type)
|
||||
{
|
||||
// there are no more previous balances or there is no previous balance for this particular asset type
|
||||
payout_asset_type = current_distribution_account_balance_iter->asset_type;
|
||||
current_balance = current_distribution_account_balance_iter->balance;
|
||||
idump((payout_asset_type)(current_balance));
|
||||
}
|
||||
else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type)
|
||||
{
|
||||
// there are no more current balances or there is no current balance for this particular previous asset type
|
||||
payout_asset_type = previous_distribution_account_balance_iter->dividend_payout_asset_type;
|
||||
previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval;
|
||||
idump((payout_asset_type)(previous_balance));
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have both a previous and a current balance for this asset type
|
||||
payout_asset_type = current_distribution_account_balance_iter->asset_type;
|
||||
current_balance = current_distribution_account_balance_iter->balance;
|
||||
previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval;
|
||||
idump((payout_asset_type)(current_balance)(previous_balance));
|
||||
}
|
||||
|
||||
share_type delta_balance = current_balance - previous_balance;
|
||||
|
||||
// Next, figure out if we want to share this out -- if the amount added to the distribution
|
||||
// account since last payout is too small, we won't bother.
|
||||
|
||||
share_type total_fee_per_asset_in_payout_asset;
|
||||
const asset_object* payout_asset_object = nullptr;
|
||||
if (payout_asset_type == asset_id_type())
|
||||
{
|
||||
payout_asset_object = &db.get_core_asset();
|
||||
total_fee_per_asset_in_payout_asset = total_fee_per_asset_in_core;
|
||||
dlog("Fee for distributing ${payout_asset_type}: ${fee}",
|
||||
("payout_asset_type", asset_id_type()(db).symbol)
|
||||
("fee", asset(total_fee_per_asset_in_core, asset_id_type())));
|
||||
}
|
||||
else
|
||||
{
|
||||
// figure out what the total fee is in terms of the payout asset
|
||||
const asset_index& asset_object_index = db.get_index_type<asset_index>();
|
||||
auto payout_asset_object_iter = asset_object_index.indices().find(payout_asset_type);
|
||||
FC_ASSERT(payout_asset_object_iter != asset_object_index.indices().end());
|
||||
|
||||
payout_asset_object = &*payout_asset_object_iter;
|
||||
asset total_fee_per_asset = asset(total_fee_per_asset_in_core, asset_id_type()) * payout_asset_object->options.core_exchange_rate;
|
||||
FC_ASSERT(total_fee_per_asset.asset_id == payout_asset_type);
|
||||
|
||||
total_fee_per_asset_in_payout_asset = total_fee_per_asset.amount;
|
||||
dlog("Fee for distributing ${payout_asset_type}: ${fee}",
|
||||
("payout_asset_type", payout_asset_type(db).symbol)("fee", total_fee_per_asset_in_payout_asset));
|
||||
}
|
||||
|
||||
share_type minimum_shares_to_distribute;
|
||||
if (dividend_data.options.minimum_fee_percentage)
|
||||
{
|
||||
fc::uint128_t minimum_amount_to_distribute = total_fee_per_asset_in_payout_asset.value;
|
||||
minimum_amount_to_distribute *= 100 * GRAPHENE_1_PERCENT;
|
||||
minimum_amount_to_distribute /= dividend_data.options.minimum_fee_percentage;
|
||||
wdump((total_fee_per_asset_in_payout_asset)(dividend_data.options));
|
||||
minimum_shares_to_distribute = minimum_amount_to_distribute.to_uint64();
|
||||
}
|
||||
|
||||
dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance));
|
||||
if (delta_balance > 0)
|
||||
{
|
||||
if (delta_balance >= minimum_shares_to_distribute)
|
||||
{
|
||||
// first, pay the fee for scheduling these dividend payments
|
||||
if (payout_asset_type == asset_id_type())
|
||||
{
|
||||
// pay fee to network
|
||||
db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) {
|
||||
d.accumulated_fees += total_fee_per_asset_in_core;
|
||||
});
|
||||
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||
asset(-total_fee_per_asset_in_core, asset_id_type()));
|
||||
delta_balance -= total_fee_per_asset_in_core;
|
||||
}
|
||||
else
|
||||
{
|
||||
const asset_dynamic_data_object& dynamic_data = payout_asset_object->dynamic_data(db);
|
||||
if (dynamic_data.fee_pool < total_fee_per_asset_in_core)
|
||||
FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} "
|
||||
"because insufficient funds in fee pool (need: ${need}, have: ${have})",
|
||||
("holder_asset_type", dividend_holder_asset_obj.symbol)
|
||||
("payout_asset_type", payout_asset_object->symbol)
|
||||
("need", asset(total_fee_per_asset_in_core, asset_id_type()))
|
||||
("have", asset(dynamic_data.fee_pool, payout_asset_type)));
|
||||
// deduct the fee from the dividend distribution account
|
||||
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||
asset(-total_fee_per_asset_in_payout_asset, payout_asset_type));
|
||||
// convert it to core
|
||||
db.modify(payout_asset_object->dynamic_data(db), [total_fee_per_asset_in_core, total_fee_per_asset_in_payout_asset](asset_dynamic_data_object& d) {
|
||||
d.fee_pool -= total_fee_per_asset_in_core;
|
||||
d.accumulated_fees += total_fee_per_asset_in_payout_asset;
|
||||
});
|
||||
// and pay it to the network
|
||||
db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) {
|
||||
d.accumulated_fees += total_fee_per_asset_in_core;
|
||||
});
|
||||
delta_balance -= total_fee_per_asset_in_payout_asset;
|
||||
}
|
||||
|
||||
dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}",
|
||||
("count", holder_account_count)
|
||||
("total", total_balance_of_dividend_asset));
|
||||
share_type remaining_amount_to_distribute = delta_balance;
|
||||
|
||||
// credit each account with their portion, don't send any back to the dividend distribution account
|
||||
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end))
|
||||
if (holder_balance_object.owner != dividend_data.dividend_distribution_account &&
|
||||
holder_balance_object.balance.value)
|
||||
{
|
||||
fc::uint128_t amount_to_credit(delta_balance.value);
|
||||
amount_to_credit *= holder_balance_object.balance.value;
|
||||
amount_to_credit /= total_balance_of_dividend_asset.value;
|
||||
wdump((delta_balance.value)(holder_balance_object.balance)(total_balance_of_dividend_asset));
|
||||
share_type shares_to_credit((int64_t)amount_to_credit.to_uint64());
|
||||
|
||||
remaining_amount_to_distribute -= shares_to_credit;
|
||||
|
||||
dlog("Crediting account ${account} with ${amount}",
|
||||
("account", holder_balance_object.owner(db).name)
|
||||
("amount", asset(shares_to_credit, payout_asset_type)));
|
||||
auto pending_payout_iter =
|
||||
pending_payout_balance_index.indices().get<by_dividend_payout_account>().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner));
|
||||
if (pending_payout_iter == pending_payout_balance_index.indices().get<by_dividend_payout_account>().end())
|
||||
db.create<pending_dividend_payout_balance_for_holder_object>( [&]( pending_dividend_payout_balance_for_holder_object& obj ){
|
||||
obj.owner = holder_balance_object.owner;
|
||||
obj.dividend_holder_asset_type = dividend_holder_asset_obj.id;
|
||||
obj.dividend_payout_asset_type = payout_asset_type;
|
||||
obj.pending_balance = shares_to_credit;
|
||||
});
|
||||
else
|
||||
db.modify(*pending_payout_iter, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){
|
||||
pending_balance.pending_balance += shares_to_credit;
|
||||
});
|
||||
}
|
||||
|
||||
for (const auto& pending_payout : pending_payout_balance_index.indices())
|
||||
dlog("Pending payout: ${account_name} -> ${amount}",
|
||||
("account_name", pending_payout.owner(db).name)
|
||||
("amount", asset(pending_payout.pending_balance, pending_payout.dividend_payout_asset_type)));
|
||||
dlog("Remaining balance not paid out: ${amount}",
|
||||
("amount", asset(remaining_amount_to_distribute, payout_asset_type)));
|
||||
|
||||
|
||||
share_type distributed_amount = delta_balance - remaining_amount_to_distribute;
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type)
|
||||
db.create<total_distributed_dividend_balance_object>( [&]( total_distributed_dividend_balance_object& obj ){
|
||||
obj.dividend_holder_asset_type = dividend_holder_asset_obj.id;
|
||||
obj.dividend_payout_asset_type = payout_asset_type;
|
||||
obj.balance_at_last_maintenance_interval = distributed_amount;
|
||||
});
|
||||
else
|
||||
db.modify(*previous_distribution_account_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval += distributed_amount;
|
||||
});
|
||||
}
|
||||
else
|
||||
FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} "
|
||||
"because amount ${delta_balance} is too small an amount to distribute.",
|
||||
("holder_asset_type", dividend_holder_asset_obj.symbol)
|
||||
("payout_asset_type", payout_asset_object->symbol)
|
||||
("delta_balance", asset(delta_balance, payout_asset_type)));
|
||||
}
|
||||
else if (delta_balance < 0)
|
||||
{
|
||||
// some amount of the asset has been withdrawn from the dividend_distribution_account,
|
||||
// meaning the current pending payout balances will add up to more than our current balance.
|
||||
// This should be extremely rare (caused by an override transfer by the asset owner).
|
||||
// Reduce all pending payouts proportionally
|
||||
share_type total_pending_balances;
|
||||
auto pending_payouts_range =
|
||||
pending_payout_balance_index.indices().get<by_dividend_payout_account>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type));
|
||||
|
||||
for (const pending_dividend_payout_balance_for_holder_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second))
|
||||
total_pending_balances += pending_balance_object.pending_balance;
|
||||
|
||||
share_type remaining_amount_to_recover = -delta_balance;
|
||||
share_type remaining_pending_balances = total_pending_balances;
|
||||
for (const pending_dividend_payout_balance_for_holder_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second))
|
||||
{
|
||||
fc::uint128_t amount_to_debit(remaining_amount_to_recover.value);
|
||||
amount_to_debit *= pending_balance_object.pending_balance.value;
|
||||
amount_to_debit /= remaining_pending_balances.value;
|
||||
share_type shares_to_debit((int64_t)amount_to_debit.to_uint64());
|
||||
|
||||
remaining_amount_to_recover -= shares_to_debit;
|
||||
remaining_pending_balances -= pending_balance_object.pending_balance;
|
||||
|
||||
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){
|
||||
pending_balance.pending_balance -= shares_to_debit;
|
||||
});
|
||||
}
|
||||
|
||||
// if we're here, we know there must be a previous balance, so just adjust it by the
|
||||
// amount we just reclaimed
|
||||
db.modify(*previous_distribution_account_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval += delta_balance;
|
||||
assert(obj.balance_at_last_maintenance_interval == current_balance);
|
||||
});
|
||||
} // end if deposit was large enough to distribute
|
||||
}
|
||||
catch (const fc::exception& e)
|
||||
{
|
||||
dlog("${e}", ("e", e));
|
||||
}
|
||||
|
||||
// iterate
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type)
|
||||
++current_distribution_account_balance_iter;
|
||||
else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type)
|
||||
++previous_distribution_account_balance_iter;
|
||||
else
|
||||
{
|
||||
++current_distribution_account_balance_iter;
|
||||
++previous_distribution_account_balance_iter;
|
||||
}
|
||||
}
|
||||
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
|
||||
dividend_data_obj.last_scheduled_distribution_time = current_head_block_time;
|
||||
dividend_data_obj.last_distribution_time = current_head_block_time;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void process_dividend_assets(database& db)
|
||||
{
|
||||
ilog("In process_dividend_assets time ${time}", ("time", db.head_block_time()));
|
||||
|
||||
const account_balance_index& balance_index = db.get_index_type<account_balance_index>();
|
||||
const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index = db.get_index_type<total_distributed_dividend_balance_object_index>();
|
||||
const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = db.get_index_type<pending_dividend_payout_balance_for_holder_object_index>();
|
||||
|
||||
// TODO: switch to iterating over only dividend assets (generalize the by_type index)
|
||||
for( const asset_object& dividend_holder_asset_obj : db.get_index_type<asset_index>().indices() )
|
||||
if (dividend_holder_asset_obj.dividend_data_id)
|
||||
{
|
||||
const asset_dividend_data_object& dividend_data = dividend_holder_asset_obj.dividend_data(db);
|
||||
const account_object& dividend_distribution_account_object = dividend_data.dividend_distribution_account(db);
|
||||
|
||||
fc::time_point_sec current_head_block_time = db.head_block_time();
|
||||
|
||||
schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, current_head_block_time,
|
||||
balance_index, distributed_dividend_balance_index, pending_payout_balance_index);
|
||||
if (dividend_data.options.next_payout_time &&
|
||||
db.head_block_time() >= *dividend_data.options.next_payout_time)
|
||||
{
|
||||
dlog("Dividend payout time has arrived for asset ${holder_asset}",
|
||||
("holder_asset", dividend_holder_asset_obj.symbol));
|
||||
|
||||
#ifndef NDEBUG
|
||||
// dump balances before the payouts for debugging
|
||||
const auto& balance_idx = db.get_index_type<account_balance_index>().indices().get<by_account_asset>();
|
||||
auto holder_account_balance_range = balance_idx.equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
|
||||
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second))
|
||||
ilog(" Current balance: ${asset}", ("asset", asset(holder_balance_object.balance, holder_balance_object.asset_type)));
|
||||
#endif
|
||||
|
||||
// when we do the payouts, we first increase the balances in all of the receiving accounts
|
||||
// and use this map to keep track of the total amount of each asset paid out.
|
||||
// Afterwards, we decrease the distribution account's balance by the total amount paid out,
|
||||
// and modify the distributed_balances accordingly
|
||||
std::map<asset_id_type, share_type> amounts_paid_out_by_asset;
|
||||
|
||||
auto pending_payouts_range =
|
||||
pending_payout_balance_index.indices().get<by_dividend_account_payout>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||
// the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account
|
||||
// we iterate in this order so we can build up a list of payouts for each account to put in the
|
||||
// virtual op
|
||||
flat_set<asset> payouts_for_this_holder;
|
||||
fc::optional<account_id_type> last_holder_account_id;
|
||||
|
||||
// cache the assets the distribution account is approved to send, we will be asking
|
||||
// for these often
|
||||
flat_map<asset_id_type, bool> approved_assets; // assets that the dividend distribution account is authorized to send/receive
|
||||
auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) {
|
||||
auto approved_assets_iter = approved_assets.find(asset_id);
|
||||
if (approved_assets_iter != approved_assets.end())
|
||||
return approved_assets_iter->second;
|
||||
bool is_approved = is_authorized_asset(db, dividend_distribution_account_object,
|
||||
asset_id(db));
|
||||
approved_assets[asset_id] = is_approved;
|
||||
return is_approved;
|
||||
};
|
||||
|
||||
for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; )
|
||||
{
|
||||
const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter;
|
||||
|
||||
if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner)
|
||||
{
|
||||
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
|
||||
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
|
||||
*last_holder_account_id,
|
||||
payouts_for_this_holder));
|
||||
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
|
||||
payouts_for_this_holder.clear();
|
||||
last_holder_account_id.reset();
|
||||
}
|
||||
|
||||
|
||||
if (is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) &&
|
||||
is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type))
|
||||
{
|
||||
dlog("Processing payout of ${asset} to account ${account}",
|
||||
("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type))
|
||||
("account", pending_balance_object.owner(db).name));
|
||||
|
||||
db.adjust_balance(pending_balance_object.owner,
|
||||
asset(pending_balance_object.pending_balance,
|
||||
pending_balance_object.dividend_payout_asset_type));
|
||||
payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance,
|
||||
pending_balance_object.dividend_payout_asset_type));
|
||||
last_holder_account_id = pending_balance_object.owner;
|
||||
amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance;
|
||||
|
||||
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){
|
||||
pending_balance.pending_balance = 0;
|
||||
});
|
||||
}
|
||||
|
||||
++pending_balance_object_iter;
|
||||
}
|
||||
// we will always be left with the last holder's data, generate the virtual op for it now.
|
||||
if (last_holder_account_id)
|
||||
{
|
||||
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
|
||||
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
|
||||
*last_holder_account_id,
|
||||
payouts_for_this_holder));
|
||||
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
|
||||
}
|
||||
|
||||
// now debit the total amount of dividends paid out from the distribution account
|
||||
// and reduce the distributed_balances accordingly
|
||||
|
||||
for (const auto& value : amounts_paid_out_by_asset)
|
||||
{
|
||||
const asset_id_type& asset_paid_out = value.first;
|
||||
const share_type& amount_paid_out = value.second;
|
||||
|
||||
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||
asset(-amount_paid_out,
|
||||
asset_paid_out));
|
||||
auto distributed_balance_iter =
|
||||
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().find(boost::make_tuple(dividend_holder_asset_obj.id,
|
||||
asset_paid_out));
|
||||
assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end());
|
||||
if (distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end())
|
||||
db.modify(*distributed_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// now schedule the next payout time
|
||||
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
|
||||
dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time;
|
||||
dividend_data_obj.last_payout_time = current_head_block_time;
|
||||
fc::optional<fc::time_point_sec> next_payout_time;
|
||||
if (dividend_data_obj.options.payout_interval)
|
||||
{
|
||||
// if there was a previous payout, make our next payment one interval
|
||||
uint32_t current_time_sec = current_head_block_time.sec_since_epoch();
|
||||
fc::time_point_sec reference_time = *dividend_data_obj.last_scheduled_payout_time;
|
||||
uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch();
|
||||
do
|
||||
next_possible_time_sec += *dividend_data_obj.options.payout_interval;
|
||||
while (next_possible_time_sec <= current_time_sec);
|
||||
|
||||
next_payout_time = next_possible_time_sec;
|
||||
}
|
||||
dividend_data_obj.options.next_payout_time = next_payout_time;
|
||||
idump((dividend_data_obj.last_scheduled_payout_time)
|
||||
(dividend_data_obj.last_payout_time)
|
||||
(dividend_data_obj.options.next_payout_time));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
|
||||
{
|
||||
const auto& gpo = get_global_properties();
|
||||
|
|
@ -692,6 +1175,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
distribute_fba_balances(*this);
|
||||
create_buyback_orders(*this);
|
||||
|
||||
process_dividend_assets(*this);
|
||||
|
||||
struct vote_tally_helper {
|
||||
database& d;
|
||||
const global_property_object& props;
|
||||
|
|
@ -831,7 +1316,12 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
}
|
||||
}
|
||||
|
||||
modify(get_dynamic_global_properties(), [next_maintenance_time](dynamic_global_property_object& d) {
|
||||
const dynamic_global_property_object& dgpo = get_dynamic_global_properties();
|
||||
|
||||
if( (dgpo.next_maintenance_time < HARDFORK_613_TIME) && (next_maintenance_time >= HARDFORK_613_TIME) )
|
||||
deprecate_annual_members(*this);
|
||||
|
||||
modify(dgpo, [next_maintenance_time](dynamic_global_property_object& d) {
|
||||
d.next_maintenance_time = next_maintenance_time;
|
||||
d.accounts_registered_this_interval = 0;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/global_property_object.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/market_object.hpp>
|
||||
#include <graphene/chain/proposal_object.hpp>
|
||||
#include <graphene/chain/transaction_object.hpp>
|
||||
|
|
@ -47,7 +48,12 @@ void database::update_global_dynamic_data( const signed_block& b )
|
|||
dynamic_global_property_id_type(0)(*this);
|
||||
|
||||
uint32_t missed_blocks = get_slot_at_time( b.timestamp );
|
||||
//#define DIRTY_TRICK // problem with missed_blocks can occur when "maintenance_interval" set to few minutes
|
||||
#ifdef DIRTY_TRICK
|
||||
if (missed_blocks != 0) {
|
||||
#else
|
||||
assert( missed_blocks != 0 );
|
||||
#endif
|
||||
missed_blocks--;
|
||||
for( uint32_t i = 0; i < missed_blocks; ++i ) {
|
||||
const auto& witness_missed = get_scheduled_witness( i+1 )(*this);
|
||||
|
|
@ -63,7 +69,9 @@ void database::update_global_dynamic_data( const signed_block& b )
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DIRTY_TRICK
|
||||
}
|
||||
#endif
|
||||
// dynamic global properties updating
|
||||
modify( _dgp, [&]( dynamic_global_property_object& dgp ){
|
||||
secret_hash_type::encoder enc;
|
||||
|
|
@ -445,7 +453,12 @@ void database::update_expired_feeds()
|
|||
assert( a.is_market_issued() );
|
||||
|
||||
const asset_bitasset_data_object& b = a.bitasset_data(*this);
|
||||
if( b.feed_is_expired(head_block_time()) )
|
||||
bool feed_is_expired;
|
||||
if( head_block_time() < HARDFORK_615_TIME )
|
||||
feed_is_expired = b.feed_is_expired_before_hardfork_615( head_block_time() );
|
||||
else
|
||||
feed_is_expired = b.feed_is_expired( head_block_time() );
|
||||
if( feed_is_expired )
|
||||
{
|
||||
modify(b, [this](asset_bitasset_data_object& a) {
|
||||
a.update_median_feeds(head_block_time());
|
||||
|
|
|
|||
4
libraries/chain/hardfork.d/599.hf
Normal file
4
libraries/chain/hardfork.d/599.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// #599 Unpacking of extension is incorrect
|
||||
#ifndef HARDFORK_599_TIME
|
||||
#define HARDFORK_599_TIME (fc::time_point_sec( 1458061200 ))
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/607.hf
Normal file
4
libraries/chain/hardfork.d/607.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// #607 Disable negative voting on workers
|
||||
#ifndef HARDFORK_607_TIME
|
||||
#define HARDFORK_607_TIME (fc::time_point_sec( 1458061200 ))
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/613.hf
Normal file
4
libraries/chain/hardfork.d/613.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// #613 Deprecate annual membership
|
||||
#ifndef HARDFORK_613_TIME
|
||||
#define HARDFORK_613_TIME (fc::time_point_sec( 1458061200 ))
|
||||
#endif
|
||||
4
libraries/chain/hardfork.d/615.hf
Normal file
4
libraries/chain/hardfork.d/615.hf
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// #615 Fix price feed expiration check, so websocket server will never spam too much data
|
||||
#ifndef HARDFORK_615_TIME
|
||||
#define HARDFORK_615_TIME (fc::time_point_sec( 1457550000 ))
|
||||
#endif
|
||||
|
|
@ -50,6 +50,7 @@ namespace graphene { namespace chain {
|
|||
* 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;
|
||||
uint32_t total_ops = 0;
|
||||
|
||||
/**
|
||||
* When calculating votes it is necessary to know how much is stored in orders (and thus unavailable for
|
||||
|
|
@ -307,6 +308,31 @@ namespace graphene { namespace chain {
|
|||
/** maps the referrer to the set of accounts that they have referred */
|
||||
map< account_id_type, set<account_id_type> > referred_by;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Tracks a pending payout of a single dividend payout asset
|
||||
* from a single dividend holder asset to a holder's account.
|
||||
*
|
||||
* Each maintenance interval, this will be adjusted to account for
|
||||
* any new transfers to the dividend distribution account.
|
||||
* @ingroup object
|
||||
*
|
||||
*/
|
||||
class pending_dividend_payout_balance_for_holder_object : public abstract_object<pending_dividend_payout_balance_for_holder_object>
|
||||
{
|
||||
public:
|
||||
static const uint8_t space_id = implementation_ids;
|
||||
static const uint8_t type_id = impl_pending_dividend_payout_balance_for_holder_object_type;
|
||||
|
||||
account_id_type owner;
|
||||
asset_id_type dividend_holder_asset_type;
|
||||
asset_id_type dividend_payout_asset_type;
|
||||
share_type pending_balance;
|
||||
|
||||
asset get_pending_balance()const { return asset(pending_balance, dividend_payout_asset_type); }
|
||||
void adjust_balance(const asset& delta);
|
||||
};
|
||||
|
||||
|
||||
struct by_account_asset;
|
||||
struct by_asset_balance;
|
||||
|
|
@ -363,6 +389,49 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
typedef generic_index<account_object, account_multi_index_type> account_index;
|
||||
|
||||
struct by_dividend_payout_account{}; // use when calculating pending payouts
|
||||
struct by_dividend_account_payout{}; // use when doing actual payouts
|
||||
struct by_account_dividend_payout{}; // use in get_full_accounts()
|
||||
|
||||
/**
|
||||
* @ingroup object_index
|
||||
*/
|
||||
typedef multi_index_container<
|
||||
pending_dividend_payout_balance_for_holder_object,
|
||||
indexed_by<
|
||||
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||
ordered_unique< tag<by_dividend_payout_account>,
|
||||
composite_key<
|
||||
pending_dividend_payout_balance_for_holder_object,
|
||||
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_holder_asset_type>,
|
||||
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_payout_asset_type>,
|
||||
member<pending_dividend_payout_balance_for_holder_object, account_id_type, &pending_dividend_payout_balance_for_holder_object::owner>
|
||||
>
|
||||
>,
|
||||
ordered_unique< tag<by_dividend_account_payout>,
|
||||
composite_key<
|
||||
pending_dividend_payout_balance_for_holder_object,
|
||||
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_holder_asset_type>,
|
||||
member<pending_dividend_payout_balance_for_holder_object, account_id_type, &pending_dividend_payout_balance_for_holder_object::owner>,
|
||||
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_payout_asset_type>
|
||||
>
|
||||
>,
|
||||
ordered_unique< tag<by_account_dividend_payout>,
|
||||
composite_key<
|
||||
pending_dividend_payout_balance_for_holder_object,
|
||||
member<pending_dividend_payout_balance_for_holder_object, account_id_type, &pending_dividend_payout_balance_for_holder_object::owner>,
|
||||
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_holder_asset_type>,
|
||||
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_payout_asset_type>
|
||||
>
|
||||
>
|
||||
>
|
||||
> pending_dividend_payout_balance_for_holder_object_multi_index_type;
|
||||
|
||||
/**
|
||||
* @ingroup object_index
|
||||
*/
|
||||
typedef generic_index<pending_dividend_payout_balance_for_holder_object, pending_dividend_payout_balance_for_holder_object_multi_index_type> pending_dividend_payout_balance_for_holder_object_index;
|
||||
|
||||
}}
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::account_object,
|
||||
|
|
@ -385,8 +454,14 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object,
|
|||
(graphene::chain::object),
|
||||
(owner)
|
||||
(most_recent_op)
|
||||
(total_ops)
|
||||
(total_core_in_orders)
|
||||
(lifetime_fees_paid)
|
||||
(pending_fees)(pending_vested_fees)
|
||||
)
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::pending_dividend_payout_balance_for_holder_object,
|
||||
(graphene::db::object),
|
||||
(owner)(dividend_holder_asset_type)(dividend_payout_asset_type)(pending_balance) )
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,18 @@ namespace graphene { namespace chain {
|
|||
const asset_bitasset_data_object* bitasset_to_update = nullptr;
|
||||
};
|
||||
|
||||
class asset_update_dividend_evaluator : public evaluator<asset_update_dividend_evaluator>
|
||||
{
|
||||
public:
|
||||
typedef asset_update_dividend_operation operation_type;
|
||||
|
||||
void_result do_evaluate( const asset_update_dividend_operation& o );
|
||||
void_result do_apply( const asset_update_dividend_operation& o );
|
||||
|
||||
const asset_object* asset_to_update = nullptr;
|
||||
const asset_dividend_data_object* asset_dividend_data_to_update = nullptr;
|
||||
};
|
||||
|
||||
class asset_update_feed_producers_evaluator : public evaluator<asset_update_feed_producers_evaluator>
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -132,6 +132,9 @@ namespace graphene { namespace chain {
|
|||
|
||||
optional<account_id_type> buyback_account;
|
||||
|
||||
/// Extra data associated with dividend-paying assets.
|
||||
optional<asset_dividend_data_id_type> dividend_data_id;
|
||||
|
||||
asset_id_type get_id()const { return id; }
|
||||
|
||||
void validate()const
|
||||
|
|
@ -148,6 +151,10 @@ namespace graphene { namespace chain {
|
|||
const asset_bitasset_data_object& bitasset_data(const DB& db)const
|
||||
{ assert(bitasset_data_id); return db.get(*bitasset_data_id); }
|
||||
|
||||
template<class DB>
|
||||
const asset_dividend_data_object& dividend_data(const DB& db)const
|
||||
{ assert(dividend_data_id); return db.get(*dividend_data_id); }
|
||||
|
||||
template<class DB>
|
||||
const asset_dynamic_data_object& dynamic_data(const DB& db)const
|
||||
{ return db.get(dynamic_asset_data_id); }
|
||||
|
|
@ -211,8 +218,10 @@ namespace graphene { namespace chain {
|
|||
|
||||
time_point_sec feed_expiration_time()const
|
||||
{ return current_feed_publication_time + options.feed_lifetime_sec; }
|
||||
bool feed_is_expired(time_point_sec current_time)const
|
||||
bool feed_is_expired_before_hardfork_615(time_point_sec current_time)const
|
||||
{ return feed_expiration_time() >= current_time; }
|
||||
bool feed_is_expired(time_point_sec current_time)const
|
||||
{ return feed_expiration_time() <= current_time; }
|
||||
void update_median_feeds(time_point_sec current_time);
|
||||
};
|
||||
|
||||
|
|
@ -245,6 +254,84 @@ namespace graphene { namespace chain {
|
|||
> asset_object_multi_index_type;
|
||||
typedef generic_index<asset_object, asset_object_multi_index_type> asset_index;
|
||||
|
||||
/**
|
||||
* @brief contains properties that only apply to dividend-paying assets
|
||||
*
|
||||
* @ingroup object
|
||||
* @ingroup implementation
|
||||
*/
|
||||
class asset_dividend_data_object : public abstract_object<asset_dividend_data_object>
|
||||
{
|
||||
public:
|
||||
static const uint8_t space_id = implementation_ids;
|
||||
static const uint8_t type_id = impl_asset_dividend_data_type;
|
||||
|
||||
/// The tunable options for Dividend-paying assets are stored in this field.
|
||||
dividend_asset_options options;
|
||||
|
||||
/// The time payouts on this asset were scheduled to be processed last
|
||||
/// This field is reset any time the dividend_asset_options are updated
|
||||
fc::optional<time_point_sec> last_scheduled_payout_time;
|
||||
|
||||
/// The time payouts on this asset were last processed
|
||||
/// (this should be the maintenance interval at or after last_scheduled_payout_time)
|
||||
/// This can be displayed for the user
|
||||
fc::optional<time_point_sec> last_payout_time;
|
||||
|
||||
/// The time pending payouts on this asset were last computed, used for
|
||||
/// correctly computing the next pending payout time.
|
||||
/// This field is reset any time the dividend_asset_options are updated
|
||||
fc::optional<time_point_sec> last_scheduled_distribution_time;
|
||||
|
||||
/// The time pending payouts on this asset were last computed.
|
||||
/// (this should be the maintenance interval at or after last_scheduled_distribution_time)
|
||||
/// This can be displayed for the user
|
||||
fc::optional<time_point_sec> last_distribution_time;
|
||||
|
||||
/// The account which collects pending payouts
|
||||
account_id_type dividend_distribution_account;
|
||||
};
|
||||
typedef multi_index_container<
|
||||
asset_dividend_data_object,
|
||||
indexed_by<
|
||||
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >
|
||||
>
|
||||
> asset_dividend_data_object_multi_index_type;
|
||||
typedef generic_index<asset_dividend_data_object, asset_dividend_data_object_multi_index_type> asset_dividend_data_object_index;
|
||||
|
||||
|
||||
// This tracks the balances in a dividend distribution account at the last time
|
||||
// pending dividend payouts were calculated (last maintenance interval).
|
||||
// At each maintenance interval, we will compare the current balance to the
|
||||
// balance stored here to see how much was deposited during that interval.
|
||||
class total_distributed_dividend_balance_object : public abstract_object<total_distributed_dividend_balance_object>
|
||||
{
|
||||
public:
|
||||
static const uint8_t space_id = implementation_ids;
|
||||
static const uint8_t type_id = impl_distributed_dividend_balance_data_type;
|
||||
|
||||
asset_id_type dividend_holder_asset_type;
|
||||
asset_id_type dividend_payout_asset_type;
|
||||
share_type balance_at_last_maintenance_interval;
|
||||
};
|
||||
struct by_dividend_payout_asset{};
|
||||
typedef multi_index_container<
|
||||
total_distributed_dividend_balance_object,
|
||||
indexed_by<
|
||||
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||
ordered_unique< tag<by_dividend_payout_asset>,
|
||||
composite_key<
|
||||
total_distributed_dividend_balance_object,
|
||||
member<total_distributed_dividend_balance_object, asset_id_type, &total_distributed_dividend_balance_object::dividend_holder_asset_type>,
|
||||
member<total_distributed_dividend_balance_object, asset_id_type, &total_distributed_dividend_balance_object::dividend_payout_asset_type>
|
||||
>
|
||||
>
|
||||
>
|
||||
> total_distributed_dividend_balance_object_multi_index_type;
|
||||
typedef generic_index<total_distributed_dividend_balance_object, total_distributed_dividend_balance_object_multi_index_type> total_distributed_dividend_balance_object_index;
|
||||
|
||||
|
||||
|
||||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::asset_dynamic_data_object, (graphene::db::object),
|
||||
|
|
@ -260,6 +347,19 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db::
|
|||
(settlement_price)
|
||||
(settlement_fund)
|
||||
)
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::asset_dividend_data_object, (graphene::db::object),
|
||||
(options)
|
||||
(last_scheduled_payout_time)
|
||||
(last_payout_time )
|
||||
(dividend_distribution_account)
|
||||
)
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::total_distributed_dividend_balance_object, (graphene::db::object),
|
||||
(dividend_holder_asset_type)
|
||||
(dividend_payout_asset_type)
|
||||
(balance_at_last_maintenance_interval)
|
||||
)
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object),
|
||||
(symbol)
|
||||
|
|
|
|||
|
|
@ -173,3 +173,14 @@
|
|||
///@}
|
||||
|
||||
#define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(743))
|
||||
|
||||
#define TOURNAMENT_MIN_ROUND_DELAY 0
|
||||
#define TOURNAMENT_MAX_ROUND_DELAY 600
|
||||
#define TOURNAMENT_MIN_TIME_PER_COMMIT_MOVE 0
|
||||
#define TOURNAMENT_MAN_TIME_PER_COMMIT_MOVE 600
|
||||
#define TOURNAMENT_MIN_TIME_PER_REVEAL_MOVE 0
|
||||
#define TOURNAMENT_MAX_TIME_PER_REVEAL_MOVE 600
|
||||
#define TOURNAMENT_DEFAULT_RAKE_FEE_PERCENTAGE (3*GRAPHENE_1_PERCENT)
|
||||
#define TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE (1*GRAPHENE_1_PERCENT)
|
||||
#define TOURNAMENT_MAXIMAL_RAKE_FEE_PERCENTAGE (20*GRAPHENE_1_PERCENT)
|
||||
#define TOURNAMENT_MAXIMAL_REGISTRATION_DEADLINE (60*60*24*30) // seconds, 30 days
|
||||
|
|
|
|||
|
|
@ -326,6 +326,8 @@ namespace graphene { namespace chain {
|
|||
//////////////////// db_debug.cpp ////////////////////
|
||||
|
||||
void debug_dump();
|
||||
void apply_debug_updates();
|
||||
void debug_update( const fc::variant_object& update );
|
||||
|
||||
//////////////////// db_market.cpp ////////////////////
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ FC_REFLECT_ENUM(graphene::chain::game_state,
|
|||
(expecting_reveal_moves)
|
||||
(game_complete))
|
||||
|
||||
FC_REFLECT_TYPENAME(graphene::chain::game_object) // manually serialized
|
||||
//FC_REFLECT_TYPENAME(graphene::chain::game_object) // manually serialized
|
||||
FC_REFLECT(graphene::chain::game_object, (players))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -162,5 +162,6 @@ FC_REFLECT_ENUM(graphene::chain::match_state,
|
|||
(match_in_progress)
|
||||
(match_complete))
|
||||
|
||||
FC_REFLECT_TYPENAME(graphene::chain::match_object) // manually serialized
|
||||
//FC_REFLECT_TYPENAME(graphene::chain::match_object) // manually serialized
|
||||
FC_REFLECT(graphene::chain::match_object, (players))
|
||||
|
||||
|
|
|
|||
|
|
@ -43,5 +43,6 @@ namespace graphene { namespace chain {
|
|||
~node_property_object(){}
|
||||
|
||||
uint32_t skip_flags = 0;
|
||||
std::map< block_id_type, std::vector< fc::variant_object > > debug_updates;
|
||||
};
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#pragma once
|
||||
#include <graphene/chain/protocol/operations.hpp>
|
||||
#include <graphene/db/object.hpp>
|
||||
#include <boost/multi_index/composite_key.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
|
|
@ -89,13 +90,44 @@ namespace graphene { namespace chain {
|
|||
public:
|
||||
static const uint8_t space_id = implementation_ids;
|
||||
static const uint8_t type_id = impl_account_transaction_history_object_type;
|
||||
account_id_type account; /// the account this operation applies to
|
||||
operation_history_id_type operation_id;
|
||||
uint32_t sequence = 0; /// the operation position within the given account
|
||||
account_transaction_history_id_type next;
|
||||
|
||||
//std::pair<account_id_type,operation_history_id_type> account_op()const { return std::tie( account, operation_id ); }
|
||||
//std::pair<account_id_type,uint32_t> account_seq()const { return std::tie( account, sequence ); }
|
||||
};
|
||||
|
||||
struct by_id;
|
||||
struct by_seq;
|
||||
struct by_op;
|
||||
typedef multi_index_container<
|
||||
account_transaction_history_object,
|
||||
indexed_by<
|
||||
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||
ordered_unique< tag<by_seq>,
|
||||
composite_key< account_transaction_history_object,
|
||||
member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>,
|
||||
member< account_transaction_history_object, uint32_t, &account_transaction_history_object::sequence>
|
||||
>
|
||||
>,
|
||||
ordered_unique< tag<by_op>,
|
||||
composite_key< account_transaction_history_object,
|
||||
member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>,
|
||||
member< account_transaction_history_object, operation_history_id_type, &account_transaction_history_object::operation_id>
|
||||
>
|
||||
>
|
||||
>
|
||||
> account_transaction_history_multi_index_type;
|
||||
|
||||
typedef generic_index<account_transaction_history_object, account_transaction_history_multi_index_type> account_transaction_history_index;
|
||||
|
||||
|
||||
} } // graphene::chain
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::operation_history_object, (graphene::chain::object),
|
||||
(op)(result)(block_num)(trx_in_block)(op_in_trx)(virtual_op) )
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::account_transaction_history_object, (graphene::chain::object),
|
||||
(operation_id)(next) )
|
||||
(account)(operation_id)(sequence)(next) )
|
||||
|
|
|
|||
|
|
@ -112,6 +112,53 @@ namespace graphene { namespace chain {
|
|||
void validate()const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The dividend_asset_options struct contains configurable options available only to dividend-paying assets.
|
||||
*
|
||||
* @note Changes to this struct will break protocol compatibility
|
||||
*/
|
||||
struct dividend_asset_options {
|
||||
/// Time when the next payout should occur.
|
||||
/// The payouts will happen on the maintenance interval at or after this time
|
||||
/// If this is set to null, there will be no payouts.
|
||||
fc::optional<fc::time_point_sec> next_payout_time;
|
||||
/// If payouts happen on a fixed schedule, this specifies the interval between
|
||||
/// payouts in seconds. After each payout, the next payout time will be incremented by
|
||||
/// this amount.
|
||||
/// If payout_interval is not set, the next payout (if any) will be the last until
|
||||
/// the options are updated again.
|
||||
fc::optional<uint32_t> payout_interval;
|
||||
/// Each dividend distribution incurs a fee that is based on the number of accounts
|
||||
/// that hold the dividend asset, not as a percentage of the amount paid out.
|
||||
/// This parameter prevents assets from being distributed unless the fee is less than
|
||||
/// the percentage here, to prevent a slow trickle of deposits to the account from being
|
||||
/// completely consumed.
|
||||
/// In other words, if you set this parameter to 10% and the fees work out to 100 BTS
|
||||
/// to share out, balances in the dividend distribution accounts will not be shared out
|
||||
/// if the balance is less than 10000 BTS.
|
||||
uint64_t minimum_fee_percentage;
|
||||
|
||||
/// Normally, pending dividend payments are calculated each maintenance interval in
|
||||
/// which there are balances in the dividend distribution account. At present, this
|
||||
/// is once per hour on the BitShares blockchain. If this is too often (too expensive
|
||||
/// in fees or to computationally-intensive for the blockchain) this can be increased.
|
||||
/// If you set this to, for example, one day, distributions will take place on even
|
||||
/// multiples of one day, allowing deposits to the distribution account to accumulate
|
||||
/// for 23 maintenance intervals and then computing the pending payouts on the 24th.
|
||||
///
|
||||
/// Payouts will always occur at the next payout time whether or not it falls on a
|
||||
/// multiple of the distribution interval, and the timer on the distribution interval
|
||||
/// are reset at payout time. So if you have the distribution interval at three days
|
||||
/// and the payout interval at one week, payouts will occur at days 3, 6, 7, 10, 13, 14...
|
||||
fc::optional<uint32_t> minimum_distribution_interval;
|
||||
|
||||
extensions_type extensions;
|
||||
|
||||
/// Perform internal consistency checks.
|
||||
/// @throws fc::exception if any check fails
|
||||
void validate()const;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ingroup operations
|
||||
|
|
@ -236,6 +283,59 @@ namespace graphene { namespace chain {
|
|||
{ return 0; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Virtual op generated when a dividend asset pays out dividends
|
||||
*/
|
||||
struct asset_dividend_distribution_operation : public base_operation
|
||||
{
|
||||
asset_dividend_distribution_operation() {}
|
||||
asset_dividend_distribution_operation(const asset_id_type& dividend_asset_id,
|
||||
const account_id_type& account_id,
|
||||
const flat_set<asset>& amounts) :
|
||||
dividend_asset_id(dividend_asset_id),
|
||||
account_id(account_id),
|
||||
amounts(amounts)
|
||||
{}
|
||||
struct fee_parameters_type {
|
||||
/* note: this is a virtual op and there are no fees directly charged for it */
|
||||
|
||||
/* Whenever the system computes the pending dividend payments for an asset,
|
||||
* it charges the distribution_base_fee + distribution_fee_per_holder.
|
||||
* The computational cost of distributing the dividend payment is proportional
|
||||
* to the number of dividend holders the asset is divided up among.
|
||||
*/
|
||||
/** This fee is charged whenever the system schedules pending dividend
|
||||
* payments.
|
||||
*/
|
||||
uint64_t distribution_base_fee;
|
||||
/** This fee is charged (in addition to the distribution_base_fee) for each
|
||||
* user the dividend payment is shared out amongst
|
||||
*/
|
||||
uint32_t distribution_fee_per_holder;
|
||||
};
|
||||
|
||||
asset fee;
|
||||
|
||||
/// The dividend-paying asset which triggered this payout
|
||||
asset_id_type dividend_asset_id;
|
||||
|
||||
/// The user account receiving the dividends
|
||||
account_id_type account_id;
|
||||
|
||||
/// The amounts received
|
||||
flat_set<asset> amounts;
|
||||
|
||||
extensions_type extensions;
|
||||
|
||||
account_id_type fee_payer()const { return account_id; }
|
||||
void validate()const {
|
||||
FC_ASSERT( false, "virtual operation" );
|
||||
}
|
||||
|
||||
share_type calculate_fee(const fee_parameters_type& params)const
|
||||
{ return 0; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup operations
|
||||
*/
|
||||
|
|
@ -319,6 +419,35 @@ namespace graphene { namespace chain {
|
|||
void validate()const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Update options specific to dividend-paying assets
|
||||
* @ingroup operations
|
||||
*
|
||||
* Dividend-paying assets have some options which are not relevant to other asset types.
|
||||
* This operation is used to update those options an an existing dividend-paying asset.
|
||||
* This can also be used to convert a non-dividend-paying asset into a dividend-paying
|
||||
* asset.
|
||||
*
|
||||
* @pre @ref issuer MUST be an existing account and MUST match asset_object::issuer on @ref asset_to_update
|
||||
* @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it
|
||||
* @pre @ref new_options SHALL be internally consistent, as verified by @ref validate()
|
||||
* @post @ref asset_to_update will have dividend-specific options matching those of new_options
|
||||
*/
|
||||
struct asset_update_dividend_operation : public base_operation
|
||||
{
|
||||
struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; };
|
||||
|
||||
asset fee;
|
||||
account_id_type issuer;
|
||||
asset_id_type asset_to_update;
|
||||
|
||||
dividend_asset_options new_options;
|
||||
extensions_type extensions;
|
||||
|
||||
account_id_type fee_payer()const { return issuer; }
|
||||
void validate()const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Update the set of feed-producing accounts for a BitAsset
|
||||
* @ingroup operations
|
||||
|
|
@ -462,6 +591,13 @@ FC_REFLECT( graphene::chain::asset_options,
|
|||
(description)
|
||||
(extensions)
|
||||
)
|
||||
|
||||
FC_REFLECT( graphene::chain::dividend_asset_options,
|
||||
(next_payout_time)
|
||||
(payout_interval)
|
||||
(extensions)
|
||||
)
|
||||
|
||||
FC_REFLECT( graphene::chain::bitasset_options,
|
||||
(feed_lifetime_sec)
|
||||
(minimum_feeds)
|
||||
|
|
@ -480,11 +616,12 @@ FC_REFLECT( graphene::chain::asset_settle_cancel_operation::fee_parameters_type,
|
|||
FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation::fee_parameters_type, (fee) )
|
||||
FC_REFLECT( graphene::chain::asset_update_operation::fee_parameters_type, (fee)(price_per_kbyte) )
|
||||
FC_REFLECT( graphene::chain::asset_update_bitasset_operation::fee_parameters_type, (fee) )
|
||||
FC_REFLECT( graphene::chain::asset_update_dividend_operation::fee_parameters_type, (fee) )
|
||||
FC_REFLECT( graphene::chain::asset_update_feed_producers_operation::fee_parameters_type, (fee) )
|
||||
FC_REFLECT( graphene::chain::asset_publish_feed_operation::fee_parameters_type, (fee) )
|
||||
FC_REFLECT( graphene::chain::asset_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) )
|
||||
FC_REFLECT( graphene::chain::asset_reserve_operation::fee_parameters_type, (fee) )
|
||||
|
||||
FC_REFLECT( graphene::chain::asset_dividend_distribution_operation::fee_parameters_type, (distribution_base_fee)(distribution_fee_per_holder))
|
||||
|
||||
FC_REFLECT( graphene::chain::asset_create_operation,
|
||||
(fee)
|
||||
|
|
@ -511,6 +648,13 @@ FC_REFLECT( graphene::chain::asset_update_bitasset_operation,
|
|||
(new_options)
|
||||
(extensions)
|
||||
)
|
||||
FC_REFLECT( graphene::chain::asset_update_dividend_operation,
|
||||
(fee)
|
||||
(issuer)
|
||||
(asset_to_update)
|
||||
(new_options)
|
||||
(extensions)
|
||||
)
|
||||
FC_REFLECT( graphene::chain::asset_update_feed_producers_operation,
|
||||
(fee)(issuer)(asset_to_update)(new_feed_producers)(extensions)
|
||||
)
|
||||
|
|
@ -525,3 +669,4 @@ FC_REFLECT( graphene::chain::asset_reserve_operation,
|
|||
(fee)(payer)(amount_to_reserve)(extensions) )
|
||||
|
||||
FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount)(extensions) );
|
||||
FC_REFLECT( graphene::chain::asset_dividend_distribution_operation, (fee)(dividend_asset_id)(account_id)(amounts)(extensions) );
|
||||
|
|
|
|||
|
|
@ -70,12 +70,14 @@ namespace graphene { namespace chain {
|
|||
uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling
|
||||
uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH;
|
||||
/* rps tournament parameters constraints */
|
||||
uint32_t min_round_delay = 0; ///< miniaml delay between games
|
||||
uint32_t max_round_delay = 600; ///< maxiaml delay between games
|
||||
uint32_t min_time_per_commit_move = 0; ///< minimal time to commit the next move
|
||||
uint32_t max_time_per_commit_move = 600; ///< maximal time to commit the next move
|
||||
uint32_t min_time_per_reveal_move = 0; ///< minimal time to reveal move
|
||||
uint32_t max_time_per_reveal_move = 600; ///< maximal time to reveal move
|
||||
uint32_t min_round_delay = TOURNAMENT_MIN_ROUND_DELAY; ///< miniaml delay between games
|
||||
uint32_t max_round_delay = TOURNAMENT_MAX_ROUND_DELAY; ///< maximal delay between games
|
||||
uint32_t min_time_per_commit_move = TOURNAMENT_MIN_TIME_PER_COMMIT_MOVE; ///< minimal time to commit the next move
|
||||
uint32_t max_time_per_commit_move = TOURNAMENT_MAN_TIME_PER_COMMIT_MOVE; ///< maximal time to commit the next move
|
||||
uint32_t min_time_per_reveal_move = TOURNAMENT_MIN_TIME_PER_REVEAL_MOVE; ///< minimal time to reveal move
|
||||
uint32_t max_time_per_reveal_move = TOURNAMENT_MAX_TIME_PER_REVEAL_MOVE; ///< maximal time to reveal move
|
||||
uint16_t rake_fee_percentage = TOURNAMENT_DEFAULT_RAKE_FEE_PERCENTAGE; ///< part of prize paid into the dividend account for the core token holders
|
||||
uint32_t maximum_registration_deadline = TOURNAMENT_MAXIMAL_REGISTRATION_DEADLINE; ///< value registration deadline must be before
|
||||
extensions_type extensions;
|
||||
|
||||
/** defined in fee_schedule.cpp */
|
||||
|
|
@ -119,5 +121,7 @@ FC_REFLECT( graphene::chain::chain_parameters,
|
|||
(max_time_per_commit_move)
|
||||
(min_time_per_reveal_move)
|
||||
(max_time_per_reveal_move)
|
||||
(rake_fee_percentage)
|
||||
(maximum_registration_deadline)
|
||||
(extensions)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ void operator<<( Stream& stream, const graphene::chain::extension<T>& value )
|
|||
fc::reflector<T>::visit( read_vtor );
|
||||
}
|
||||
|
||||
|
||||
|
||||
template< typename Stream, typename T >
|
||||
struct graphene_extension_unpack_visitor
|
||||
{
|
||||
|
|
@ -108,7 +110,7 @@ struct graphene_extension_unpack_visitor
|
|||
{
|
||||
if( (count_left > 0) && (which == next_which) )
|
||||
{
|
||||
Member temp;
|
||||
typename Member::value_type temp;
|
||||
fc::raw::unpack( stream, temp );
|
||||
(value.*member) = temp;
|
||||
--count_left;
|
||||
|
|
|
|||
|
|
@ -95,7 +95,9 @@ namespace graphene { namespace chain {
|
|||
fba_distribute_operation, // VIRTUAL
|
||||
tournament_create_operation,
|
||||
tournament_join_operation,
|
||||
game_move_operation
|
||||
game_move_operation,
|
||||
asset_update_dividend_operation,
|
||||
asset_dividend_distribution_operation // VIRTUAL
|
||||
> operation;
|
||||
|
||||
/// @} // operations group
|
||||
|
|
|
|||
|
|
@ -159,7 +159,10 @@ namespace graphene { namespace chain {
|
|||
impl_budget_record_object_type,
|
||||
impl_special_authority_object_type,
|
||||
impl_buyback_object_type,
|
||||
impl_fba_accumulator_object_type
|
||||
impl_fba_accumulator_object_type,
|
||||
impl_asset_dividend_data_type,
|
||||
impl_pending_dividend_payout_balance_for_holder_object_type,
|
||||
impl_distributed_dividend_balance_data_type
|
||||
};
|
||||
|
||||
//typedef fc::unsigned_int object_id_type;
|
||||
|
|
@ -220,11 +223,15 @@ namespace graphene { namespace chain {
|
|||
class buyback_object;
|
||||
class fba_accumulator_object;
|
||||
class tournament_details_object;
|
||||
class asset_dividend_data_object;
|
||||
class pending_dividend_payout_balance_for_holder_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;
|
||||
typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> asset_dynamic_data_id_type;
|
||||
typedef object_id< implementation_ids, impl_asset_bitasset_data_type, asset_bitasset_data_object> asset_bitasset_data_id_type;
|
||||
typedef object_id< implementation_ids, impl_asset_dividend_data_type, asset_dividend_data_object> asset_dividend_data_id_type;
|
||||
typedef object_id< implementation_ids, impl_pending_dividend_payout_balance_for_holder_object_type, pending_dividend_payout_balance_for_holder_object> pending_dividend_payout_balance_for_holder_object_type;
|
||||
typedef object_id< implementation_ids, impl_account_balance_object_type, account_balance_object> account_balance_id_type;
|
||||
typedef object_id< implementation_ids, impl_account_statistics_object_type,account_statistics_object> account_statistics_id_type;
|
||||
typedef object_id< implementation_ids, impl_transaction_object_type, transaction_object> transaction_obj_id_type;
|
||||
|
|
@ -378,6 +385,9 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
|
|||
(impl_special_authority_object_type)
|
||||
(impl_buyback_object_type)
|
||||
(impl_fba_accumulator_object_type)
|
||||
(impl_asset_dividend_data_type)
|
||||
(impl_pending_dividend_payout_balance_for_holder_object_type)
|
||||
(impl_distributed_dividend_balance_data_type)
|
||||
)
|
||||
|
||||
FC_REFLECT_TYPENAME( graphene::chain::share_type )
|
||||
|
|
|
|||
|
|
@ -232,7 +232,8 @@ FC_REFLECT_DERIVED(graphene::chain::tournament_details_object, (graphene::db::ob
|
|||
(registered_players)
|
||||
(payers)
|
||||
(matches))
|
||||
FC_REFLECT_TYPENAME(graphene::chain::tournament_object) // manually serialized
|
||||
//FC_REFLECT_TYPENAME(graphene::chain::tournament_object) // manually serialized
|
||||
FC_REFLECT(graphene::chain::tournament_object, (creator))
|
||||
FC_REFLECT_ENUM(graphene::chain::tournament_state,
|
||||
(accepting_registrations)
|
||||
(awaiting_start)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
*/
|
||||
#include <graphene/chain/protocol/account.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
/**
|
||||
|
|
@ -135,6 +135,12 @@ bool is_valid_name( const string& name )
|
|||
break;
|
||||
begin = end+1;
|
||||
}
|
||||
|
||||
// only dividend distribution accounts linked to a dividend asset can end in -dividend-distribution, and
|
||||
// these can only be created as a side-effect of the asset_update_dividend_operation
|
||||
if( boost::algorithm::ends_with(name, "-dividend-distribution") )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
} FC_CAPTURE_AND_RETHROW( (name) ) }
|
||||
|
||||
|
|
|
|||
|
|
@ -183,6 +183,12 @@ void asset_update_bitasset_operation::validate() const
|
|||
new_options.validate();
|
||||
}
|
||||
|
||||
void asset_update_dividend_operation::validate() const
|
||||
{
|
||||
FC_ASSERT( fee.amount >= 0 );
|
||||
new_options.validate();
|
||||
}
|
||||
|
||||
void asset_update_feed_producers_operation::validate() const
|
||||
{
|
||||
FC_ASSERT( fee.amount >= 0 );
|
||||
|
|
@ -201,6 +207,10 @@ void bitasset_options::validate() const
|
|||
FC_ASSERT(maximum_force_settlement_volume <= GRAPHENE_100_PERCENT);
|
||||
}
|
||||
|
||||
void dividend_asset_options::validate() const
|
||||
{
|
||||
}
|
||||
|
||||
void asset_options::validate()const
|
||||
{
|
||||
FC_ASSERT( max_supply > 0 );
|
||||
|
|
|
|||
|
|
@ -188,6 +188,12 @@ namespace graphene { namespace chain {
|
|||
"Maximum transaction expiration time must be greater than a block interval" );
|
||||
FC_ASSERT( maximum_proposal_lifetime - committee_proposal_review_period > block_interval,
|
||||
"Committee proposal review period must be less than the maximum proposal lifetime" );
|
||||
|
||||
FC_ASSERT( rake_fee_percentage >= TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE,
|
||||
"Rake fee percentage must not be less than ${min}", ("min",TOURNAMENT_MINIMAL_RAKE_FEE_PERCENTAGE));
|
||||
FC_ASSERT( rake_fee_percentage <= TOURNAMENT_MAXIMAL_RAKE_FEE_PERCENTAGE,
|
||||
"Rake fee percentage must not be greater than ${max}", ("max", TOURNAMENT_MAXIMAL_RAKE_FEE_PERCENTAGE));
|
||||
|
||||
}
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace graphene { namespace chain {
|
|||
FC_ASSERT(op.options.registration_deadline >= d.head_block_time(), "Registration deadline has already passed");
|
||||
|
||||
// TODO: make this committee-set
|
||||
const fc::time_point_sec maximum_registration_deadline = d.head_block_time() + fc::days(30);
|
||||
const fc::time_point_sec maximum_registration_deadline = d.head_block_time() + d.get_global_properties().parameters.maximum_registration_deadline;
|
||||
FC_ASSERT(op.options.registration_deadline <= maximum_registration_deadline,
|
||||
"Registration deadline must be before ${maximum_registration_deadline}",
|
||||
("maximum_registration_deadline", maximum_registration_deadline));
|
||||
|
|
@ -95,6 +95,7 @@ namespace graphene { namespace chain {
|
|||
"Time to reveal the move must not be greater than ${max}",
|
||||
("max", maximum_time_per_reveal_move));
|
||||
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
|
|
|
|||
|
|
@ -254,10 +254,30 @@ namespace graphene { namespace chain {
|
|||
fc_ilog(fc::logger::get("tournament"),
|
||||
"Tournament ${id} is complete",
|
||||
("id", fsm.tournament_obj->id));
|
||||
|
||||
// Distribute prize money when a tournament ends
|
||||
#ifndef NDEBUG
|
||||
const tournament_details_object& details = fsm.tournament_obj->tournament_details_id(event.db);
|
||||
share_type total_prize = 0;
|
||||
for (const auto& payer_pair : details.payers)
|
||||
{
|
||||
total_prize += payer_pair.second;
|
||||
}
|
||||
assert(total_prize == fsm.tournament_obj->prize_pool);
|
||||
#endif
|
||||
assert(event.match.match_winners.size() == 1);
|
||||
const account_id_type& winner = *event.match.match_winners.begin();
|
||||
uint16_t rake_fee_percentage = event.db.get_global_properties().parameters.rake_fee_percentage;
|
||||
share_type rake_amount = (fc::uint128_t(fsm.tournament_obj->prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT).to_uint64();
|
||||
#ifdef TOURNAMENT_RAKE_FEE_ACCOUNT_ID
|
||||
event.db.adjust_balance(account_id_type(TOURNAMENT_RAKE_FEE_ACCOUNT_ID),
|
||||
asset(rake_amount, fsm.tournament_obj->options.buy_in.asset_id));
|
||||
#endif
|
||||
event.db.adjust_balance(winner, asset(fsm.tournament_obj->prize_pool - rake_amount,
|
||||
fsm.tournament_obj->options.buy_in.asset_id));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
typedef accepting_registrations initial_state;
|
||||
|
||||
typedef tournament_state_machine_ x; // makes transition table cleaner
|
||||
|
|
|
|||
|
|
@ -130,6 +130,8 @@ namespace graphene { namespace db {
|
|||
virtual fc::uint128 hash()const = 0;
|
||||
virtual void add_observer( const shared_ptr<index_observer>& ) = 0;
|
||||
|
||||
virtual void object_from_variant( const fc::variant& var, object& obj )const = 0;
|
||||
virtual void object_default( object& obj )const = 0;
|
||||
};
|
||||
|
||||
class secondary_index
|
||||
|
|
@ -298,6 +300,24 @@ namespace graphene { namespace db {
|
|||
_observers.emplace_back( o );
|
||||
}
|
||||
|
||||
virtual void object_from_variant( const fc::variant& var, object& obj )const override
|
||||
{
|
||||
object_id_type id = obj.id;
|
||||
object_type* result = dynamic_cast<object_type*>( &obj );
|
||||
FC_ASSERT( result != nullptr );
|
||||
fc::from_variant( var, *result );
|
||||
obj.id = id;
|
||||
}
|
||||
|
||||
virtual void object_default( object& obj )const override
|
||||
{
|
||||
object_id_type id = obj.id;
|
||||
object_type* result = dynamic_cast<object_type*>( &obj );
|
||||
FC_ASSERT( result != nullptr );
|
||||
(*result) = object_type();
|
||||
obj.id = id;
|
||||
}
|
||||
|
||||
private:
|
||||
object_id_type _next_id;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3952,12 +3952,20 @@ namespace graphene { namespace net { namespace detail {
|
|||
}
|
||||
|
||||
unsigned handle_message_call_count = 0;
|
||||
for (fc::future<void>& handle_message_call : _handle_message_calls_in_progress)
|
||||
while( true )
|
||||
{
|
||||
auto it = _handle_message_calls_in_progress.begin();
|
||||
if( it == _handle_message_calls_in_progress.end() )
|
||||
break;
|
||||
if( it->ready() || it->error() || it->canceled() )
|
||||
{
|
||||
_handle_message_calls_in_progress.erase( it );
|
||||
continue;
|
||||
}
|
||||
++handle_message_call_count;
|
||||
try
|
||||
{
|
||||
handle_message_call.cancel_and_wait("node_impl::close()");
|
||||
it->cancel_and_wait("node_impl::close()");
|
||||
dlog("handle_message call #${count} task terminated", ("count", handle_message_call_count));
|
||||
}
|
||||
catch ( const fc::canceled_exception& )
|
||||
|
|
@ -3973,7 +3981,6 @@ namespace graphene { namespace net { namespace detail {
|
|||
wlog("Exception thrown while terminating handle_message call #${count} task, ignoring",("count", handle_message_call_count));
|
||||
}
|
||||
}
|
||||
_handle_message_calls_in_progress.clear();
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@ add_subdirectory( account_history )
|
|||
add_subdirectory( market_history )
|
||||
add_subdirectory( delayed_node )
|
||||
add_subdirectory( generate_genesis )
|
||||
add_subdirectory( debug_witness )
|
||||
|
|
|
|||
|
|
@ -119,10 +119,13 @@ void account_history_plugin_impl::update_account_histories( const signed_block&
|
|||
const auto& stats_obj = account_id(db).statistics(db);
|
||||
const auto& ath = db.create<account_transaction_history_object>( [&]( account_transaction_history_object& obj ){
|
||||
obj.operation_id = oho.id;
|
||||
obj.account = account_id;
|
||||
obj.sequence = stats_obj.total_ops+1;
|
||||
obj.next = stats_obj.most_recent_op;
|
||||
});
|
||||
db.modify( stats_obj, [&]( account_statistics_object& obj ){
|
||||
obj.most_recent_op = ath.id;
|
||||
obj.total_ops = ath.sequence;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -148,6 +151,11 @@ void account_history_plugin_impl::update_account_histories( const signed_block&
|
|||
}
|
||||
} // end namespace detail
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
account_history_plugin::account_history_plugin() :
|
||||
my( new detail::account_history_plugin_impl(*this) )
|
||||
{
|
||||
|
|
@ -177,7 +185,7 @@ void account_history_plugin::plugin_initialize(const boost::program_options::var
|
|||
{
|
||||
database().applied_block.connect( [&]( const signed_block& b){ my->update_account_histories(b); } );
|
||||
database().add_index< primary_index< simple_index< operation_history_object > > >();
|
||||
database().add_index< primary_index< simple_index< account_transaction_history_object > > >();
|
||||
database().add_index< primary_index< account_transaction_history_index > >();
|
||||
|
||||
LOAD_VALUE_SET(options, "tracked-accounts", my->_tracked_accounts, graphene::chain::account_id_type);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,15 @@
|
|||
#include <graphene/app/plugin.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
|
||||
#include <graphene/chain/operation_history_object.hpp>
|
||||
|
||||
#include <fc/thread/future.hpp>
|
||||
|
||||
namespace graphene { namespace account_history {
|
||||
using namespace chain;
|
||||
using namespace chain;
|
||||
//using namespace graphene::db;
|
||||
//using boost::multi_index_container;
|
||||
//using namespace boost::multi_index;
|
||||
|
||||
//
|
||||
// Plugins should #define their SPACE_ID's so plugins with
|
||||
|
|
@ -78,3 +83,27 @@ class account_history_plugin : public graphene::app::plugin
|
|||
|
||||
} } //graphene::account_history
|
||||
|
||||
/*struct by_id;
|
||||
struct by_seq;
|
||||
struct by_op;
|
||||
typedef boost::multi_index_container<
|
||||
graphene::chain::account_transaction_history_object,
|
||||
boost::multi_index::indexed_by<
|
||||
boost::multi_index::ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||
boost::multi_index::ordered_unique< tag<by_seq>,
|
||||
composite_key< account_transaction_history_object,
|
||||
member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>,
|
||||
member< account_transaction_history_object, uint32_t, &account_transaction_history_object::sequence>
|
||||
>
|
||||
>,
|
||||
boost::multi_index::ordered_unique< tag<by_op>,
|
||||
composite_key< account_transaction_history_object,
|
||||
member< account_transaction_history_object, account_id_type, &account_transaction_history_object::account>,
|
||||
member< account_transaction_history_object, operation_history_id_type, &account_transaction_history_object::operation_id>
|
||||
>
|
||||
>
|
||||
>
|
||||
> account_transaction_history_multi_index_type;
|
||||
|
||||
typedef graphene::account_history::generic_index<graphene::chain::account_transaction_history_object, account_transaction_history_multi_index_type> account_transaction_history_index;
|
||||
*/
|
||||
|
|
|
|||
18
libraries/plugins/debug_witness/CMakeLists.txt
Normal file
18
libraries/plugins/debug_witness/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
file(GLOB HEADERS "include/graphene/debug_witness/*.hpp")
|
||||
|
||||
add_library( graphene_debug_witness
|
||||
debug_api.cpp
|
||||
debug_witness.cpp
|
||||
)
|
||||
|
||||
target_link_libraries( graphene_debug_witness graphene_chain graphene_app )
|
||||
target_include_directories( graphene_debug_witness
|
||||
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
|
||||
|
||||
install( TARGETS
|
||||
graphene_debug_witness
|
||||
|
||||
RUNTIME DESTINATION bin
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
157
libraries/plugins/debug_witness/debug_api.cpp
Normal file
157
libraries/plugins/debug_witness/debug_api.cpp
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
|
||||
#include <fc/filesystem.hpp>
|
||||
#include <fc/optional.hpp>
|
||||
#include <fc/variant_object.hpp>
|
||||
|
||||
#include <graphene/app/application.hpp>
|
||||
|
||||
#include <graphene/chain/block_database.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
|
||||
#include <graphene/debug_witness/debug_api.hpp>
|
||||
#include <graphene/debug_witness/debug_witness.hpp>
|
||||
|
||||
namespace graphene { namespace debug_witness {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class debug_api_impl
|
||||
{
|
||||
public:
|
||||
debug_api_impl( graphene::app::application& _app );
|
||||
|
||||
void debug_push_blocks( const std::string& src_filename, uint32_t count );
|
||||
void debug_generate_blocks( const std::string& debug_key, uint32_t count );
|
||||
void debug_update_object( const fc::variant_object& update );
|
||||
//void debug_save_db( std::string db_path );
|
||||
void debug_stream_json_objects( const std::string& filename );
|
||||
void debug_stream_json_objects_flush();
|
||||
std::shared_ptr< graphene::debug_witness_plugin::debug_witness_plugin > get_plugin();
|
||||
|
||||
graphene::app::application& app;
|
||||
};
|
||||
|
||||
debug_api_impl::debug_api_impl( graphene::app::application& _app ) : app( _app )
|
||||
{}
|
||||
|
||||
|
||||
void debug_api_impl::debug_push_blocks( const std::string& src_filename, uint32_t count )
|
||||
{
|
||||
if( count == 0 )
|
||||
return;
|
||||
|
||||
std::shared_ptr< graphene::chain::database > db = app.chain_database();
|
||||
fc::path src_path = fc::path( src_filename );
|
||||
if( fc::is_directory( src_path ) )
|
||||
{
|
||||
ilog( "Loading ${n} from block_database ${fn}", ("n", count)("fn", src_filename) );
|
||||
graphene::chain::block_database bdb;
|
||||
bdb.open( src_path );
|
||||
uint32_t first_block = db->head_block_num()+1;
|
||||
for( uint32_t i=0; i<count; i++ )
|
||||
{
|
||||
fc::optional< graphene::chain::signed_block > block = bdb.fetch_by_number( first_block+i );
|
||||
if( !block.valid() )
|
||||
{
|
||||
wlog( "Block database ${fn} only contained ${i} of ${n} requested blocks", ("i", i)("n", count)("fn", src_filename) );
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
db->push_block( *block );
|
||||
}
|
||||
catch( const fc::exception& e )
|
||||
{
|
||||
elog( "Got exception pushing block ${bn} : ${bid} (${i} of ${n})", ("bn", block->block_num())("bid", block->id())("i", i)("n", count) );
|
||||
elog( "Exception backtrace: ${bt}", ("bt", e.to_detail_string()) );
|
||||
}
|
||||
}
|
||||
ilog( "Completed loading block_database successfully" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void debug_api_impl::debug_generate_blocks( const std::string& debug_key, uint32_t count )
|
||||
{
|
||||
if( count == 0 )
|
||||
return;
|
||||
|
||||
fc::optional<fc::ecc::private_key> debug_private_key = graphene::utilities::wif_to_key( debug_key );
|
||||
FC_ASSERT( debug_private_key.valid() );
|
||||
graphene::chain::public_key_type debug_public_key = debug_private_key->get_public_key();
|
||||
|
||||
std::shared_ptr< graphene::chain::database > db = app.chain_database();
|
||||
for( uint32_t i=0; i<count; i++ )
|
||||
{
|
||||
graphene::chain::witness_id_type scheduled_witness = db->get_scheduled_witness( 1 );
|
||||
fc::time_point_sec scheduled_time = db->get_slot_time( 1 );
|
||||
graphene::chain::public_key_type scheduled_key = scheduled_witness( *db ).signing_key;
|
||||
if( scheduled_key != debug_public_key )
|
||||
{
|
||||
ilog( "Modified key for witness ${w}", ("w", scheduled_witness) );
|
||||
fc::mutable_variant_object update;
|
||||
update("_action", "update")("id", scheduled_witness)("signing_key", debug_public_key);
|
||||
db->debug_update( update );
|
||||
}
|
||||
db->generate_block( scheduled_time, scheduled_witness, *debug_private_key, graphene::chain::database::skip_nothing );
|
||||
}
|
||||
}
|
||||
|
||||
void debug_api_impl::debug_update_object( const fc::variant_object& update )
|
||||
{
|
||||
std::shared_ptr< graphene::chain::database > db = app.chain_database();
|
||||
db->debug_update( update );
|
||||
}
|
||||
|
||||
std::shared_ptr< graphene::debug_witness_plugin::debug_witness_plugin > debug_api_impl::get_plugin()
|
||||
{
|
||||
return app.get_plugin< graphene::debug_witness_plugin::debug_witness_plugin >( "debug_witness" );
|
||||
}
|
||||
|
||||
void debug_api_impl::debug_stream_json_objects( const std::string& filename )
|
||||
{
|
||||
get_plugin()->set_json_object_stream( filename );
|
||||
}
|
||||
|
||||
void debug_api_impl::debug_stream_json_objects_flush()
|
||||
{
|
||||
get_plugin()->flush_json_object_stream();
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
debug_api::debug_api( graphene::app::application& app )
|
||||
{
|
||||
my = std::make_shared< detail::debug_api_impl >(app);
|
||||
}
|
||||
|
||||
void debug_api::debug_push_blocks( std::string source_filename, uint32_t count )
|
||||
{
|
||||
my->debug_push_blocks( source_filename, count );
|
||||
}
|
||||
|
||||
void debug_api::debug_generate_blocks( std::string debug_key, uint32_t count )
|
||||
{
|
||||
my->debug_generate_blocks( debug_key, count );
|
||||
}
|
||||
|
||||
void debug_api::debug_update_object( fc::variant_object update )
|
||||
{
|
||||
my->debug_update_object( update );
|
||||
}
|
||||
|
||||
void debug_api::debug_stream_json_objects( std::string filename )
|
||||
{
|
||||
my->debug_stream_json_objects( filename );
|
||||
}
|
||||
|
||||
void debug_api::debug_stream_json_objects_flush()
|
||||
{
|
||||
my->debug_stream_json_objects_flush();
|
||||
}
|
||||
|
||||
|
||||
} } // graphene::debug_witness
|
||||
173
libraries/plugins/debug_witness/debug_witness.cpp
Normal file
173
libraries/plugins/debug_witness/debug_witness.cpp
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include <graphene/debug_witness/debug_witness.hpp>
|
||||
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
#include <graphene/time/time.hpp>
|
||||
|
||||
#include <graphene/utilities/key_conversion.hpp>
|
||||
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
#include <fc/thread/thread.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace graphene::debug_witness_plugin;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
namespace bpo = boost::program_options;
|
||||
|
||||
debug_witness_plugin::~debug_witness_plugin() {}
|
||||
|
||||
void debug_witness_plugin::plugin_set_program_options(
|
||||
boost::program_options::options_description& command_line_options,
|
||||
boost::program_options::options_description& config_file_options)
|
||||
{
|
||||
auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan")));
|
||||
command_line_options.add_options()
|
||||
("private-key", bpo::value<vector<string>>()->composing()->multitoken()->
|
||||
DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))),
|
||||
"Tuple of [PublicKey, WIF private key] (may specify multiple times)");
|
||||
config_file_options.add(command_line_options);
|
||||
}
|
||||
|
||||
std::string debug_witness_plugin::plugin_name()const
|
||||
{
|
||||
return "debug_witness";
|
||||
}
|
||||
|
||||
void debug_witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)
|
||||
{ try {
|
||||
ilog("debug_witness plugin: plugin_initialize() begin");
|
||||
_options = &options;
|
||||
|
||||
if( options.count("private-key") )
|
||||
{
|
||||
const std::vector<std::string> key_id_to_wif_pair_strings = options["private-key"].as<std::vector<std::string>>();
|
||||
for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings)
|
||||
{
|
||||
auto key_id_to_wif_pair = graphene::app::dejsonify<std::pair<chain::public_key_type, std::string> >(key_id_to_wif_pair_string);
|
||||
idump((key_id_to_wif_pair));
|
||||
fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second);
|
||||
if (!private_key)
|
||||
{
|
||||
// the key isn't in WIF format; see if they are still passing the old native private key format. This is
|
||||
// just here to ease the transition, can be removed soon
|
||||
try
|
||||
{
|
||||
private_key = fc::variant(key_id_to_wif_pair.second).as<fc::ecc::private_key>();
|
||||
}
|
||||
catch (const fc::exception&)
|
||||
{
|
||||
FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second));
|
||||
}
|
||||
}
|
||||
_private_keys[key_id_to_wif_pair.first] = *private_key;
|
||||
}
|
||||
}
|
||||
ilog("debug_witness plugin: plugin_initialize() end");
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
void debug_witness_plugin::plugin_startup()
|
||||
{
|
||||
ilog("debug_witness_plugin::plugin_startup() begin");
|
||||
chain::database& db = database();
|
||||
|
||||
// connect needed signals
|
||||
|
||||
_applied_block_conn = db.applied_block.connect([this](const graphene::chain::signed_block& b){ on_applied_block(b); });
|
||||
_changed_objects_conn = db.changed_objects.connect([this](const std::vector<graphene::db::object_id_type>& ids){ on_changed_objects(ids); });
|
||||
_removed_objects_conn = db.removed_objects.connect([this](const std::vector<const graphene::db::object*>& objs){ on_removed_objects(objs); });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void debug_witness_plugin::on_changed_objects( const std::vector<graphene::db::object_id_type>& ids )
|
||||
{
|
||||
if( _json_object_stream && (ids.size() > 0) )
|
||||
{
|
||||
const chain::database& db = database();
|
||||
for( const graphene::db::object_id_type& oid : ids )
|
||||
{
|
||||
const graphene::db::object* obj = db.find_object( oid );
|
||||
if( obj == nullptr )
|
||||
{
|
||||
(*_json_object_stream) << "{\"id\":" << fc::json::to_string( oid ) << "}\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
(*_json_object_stream) << fc::json::to_string( obj->to_variant() ) << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void debug_witness_plugin::on_removed_objects( const std::vector<const graphene::db::object*> objs )
|
||||
{
|
||||
/*
|
||||
if( _json_object_stream )
|
||||
{
|
||||
for( const graphene::db::object* obj : objs )
|
||||
{
|
||||
(*_json_object_stream) << "{\"id\":" << fc::json::to_string( obj->id ) << "}\n";
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void debug_witness_plugin::on_applied_block( const graphene::chain::signed_block& b )
|
||||
{
|
||||
if( _json_object_stream )
|
||||
{
|
||||
(*_json_object_stream) << "{\"bn\":" << fc::to_string( b.block_num() ) << "}\n";
|
||||
}
|
||||
}
|
||||
|
||||
void debug_witness_plugin::set_json_object_stream( const std::string& filename )
|
||||
{
|
||||
if( _json_object_stream )
|
||||
{
|
||||
_json_object_stream->close();
|
||||
_json_object_stream.reset();
|
||||
}
|
||||
_json_object_stream = std::make_shared< std::ofstream >( filename );
|
||||
}
|
||||
|
||||
void debug_witness_plugin::flush_json_object_stream()
|
||||
{
|
||||
if( _json_object_stream )
|
||||
_json_object_stream->flush();
|
||||
}
|
||||
|
||||
void debug_witness_plugin::plugin_shutdown()
|
||||
{
|
||||
if( _json_object_stream )
|
||||
{
|
||||
_json_object_stream->close();
|
||||
_json_object_stream.reset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <fc/api.hpp>
|
||||
#include <fc/variant_object.hpp>
|
||||
|
||||
namespace graphene { namespace app {
|
||||
class application;
|
||||
} }
|
||||
|
||||
namespace graphene { namespace debug_witness {
|
||||
|
||||
namespace detail {
|
||||
class debug_api_impl;
|
||||
}
|
||||
|
||||
class debug_api
|
||||
{
|
||||
public:
|
||||
debug_api( graphene::app::application& app );
|
||||
|
||||
/**
|
||||
* Push blocks from existing database.
|
||||
*/
|
||||
void debug_push_blocks( std::string src_filename, uint32_t count );
|
||||
|
||||
/**
|
||||
* Generate blocks locally.
|
||||
*/
|
||||
void debug_generate_blocks( std::string debug_key, uint32_t count );
|
||||
|
||||
/**
|
||||
* Directly manipulate database objects (will undo and re-apply last block with new changes post-applied).
|
||||
*/
|
||||
void debug_update_object( fc::variant_object update );
|
||||
|
||||
/**
|
||||
* Start a node with given initial path.
|
||||
*/
|
||||
// not implemented
|
||||
//void start_node( std::string name, std::string initial_db_path );
|
||||
|
||||
/**
|
||||
* Save the database to disk.
|
||||
*/
|
||||
// not implemented
|
||||
//void save_db( std::string db_path );
|
||||
|
||||
/**
|
||||
* Stream objects to file. (Hint: Create with mkfifo and pipe it to a script)
|
||||
*/
|
||||
|
||||
void debug_stream_json_objects( std::string filename );
|
||||
|
||||
/**
|
||||
* Flush streaming file.
|
||||
*/
|
||||
void debug_stream_json_objects_flush();
|
||||
|
||||
std::shared_ptr< detail::debug_api_impl > my;
|
||||
};
|
||||
|
||||
} }
|
||||
|
||||
FC_API(graphene::debug_witness::debug_api,
|
||||
(debug_push_blocks)
|
||||
(debug_generate_blocks)
|
||||
(debug_update_object)
|
||||
(debug_stream_json_objects)
|
||||
(debug_stream_json_objects_flush)
|
||||
)
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <graphene/app/plugin.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
|
||||
#include <fc/thread/future.hpp>
|
||||
|
||||
namespace graphene { namespace debug_witness_plugin {
|
||||
|
||||
class debug_witness_plugin : public graphene::app::plugin {
|
||||
public:
|
||||
~debug_witness_plugin();
|
||||
|
||||
std::string plugin_name()const override;
|
||||
|
||||
virtual void plugin_set_program_options(
|
||||
boost::program_options::options_description &command_line_options,
|
||||
boost::program_options::options_description &config_file_options
|
||||
) override;
|
||||
|
||||
virtual void plugin_initialize( const boost::program_options::variables_map& options ) override;
|
||||
virtual void plugin_startup() override;
|
||||
virtual void plugin_shutdown() override;
|
||||
|
||||
void set_json_object_stream( const std::string& filename );
|
||||
void flush_json_object_stream();
|
||||
|
||||
private:
|
||||
|
||||
void on_changed_objects( const std::vector<graphene::db::object_id_type>& ids );
|
||||
void on_removed_objects( const std::vector<const graphene::db::object*> objs );
|
||||
void on_applied_block( const graphene::chain::signed_block& b );
|
||||
|
||||
boost::program_options::variables_map _options;
|
||||
|
||||
std::map<chain::public_key_type, fc::ecc::private_key> _private_keys;
|
||||
|
||||
std::shared_ptr< std::ofstream > _json_object_stream;
|
||||
boost::signals2::scoped_connection _applied_block_conn;
|
||||
boost::signals2::scoped_connection _changed_objects_conn;
|
||||
boost::signals2::scoped_connection _removed_objects_conn;
|
||||
};
|
||||
|
||||
} } //graphene::debug_witness_plugin
|
||||
|
|
@ -853,6 +853,51 @@ class wallet_api
|
|||
uint32_t timeout_sec = 0,
|
||||
bool fill_or_kill = false,
|
||||
bool broadcast = false);
|
||||
|
||||
/** Place a limit order attempting to sell one asset for another.
|
||||
*
|
||||
* This API call abstracts away some of the details of the sell_asset call to be more
|
||||
* user friendly. All orders placed with sell never timeout and will not be killed if they
|
||||
* cannot be filled immediately. If you wish for one of these parameters to be different,
|
||||
* then sell_asset should be used instead.
|
||||
*
|
||||
* @param seller_account the account providing the asset being sold, and which will
|
||||
* receive the processed of the sale.
|
||||
* @param base The name or id of the asset to sell.
|
||||
* @param quote The name or id of the asset to recieve.
|
||||
* @param rate The rate in base:quote at which you want to sell.
|
||||
* @param amount The amount of base you want to sell.
|
||||
* @param broadcast true to broadcast the transaction on the network.
|
||||
* @returns The signed transaction selling the funds.
|
||||
*/
|
||||
signed_transaction sell( string seller_account,
|
||||
string base,
|
||||
string quote,
|
||||
double rate,
|
||||
double amount,
|
||||
bool broadcast );
|
||||
|
||||
/** Place a limit order attempting to buy one asset with another.
|
||||
*
|
||||
* This API call abstracts away some of the details of the sell_asset call to be more
|
||||
* user friendly. All orders placed with buy never timeout and will not be killed if they
|
||||
* cannot be filled immediately. If you wish for one of these parameters to be different,
|
||||
* then sell_asset should be used instead.
|
||||
*
|
||||
* @param buyer_account The account buying the asset for another asset.
|
||||
* @param base The name or id of the asset to buy.
|
||||
* @param quote The name or id of the assest being offered as payment.
|
||||
* @param rate The rate in base:quote at which you want to buy.
|
||||
* @param amount the amount of base you want to buy.
|
||||
* @param broadcast true to broadcast the transaction on the network.
|
||||
* @param The signed transaction selling the funds.
|
||||
*/
|
||||
signed_transaction buy( string buyer_account,
|
||||
string base,
|
||||
string quote,
|
||||
double rate,
|
||||
double amount,
|
||||
bool broadcast );
|
||||
|
||||
/** Borrow an asset or update the debt/collateral ratio for the loan.
|
||||
*
|
||||
|
|
@ -960,6 +1005,21 @@ class wallet_api
|
|||
bitasset_options new_options,
|
||||
bool broadcast = false);
|
||||
|
||||
|
||||
/** Update the given asset's dividend asset options.
|
||||
*
|
||||
* If the asset is not already a dividend-paying asset, it will be converted into one.
|
||||
*
|
||||
* @param symbol the name or id of the asset to update, which must be a market-issued asset
|
||||
* @param new_options the new dividend_asset_options object, which will entirely replace the existing
|
||||
* options.
|
||||
* @param broadcast true to broadcast the transaction on the network
|
||||
* @returns the signed transaction updating the asset
|
||||
*/
|
||||
signed_transaction update_dividend_asset(string symbol,
|
||||
dividend_asset_options new_options,
|
||||
bool broadcast = false);
|
||||
|
||||
/** Update the set of feed-producing accounts for a BitAsset.
|
||||
*
|
||||
* BitAssets have price feeds selected by taking the median values of recommendations from a set of feed producers.
|
||||
|
|
@ -1405,6 +1465,8 @@ class wallet_api
|
|||
const approval_delta& delta,
|
||||
bool broadcast /* = false */
|
||||
);
|
||||
|
||||
order_book get_order_book( const string& base, const string& quote, unsigned limit = 50);
|
||||
|
||||
/** Creates a new tournament
|
||||
* @param creator the accout that is paying the fee to create the tournament
|
||||
|
|
@ -1444,6 +1506,11 @@ class wallet_api
|
|||
|
||||
void dbg_make_uia(string creator, string symbol);
|
||||
void dbg_make_mia(string creator, string symbol);
|
||||
void dbg_push_blocks( std::string src_filename, uint32_t count );
|
||||
void dbg_generate_blocks( std::string debug_wif_key, uint32_t count );
|
||||
void dbg_stream_json_objects( const std::string& filename );
|
||||
void dbg_update_object( fc::variant_object update );
|
||||
|
||||
void flood_network(string prefix, uint32_t number_of_transactions);
|
||||
|
||||
void network_add_nodes( const vector<string>& nodes );
|
||||
|
|
@ -1558,6 +1625,8 @@ FC_API( graphene::wallet::wallet_api,
|
|||
(upgrade_account)
|
||||
(create_account_with_brain_key)
|
||||
(sell_asset)
|
||||
(sell)
|
||||
(buy)
|
||||
(borrow_asset)
|
||||
(cancel_order)
|
||||
(transfer)
|
||||
|
|
@ -1566,6 +1635,7 @@ FC_API( graphene::wallet::wallet_api,
|
|||
(create_asset)
|
||||
(update_asset)
|
||||
(update_bitasset)
|
||||
(update_dividend_asset)
|
||||
(update_asset_feed_producers)
|
||||
(publish_asset_feed)
|
||||
(issue_asset)
|
||||
|
|
@ -1615,6 +1685,10 @@ FC_API( graphene::wallet::wallet_api,
|
|||
(approve_proposal)
|
||||
(dbg_make_uia)
|
||||
(dbg_make_mia)
|
||||
(dbg_push_blocks)
|
||||
(dbg_generate_blocks)
|
||||
(dbg_stream_json_objects)
|
||||
(dbg_update_object)
|
||||
(flood_network)
|
||||
(network_add_nodes)
|
||||
(network_get_connected_peers)
|
||||
|
|
@ -1635,4 +1709,5 @@ FC_API( graphene::wallet::wallet_api,
|
|||
(rps_throw)
|
||||
(get_upcoming_tournaments)
|
||||
(get_tournament)
|
||||
(get_order_book)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/multiprecision/integer.hpp>
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
|
@ -78,6 +79,7 @@
|
|||
#include <graphene/wallet/wallet.hpp>
|
||||
#include <graphene/wallet/api_documentation.hpp>
|
||||
#include <graphene/wallet/reflect_util.hpp>
|
||||
#include <graphene/debug_witness/debug_api.hpp>
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
|
||||
#ifndef WIN32
|
||||
|
|
@ -131,6 +133,7 @@ public:
|
|||
std::string operator()(const account_create_operation& op)const;
|
||||
std::string operator()(const account_update_operation& op)const;
|
||||
std::string operator()(const asset_create_operation& op)const;
|
||||
std::string operator()(const asset_dividend_distribution_operation& op)const;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
|
|
@ -289,7 +292,7 @@ private:
|
|||
auto iter = _wallet.pending_witness_registrations.find(witness_name);
|
||||
FC_ASSERT(iter != _wallet.pending_witness_registrations.end());
|
||||
std::string wif_key = iter->second;
|
||||
|
||||
|
||||
// get the list key id this key is registered with in the chain
|
||||
fc::optional<fc::ecc::private_key> witness_private_key = wif_to_key(wif_key);
|
||||
FC_ASSERT(witness_private_key);
|
||||
|
|
@ -325,12 +328,12 @@ private:
|
|||
if( optional_account )
|
||||
claim_registered_account(*optional_account);
|
||||
}
|
||||
|
||||
|
||||
if (!_wallet.pending_witness_registrations.empty())
|
||||
{
|
||||
// make a vector of the owner accounts for witnesses pending registration
|
||||
std::vector<string> pending_witness_names = boost::copy_range<std::vector<string> >(boost::adaptors::keys(_wallet.pending_witness_registrations));
|
||||
|
||||
|
||||
// look up the owners on the blockchain
|
||||
std::vector<fc::optional<graphene::chain::account_object>> owner_account_objects = _remote_db->lookup_account_names(pending_witness_names);
|
||||
|
||||
|
|
@ -480,9 +483,9 @@ private:
|
|||
|
||||
// if the user executes the same command twice in quick succession,
|
||||
// we might generate the same transaction id, and cause the second
|
||||
// transaction to be rejected. This can be avoided by altering the
|
||||
// transaction to be rejected. This can be avoided by altering the
|
||||
// second transaction slightly (bumping up the expiration time by
|
||||
// a second). Keep track of recent transaction ids we've generated
|
||||
// a second). Keep track of recent transaction ids we've generated
|
||||
// so we can know if we need to do this
|
||||
struct recently_generated_transaction_record
|
||||
{
|
||||
|
|
@ -492,7 +495,7 @@ private:
|
|||
struct timestamp_index{};
|
||||
typedef boost::multi_index_container<recently_generated_transaction_record,
|
||||
boost::multi_index::indexed_by<boost::multi_index::hashed_unique<boost::multi_index::member<recently_generated_transaction_record,
|
||||
graphene::chain::transaction_id_type,
|
||||
graphene::chain::transaction_id_type,
|
||||
&recently_generated_transaction_record::transaction_id>,
|
||||
std::hash<graphene::chain::transaction_id_type> >,
|
||||
boost::multi_index::ordered_non_unique<boost::multi_index::tag<timestamp_index>,
|
||||
|
|
@ -814,7 +817,7 @@ public:
|
|||
if (!optional_private_key)
|
||||
FC_THROW("Invalid private key");
|
||||
graphene::chain::public_key_type wif_pub_key = optional_private_key->get_public_key();
|
||||
|
||||
|
||||
account_object account = get_account( account_name_or_id );
|
||||
|
||||
// make a list of all current public keys for the named account
|
||||
|
|
@ -1313,7 +1316,7 @@ public:
|
|||
{ try {
|
||||
int active_key_index = find_first_unused_derived_key_index(owner_privkey);
|
||||
fc::ecc::private_key active_privkey = derive_private_key( key_to_wif(owner_privkey), active_key_index);
|
||||
|
||||
|
||||
int memo_key_index = find_first_unused_derived_key_index(active_privkey);
|
||||
fc::ecc::private_key memo_privkey = derive_private_key( key_to_wif(active_privkey), memo_key_index);
|
||||
|
||||
|
|
@ -1469,6 +1472,27 @@ public:
|
|||
return sign_transaction( tx, broadcast );
|
||||
} FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) }
|
||||
|
||||
signed_transaction update_dividend_asset(string symbol,
|
||||
dividend_asset_options new_options,
|
||||
bool broadcast /* = false */)
|
||||
{ try {
|
||||
optional<asset_object> asset_to_update = find_asset(symbol);
|
||||
if (!asset_to_update)
|
||||
FC_THROW("No asset with that symbol exists!");
|
||||
|
||||
asset_update_dividend_operation update_op;
|
||||
update_op.issuer = asset_to_update->issuer;
|
||||
update_op.asset_to_update = asset_to_update->id;
|
||||
update_op.new_options = new_options;
|
||||
|
||||
signed_transaction tx;
|
||||
tx.operations.push_back( update_op );
|
||||
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
|
||||
tx.validate();
|
||||
|
||||
return sign_transaction( tx, broadcast );
|
||||
} FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) }
|
||||
|
||||
signed_transaction update_asset_feed_producers(string symbol,
|
||||
flat_set<string> new_feed_producers,
|
||||
bool broadcast /* = false */)
|
||||
|
|
@ -1621,7 +1645,7 @@ public:
|
|||
return sign_transaction( tx, broadcast );
|
||||
} FC_CAPTURE_AND_RETHROW( (authorizing_account)(account_to_list)(new_listing_status)(broadcast) ) }
|
||||
|
||||
signed_transaction create_committee_member(string owner_account, string url,
|
||||
signed_transaction create_committee_member(string owner_account, string url,
|
||||
bool broadcast /* = false */)
|
||||
{ try {
|
||||
|
||||
|
|
@ -1641,7 +1665,7 @@ public:
|
|||
|
||||
witness_object get_witness(string owner_account)
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
fc::optional<witness_id_type> witness_id = maybe_id<witness_id_type>(owner_account);
|
||||
if (witness_id)
|
||||
|
|
@ -1676,7 +1700,7 @@ public:
|
|||
|
||||
committee_member_object get_committee_member(string owner_account)
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
fc::optional<committee_member_id_type> committee_member_id = maybe_id<committee_member_id_type>(owner_account);
|
||||
if (committee_member_id)
|
||||
|
|
@ -1735,7 +1759,7 @@ public:
|
|||
tx.operations.push_back( witness_create_op );
|
||||
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
|
||||
tx.validate();
|
||||
|
||||
|
||||
_wallet.pending_witness_registrations[owner_account] = key_to_wif(witness_private_key);
|
||||
|
||||
return sign_transaction( tx, broadcast );
|
||||
|
|
@ -2049,7 +2073,7 @@ public:
|
|||
|
||||
if (account_object_to_modify.options.num_witness == desired_number_of_witnesses &&
|
||||
account_object_to_modify.options.num_committee == desired_number_of_committee_members)
|
||||
FC_THROW("Account ${account} is already voting for ${witnesses} witnesses and ${committee_members} committee_members",
|
||||
FC_THROW("Account ${account} is already voting for ${witnesses} witnesses and ${committee_members} committee_members",
|
||||
("account", account_to_modify)("witnesses", desired_number_of_witnesses)("committee_members",desired_number_of_witnesses));
|
||||
account_object_to_modify.options.num_witness = desired_number_of_witnesses;
|
||||
account_object_to_modify.options.num_committee = desired_number_of_committee_members;
|
||||
|
|
@ -2436,7 +2460,7 @@ public:
|
|||
for( auto& r : records )
|
||||
{
|
||||
asset_object as = get_asset( r.amount.asset_id );
|
||||
ss << fc::get_approximate_relative_time_string( r.date )
|
||||
ss << fc::get_approximate_relative_time_string( r.date )
|
||||
<< " " << as.amount_to_pretty_string( r.amount ) << " " << r.from_label << " => " << r.to_label << " " << r.memo <<"\n";
|
||||
}
|
||||
return ss.str();
|
||||
|
|
@ -2573,6 +2597,91 @@ public:
|
|||
ss << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
};
|
||||
m["get_order_book"] = [this](variant result, const fc::variants& a)
|
||||
{
|
||||
auto orders = result.as<order_book>();
|
||||
auto bids = orders.bids;
|
||||
auto asks = orders.asks;
|
||||
std::stringstream ss;
|
||||
std::stringstream sum_stream;
|
||||
sum_stream << "Sum(" << orders.base << ')';
|
||||
double bid_sum = 0;
|
||||
double ask_sum = 0;
|
||||
const int spacing = 20;
|
||||
|
||||
auto prettify_num = [&]( double n )
|
||||
{
|
||||
//ss << n;
|
||||
if (abs( round( n ) - n ) < 0.00000000001 )
|
||||
{
|
||||
//ss << setiosflags( !ios::fixed ) << (int) n; // doesn't compile on Linux with gcc
|
||||
ss << (int) n;
|
||||
}
|
||||
else if (n - floor(n) < 0.000001)
|
||||
{
|
||||
ss << setiosflags( ios::fixed ) << setprecision(10) << n;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << setiosflags( ios::fixed ) << setprecision(6) << n;
|
||||
}
|
||||
};
|
||||
|
||||
ss << setprecision( 8 ) << setiosflags( ios::fixed ) << setiosflags( ios::left );
|
||||
|
||||
ss << ' ' << setw( (spacing * 4) + 6 ) << "BUY ORDERS" << "SELL ORDERS\n"
|
||||
<< ' ' << setw( spacing + 1 ) << "Price" << setw( spacing ) << orders.quote << ' ' << setw( spacing )
|
||||
<< orders.base << ' ' << setw( spacing ) << sum_stream.str()
|
||||
<< " " << setw( spacing + 1 ) << "Price" << setw( spacing ) << orders.quote << ' ' << setw( spacing )
|
||||
<< orders.base << ' ' << setw( spacing ) << sum_stream.str()
|
||||
<< "\n====================================================================================="
|
||||
<< "|=====================================================================================\n";
|
||||
|
||||
for (unsigned i = 0; i < bids.size() || i < asks.size() ; i++)
|
||||
{
|
||||
if ( i < bids.size() )
|
||||
{
|
||||
bid_sum += bids[i].base;
|
||||
ss << ' ' << setw( spacing );
|
||||
prettify_num( bids[i].price );
|
||||
ss << ' ' << setw( spacing );
|
||||
prettify_num( bids[i].quote );
|
||||
ss << ' ' << setw( spacing );
|
||||
prettify_num( bids[i].base );
|
||||
ss << ' ' << setw( spacing );
|
||||
prettify_num( bid_sum );
|
||||
ss << ' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << setw( (spacing * 4) + 5 ) << ' ';
|
||||
}
|
||||
|
||||
ss << '|';
|
||||
|
||||
if ( i < asks.size() )
|
||||
{
|
||||
ask_sum += asks[i].base;
|
||||
ss << ' ' << setw( spacing );
|
||||
prettify_num( asks[i].price );
|
||||
ss << ' ' << setw( spacing );
|
||||
prettify_num( asks[i].quote );
|
||||
ss << ' ' << setw( spacing );
|
||||
prettify_num( asks[i].base );
|
||||
ss << ' ' << setw( spacing );
|
||||
prettify_num( ask_sum );
|
||||
}
|
||||
|
||||
ss << '\n';
|
||||
}
|
||||
|
||||
ss << endl
|
||||
<< "Buy Total: " << bid_sum << ' ' << orders.base << endl
|
||||
<< "Sell Total: " << ask_sum << ' ' << orders.base << endl;
|
||||
|
||||
return ss.str();
|
||||
};
|
||||
|
||||
|
|
@ -2747,6 +2856,34 @@ public:
|
|||
create_asset(get_account(creator).name, symbol, 2, opts, bopts, true);
|
||||
}
|
||||
|
||||
void dbg_push_blocks( const std::string& src_filename, uint32_t count )
|
||||
{
|
||||
use_debug_api();
|
||||
(*_remote_debug)->debug_push_blocks( src_filename, count );
|
||||
(*_remote_debug)->debug_stream_json_objects_flush();
|
||||
}
|
||||
|
||||
void dbg_generate_blocks( const std::string& debug_wif_key, uint32_t count )
|
||||
{
|
||||
use_debug_api();
|
||||
(*_remote_debug)->debug_generate_blocks( debug_wif_key, count );
|
||||
(*_remote_debug)->debug_stream_json_objects_flush();
|
||||
}
|
||||
|
||||
void dbg_stream_json_objects( const std::string& filename )
|
||||
{
|
||||
use_debug_api();
|
||||
(*_remote_debug)->debug_stream_json_objects( filename );
|
||||
(*_remote_debug)->debug_stream_json_objects_flush();
|
||||
}
|
||||
|
||||
void dbg_update_object( const fc::variant_object& update )
|
||||
{
|
||||
use_debug_api();
|
||||
(*_remote_debug)->debug_update_object( update );
|
||||
(*_remote_debug)->debug_stream_json_objects_flush();
|
||||
}
|
||||
|
||||
void use_network_node_api()
|
||||
{
|
||||
if( _remote_net_node )
|
||||
|
|
@ -2765,6 +2902,26 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void use_debug_api()
|
||||
{
|
||||
if( _remote_debug )
|
||||
return;
|
||||
try
|
||||
{
|
||||
_remote_debug = _remote_api->debug();
|
||||
}
|
||||
catch( const fc::exception& e )
|
||||
{
|
||||
std::cerr << "\nCouldn't get debug node API. You probably are not configured\n"
|
||||
"to access the debug API on the node you are connecting to.\n"
|
||||
"\n"
|
||||
"To fix this problem:\n"
|
||||
"- Please ensure you are running debug_node, not witness_node.\n"
|
||||
"- Please follow the instructions in README.md to set up an apiaccess file.\n"
|
||||
"\n";
|
||||
}
|
||||
}
|
||||
|
||||
void network_add_nodes( const vector<string>& nodes )
|
||||
{
|
||||
use_network_node_api();
|
||||
|
|
@ -2801,7 +2958,7 @@ public:
|
|||
dbg_make_uia(master.name, "SHILL");
|
||||
} catch(...) {/* Ignore; the asset probably already exists.*/}
|
||||
|
||||
fc::time_point start = fc::time_point::now();
|
||||
fc::time_point start = fc::time_point::now();
|
||||
for( int i = 0; i < number_of_accounts; ++i )
|
||||
{
|
||||
std::ostringstream brain_key;
|
||||
|
|
@ -2858,6 +3015,7 @@ public:
|
|||
fc::api<network_broadcast_api> _remote_net_broadcast;
|
||||
fc::api<history_api> _remote_hist;
|
||||
optional< fc::api<network_node_api> > _remote_net_node;
|
||||
optional< fc::api<graphene::debug_witness::debug_api> > _remote_debug;
|
||||
|
||||
flat_map<string, operation> _prototype_ops;
|
||||
|
||||
|
|
@ -2901,7 +3059,7 @@ std::string operation_printer::operator()(const T& op)const
|
|||
//op.get_balance_delta( acc, result );
|
||||
auto a = wallet.get_asset( op.fee.asset_id );
|
||||
auto payer = wallet.get_account( op.fee_payer() );
|
||||
|
||||
|
||||
string op_name = fc::get_typename<T>::name();
|
||||
if( op_name.find_last_of(':') != string::npos )
|
||||
op_name.erase(0, op_name.find_last_of(':')+1);
|
||||
|
|
@ -2911,9 +3069,7 @@ std::string operation_printer::operator()(const T& op)const
|
|||
operation_result_printer rprinter(wallet);
|
||||
std::string str_result = result.visit(rprinter);
|
||||
if( str_result != "" )
|
||||
{
|
||||
out << " result: " << str_result;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
std::string operation_printer::operator()(const transfer_from_blind_operation& op)const
|
||||
|
|
@ -2921,7 +3077,7 @@ std::string operation_printer::operator()(const transfer_from_blind_operation& o
|
|||
auto a = wallet.get_asset( op.fee.asset_id );
|
||||
auto receiver = wallet.get_account( op.to );
|
||||
|
||||
out << receiver.name
|
||||
out << receiver.name
|
||||
<< " received " << a.amount_to_pretty_string( op.amount ) << " from blinded balance";
|
||||
return "";
|
||||
}
|
||||
|
|
@ -2931,7 +3087,7 @@ std::string operation_printer::operator()(const transfer_to_blind_operation& op)
|
|||
auto a = wallet.get_asset( op.amount.asset_id );
|
||||
auto sender = wallet.get_account( op.from );
|
||||
|
||||
out << sender.name
|
||||
out << sender.name
|
||||
<< " sent " << a.amount_to_pretty_string( op.amount ) << " to " << op.outputs.size() << " blinded balance" << (op.outputs.size()>1?"s":"")
|
||||
<< " fee: " << fa.amount_to_pretty_string( op.fee );
|
||||
return "";
|
||||
|
|
@ -2993,6 +3149,22 @@ std::string operation_printer::operator()(const asset_create_operation& op) cons
|
|||
return fee(op.fee);
|
||||
}
|
||||
|
||||
std::string operation_printer::operator()(const asset_dividend_distribution_operation& op)const
|
||||
{
|
||||
asset_object dividend_paying_asset = wallet.get_asset(op.dividend_asset_id);
|
||||
account_object receiver = wallet.get_account(op.account_id);
|
||||
|
||||
out << receiver.name << " received dividend payments for " << dividend_paying_asset.symbol << ": ";
|
||||
std::vector<std::string> pretty_payout_amounts;
|
||||
for (const asset& payment : op.amounts)
|
||||
{
|
||||
asset_object payout_asset = wallet.get_asset(payment.asset_id);
|
||||
pretty_payout_amounts.push_back(payout_asset.amount_to_pretty_string(payment));
|
||||
}
|
||||
out << boost::algorithm::join(pretty_payout_amounts, ", ");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string operation_result_printer::operator()(const void_result& x) const
|
||||
{
|
||||
return "";
|
||||
|
|
@ -3068,7 +3240,7 @@ vector<operation_detail> wallet_api::get_account_history(string name, int limit)
|
|||
while( limit > 0 )
|
||||
{
|
||||
operation_history_id_type start;
|
||||
if( result.size() )
|
||||
if( result.size() )
|
||||
{
|
||||
start = result.back().op.id;
|
||||
start = start + 1;
|
||||
|
|
@ -3081,7 +3253,7 @@ vector<operation_detail> wallet_api::get_account_history(string name, int limit)
|
|||
auto memo = o.op.visit(detail::operation_printer(ss, *my, o.result));
|
||||
result.push_back( operation_detail{ memo, ss.str(), o } );
|
||||
}
|
||||
if( current.size() < std::min(100,limit) )
|
||||
if( (int)current.size() < std::min(100,limit) )
|
||||
break;
|
||||
limit -= current.size();
|
||||
}
|
||||
|
|
@ -3450,6 +3622,14 @@ signed_transaction wallet_api::update_bitasset(string symbol,
|
|||
return my->update_bitasset(symbol, new_options, broadcast);
|
||||
}
|
||||
|
||||
signed_transaction wallet_api::update_dividend_asset(string symbol,
|
||||
dividend_asset_options new_options,
|
||||
bool broadcast /* = false */)
|
||||
{
|
||||
return my->update_dividend_asset(symbol, new_options, broadcast);
|
||||
}
|
||||
|
||||
|
||||
signed_transaction wallet_api::update_asset_feed_producers(string symbol,
|
||||
flat_set<string> new_feed_producers,
|
||||
bool broadcast /* = false */)
|
||||
|
|
@ -3504,7 +3684,7 @@ signed_transaction wallet_api::whitelist_account(string authorizing_account,
|
|||
return my->whitelist_account(authorizing_account, account_to_list, new_listing_status, broadcast);
|
||||
}
|
||||
|
||||
signed_transaction wallet_api::create_committee_member(string owner_account, string url,
|
||||
signed_transaction wallet_api::create_committee_member(string owner_account, string url,
|
||||
bool broadcast /* = false */)
|
||||
{
|
||||
return my->create_committee_member(owner_account, url, broadcast);
|
||||
|
|
@ -3610,7 +3790,7 @@ signed_transaction wallet_api::set_desired_witness_and_committee_member_count(st
|
|||
uint16_t desired_number_of_committee_members,
|
||||
bool broadcast /* = false */)
|
||||
{
|
||||
return my->set_desired_witness_and_committee_member_count(account_to_modify, desired_number_of_witnesses,
|
||||
return my->set_desired_witness_and_committee_member_count(account_to_modify, desired_number_of_witnesses,
|
||||
desired_number_of_committee_members, broadcast);
|
||||
}
|
||||
|
||||
|
|
@ -3641,6 +3821,26 @@ void wallet_api::dbg_make_mia(string creator, string symbol)
|
|||
my->dbg_make_mia(creator, symbol);
|
||||
}
|
||||
|
||||
void wallet_api::dbg_push_blocks( std::string src_filename, uint32_t count )
|
||||
{
|
||||
my->dbg_push_blocks( src_filename, count );
|
||||
}
|
||||
|
||||
void wallet_api::dbg_generate_blocks( std::string debug_wif_key, uint32_t count )
|
||||
{
|
||||
my->dbg_generate_blocks( debug_wif_key, count );
|
||||
}
|
||||
|
||||
void wallet_api::dbg_stream_json_objects( const std::string& filename )
|
||||
{
|
||||
my->dbg_stream_json_objects( filename );
|
||||
}
|
||||
|
||||
void wallet_api::dbg_update_object( fc::variant_object update )
|
||||
{
|
||||
my->dbg_update_object( update );
|
||||
}
|
||||
|
||||
void wallet_api::network_add_nodes( const vector<string>& nodes )
|
||||
{
|
||||
my->network_add_nodes( nodes );
|
||||
|
|
@ -3950,7 +4150,7 @@ vector< signed_transaction > wallet_api_impl::import_balance( string name_or_id,
|
|||
if( broadcast )
|
||||
_remote_net_broadcast->broadcast_transaction(signed_tx);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
} FC_CAPTURE_AND_RETHROW( (name_or_id) ) }
|
||||
|
||||
|
|
@ -3980,6 +4180,28 @@ signed_transaction wallet_api::sell_asset(string seller_account,
|
|||
symbol_to_receive, expiration, fill_or_kill, broadcast);
|
||||
}
|
||||
|
||||
signed_transaction wallet_api::sell( string seller_account,
|
||||
string base,
|
||||
string quote,
|
||||
double rate,
|
||||
double amount,
|
||||
bool broadcast )
|
||||
{
|
||||
return my->sell_asset( seller_account, std::to_string( amount ), base,
|
||||
std::to_string( rate * amount ), quote, 0, false, broadcast );
|
||||
}
|
||||
|
||||
signed_transaction wallet_api::buy( string buyer_account,
|
||||
string base,
|
||||
string quote,
|
||||
double rate,
|
||||
double amount,
|
||||
bool broadcast )
|
||||
{
|
||||
return my->sell_asset( buyer_account, std::to_string( rate * amount ), quote,
|
||||
std::to_string( amount ), base, 0, false, broadcast );
|
||||
}
|
||||
|
||||
signed_transaction wallet_api::borrow_asset(string seller_name, string amount_to_sell,
|
||||
string asset_symbol, string amount_of_collateral, bool broadcast)
|
||||
{
|
||||
|
|
@ -4103,7 +4325,7 @@ vector<asset> wallet_api::get_blind_balances( string key_or_label )
|
|||
}
|
||||
|
||||
blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_key_or_label,
|
||||
string to_account_id_or_name,
|
||||
string to_account_id_or_name,
|
||||
string amount_in,
|
||||
string symbol,
|
||||
bool broadcast )
|
||||
|
|
@ -4122,7 +4344,7 @@ blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_ke
|
|||
|
||||
|
||||
auto conf = blind_transfer_help( from_blind_account_key_or_label,
|
||||
from_blind_account_key_or_label,
|
||||
from_blind_account_key_or_label,
|
||||
blind_in, symbol, false, true/*to_temp*/ );
|
||||
FC_ASSERT( conf.outputs.size() > 0 );
|
||||
|
||||
|
|
@ -4137,24 +4359,24 @@ blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_ke
|
|||
conf.trx.operations.push_back(from_blind);
|
||||
ilog( "about to validate" );
|
||||
conf.trx.validate();
|
||||
|
||||
|
||||
if( broadcast && conf.outputs.size() == 2 ) {
|
||||
|
||||
|
||||
// Save the change
|
||||
blind_confirmation::output conf_output;
|
||||
blind_confirmation::output change_output = conf.outputs[0];
|
||||
|
||||
|
||||
// The wallet must have a private key for confirmation.to, this is used to decrypt the memo
|
||||
public_key_type from_key = get_public_key(from_blind_account_key_or_label);
|
||||
conf_output.confirmation.to = from_key;
|
||||
conf_output.confirmation.one_time_key = change_output.confirmation.one_time_key;
|
||||
conf_output.confirmation.encrypted_memo = change_output.confirmation.encrypted_memo;
|
||||
conf_output.confirmation_receipt = conf_output.confirmation;
|
||||
//try {
|
||||
//try {
|
||||
receive_blind_transfer( conf_output.confirmation_receipt, from_blind_account_key_or_label, "@"+to_account.name );
|
||||
//} catch ( ... ){}
|
||||
}
|
||||
|
||||
|
||||
ilog( "about to broadcast" );
|
||||
conf.trx = sign_transaction( conf.trx, broadcast );
|
||||
|
||||
|
|
@ -4175,7 +4397,7 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
|
|||
string symbol,
|
||||
bool broadcast,
|
||||
bool to_temp )
|
||||
{
|
||||
{
|
||||
blind_confirmation confirm;
|
||||
try {
|
||||
|
||||
|
|
@ -4219,7 +4441,7 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
|
|||
blinding_factors.push_back( start->data.blinding_factor );
|
||||
total_amount += start->amount;
|
||||
|
||||
if( total_amount >= amount + blind_tr.fee )
|
||||
if( total_amount >= amount + blind_tr.fee )
|
||||
break;
|
||||
}
|
||||
++start;
|
||||
|
|
@ -4287,7 +4509,7 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
|
|||
conf_output.decrypted_memo.amount = change;
|
||||
conf_output.decrypted_memo.blinding_factor = change_blind_factor;
|
||||
conf_output.decrypted_memo.commitment = change_out.commitment;
|
||||
conf_output.decrypted_memo.check = from_secret._hash[0];
|
||||
conf_output.decrypted_memo.check = from_secret._hash[0];
|
||||
conf_output.confirmation.one_time_key = one_time_key.get_public_key();
|
||||
conf_output.confirmation.to = from_key;
|
||||
conf_output.confirmation.encrypted_memo = fc::aes_encrypt( from_secret, fc::raw::pack( conf_output.decrypted_memo ) );
|
||||
|
|
@ -4305,7 +4527,7 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
|
|||
conf_output.decrypted_memo.amount = amount;
|
||||
conf_output.decrypted_memo.blinding_factor = blind_factor;
|
||||
conf_output.decrypted_memo.commitment = to_out.commitment;
|
||||
conf_output.decrypted_memo.check = secret._hash[0];
|
||||
conf_output.decrypted_memo.check = secret._hash[0];
|
||||
conf_output.confirmation.one_time_key = one_time_key.get_public_key();
|
||||
conf_output.confirmation.to = to_key;
|
||||
conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) );
|
||||
|
|
@ -4315,9 +4537,9 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
|
|||
confirm.outputs.push_back( conf_output );
|
||||
|
||||
/** commitments must be in sorted order */
|
||||
std::sort( blind_tr.outputs.begin(), blind_tr.outputs.end(),
|
||||
std::sort( blind_tr.outputs.begin(), blind_tr.outputs.end(),
|
||||
[&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } );
|
||||
std::sort( blind_tr.inputs.begin(), blind_tr.inputs.end(),
|
||||
std::sort( blind_tr.inputs.begin(), blind_tr.inputs.end(),
|
||||
[&]( const blind_input& a, const blind_input& b ){ return a.commitment < b.commitment; } );
|
||||
|
||||
confirm.trx.operations.emplace_back( std::move(blind_tr) );
|
||||
|
|
@ -4342,10 +4564,10 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
|
|||
* Transfers a public balance from @from to one or more blinded balances using a
|
||||
* stealth transfer.
|
||||
*/
|
||||
blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name,
|
||||
blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name,
|
||||
string asset_symbol,
|
||||
/** map from key or label to amount */
|
||||
vector<pair<string, string>> to_amounts,
|
||||
vector<pair<string, string>> to_amounts,
|
||||
bool broadcast )
|
||||
{ try {
|
||||
FC_ASSERT( !is_locked() );
|
||||
|
|
@ -4377,7 +4599,7 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name
|
|||
|
||||
auto amount = asset_obj->amount_from_string(item.second);
|
||||
total_amount += amount;
|
||||
|
||||
|
||||
|
||||
fc::ecc::public_key to_pub_key = to_key;
|
||||
blind_output out;
|
||||
|
|
@ -4393,7 +4615,7 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name
|
|||
conf_output.decrypted_memo.amount = amount;
|
||||
conf_output.decrypted_memo.blinding_factor = blind_factor;
|
||||
conf_output.decrypted_memo.commitment = out.commitment;
|
||||
conf_output.decrypted_memo.check = secret._hash[0];
|
||||
conf_output.decrypted_memo.check = secret._hash[0];
|
||||
conf_output.confirmation.one_time_key = one_time_key.get_public_key();
|
||||
conf_output.confirmation.to = to_key;
|
||||
conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) );
|
||||
|
|
@ -4407,7 +4629,7 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name
|
|||
bop.blinding_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() );
|
||||
|
||||
/** commitments must be in sorted order */
|
||||
std::sort( bop.outputs.begin(), bop.outputs.end(),
|
||||
std::sort( bop.outputs.begin(), bop.outputs.end(),
|
||||
[&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } );
|
||||
|
||||
confirm.trx.operations.push_back( bop );
|
||||
|
|
@ -4453,7 +4675,7 @@ blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, s
|
|||
|
||||
result.to_key = *conf.to;
|
||||
result.to_label = get_key_label( result.to_key );
|
||||
if( memo.from )
|
||||
if( memo.from )
|
||||
{
|
||||
result.from_key = *memo.from;
|
||||
result.from_label = get_key_label( result.from_key );
|
||||
|
|
@ -4473,7 +4695,7 @@ blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, s
|
|||
// confirm the amount matches the commitment (verify the blinding factor)
|
||||
auto commtiment_test = fc::ecc::blind( memo.blinding_factor, memo.amount.amount.value );
|
||||
FC_ASSERT( fc::ecc::verify_sum( {commtiment_test}, {memo.commitment}, 0 ) );
|
||||
|
||||
|
||||
blind_balance bal;
|
||||
bal.amount = memo.amount;
|
||||
bal.to = *conf.to;
|
||||
|
|
@ -4491,7 +4713,7 @@ blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, s
|
|||
auto child_key_itr = owner.key_auths.find( child_pubkey );
|
||||
if( child_key_itr != owner.key_auths.end() )
|
||||
my->_keys[child_key_itr->first] = key_to_wif( child_priv_key );
|
||||
|
||||
|
||||
// my->_wallet.blinded_balances[memo.amount.asset_id][bal.to].push_back( bal );
|
||||
|
||||
result.date = fc::time_point::now();
|
||||
|
|
@ -4508,7 +4730,7 @@ vector<blind_receipt> wallet_api::blind_history( string key_or_account )
|
|||
vector<blind_receipt> result;
|
||||
auto pub_key = get_public_key( key_or_account );
|
||||
|
||||
if( pub_key == public_key_type() )
|
||||
if( pub_key == public_key_type() )
|
||||
return vector<blind_receipt>();
|
||||
|
||||
for( auto& r : my->_wallet.blind_receipts )
|
||||
|
|
@ -4629,6 +4851,11 @@ signed_block_with_info::signed_block_with_info()
|
|||
{
|
||||
}
|
||||
|
||||
order_book wallet_api::get_order_book( const string& base, const string& quote, unsigned limit )
|
||||
{
|
||||
return( my->_remote_db->get_order_book( base, quote, limit ) );
|
||||
}
|
||||
|
||||
signed_block_with_info::signed_block_with_info( const signed_block& block )
|
||||
: signed_block( block )
|
||||
{
|
||||
|
|
|
|||
21
programs/debug_node/CMakeLists.txt
Normal file
21
programs/debug_node/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
add_executable( debug_node main.cpp )
|
||||
if( UNIX AND NOT APPLE )
|
||||
set(rt_library rt )
|
||||
endif()
|
||||
|
||||
find_package( Gperftools QUIET )
|
||||
if( GPERFTOOLS_FOUND )
|
||||
message( STATUS "Found gperftools; compiling debug_node with TCMalloc")
|
||||
list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc )
|
||||
endif()
|
||||
|
||||
target_link_libraries( debug_node
|
||||
PRIVATE graphene_app graphene_account_history graphene_market_history graphene_witness graphene_debug_witness graphene_chain graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )
|
||||
|
||||
install( TARGETS
|
||||
debug_node
|
||||
|
||||
RUNTIME DESTINATION bin
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
307
programs/debug_node/main.cpp
Normal file
307
programs/debug_node/main.cpp
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
|
||||
*
|
||||
* The MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include <graphene/app/application.hpp>
|
||||
|
||||
#include <graphene/debug_witness/debug_witness.hpp>
|
||||
#include <graphene/account_history/account_history_plugin.hpp>
|
||||
#include <graphene/market_history/market_history_plugin.hpp>
|
||||
|
||||
#include <fc/exception/exception.hpp>
|
||||
#include <fc/thread/thread.hpp>
|
||||
#include <fc/interprocess/signals.hpp>
|
||||
#include <fc/log/console_appender.hpp>
|
||||
#include <fc/log/file_appender.hpp>
|
||||
#include <fc/log/logger.hpp>
|
||||
#include <fc/log/logger_config.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef WIN32
|
||||
# include <signal.h>
|
||||
#else
|
||||
# include <csignal>
|
||||
#endif
|
||||
|
||||
using namespace graphene;
|
||||
namespace bpo = boost::program_options;
|
||||
|
||||
void write_default_logging_config_to_stream(std::ostream& out);
|
||||
fc::optional<fc::logging_config> load_logging_config_from_ini_file(const fc::path& config_ini_filename);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
app::application* node = new app::application();
|
||||
fc::oexception unhandled_exception;
|
||||
try {
|
||||
bpo::options_description app_options("Graphene Witness Node");
|
||||
bpo::options_description cfg_options("Graphene Witness Node");
|
||||
app_options.add_options()
|
||||
("help,h", "Print this help message and exit.")
|
||||
("data-dir,d", bpo::value<boost::filesystem::path>()->default_value("witness_node_data_dir"), "Directory containing databases, configuration file, etc.")
|
||||
;
|
||||
|
||||
bpo::variables_map options;
|
||||
|
||||
auto witness_plug = node->register_plugin<debug_witness_plugin::debug_witness_plugin>();
|
||||
auto history_plug = node->register_plugin<account_history::account_history_plugin>();
|
||||
auto market_history_plug = node->register_plugin<market_history::market_history_plugin>();
|
||||
|
||||
try
|
||||
{
|
||||
bpo::options_description cli, cfg;
|
||||
node->set_program_options(cli, cfg);
|
||||
app_options.add(cli);
|
||||
cfg_options.add(cfg);
|
||||
bpo::store(bpo::parse_command_line(argc, argv, app_options), options);
|
||||
}
|
||||
catch (const boost::program_options::error& e)
|
||||
{
|
||||
std::cerr << "Error parsing command line: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if( options.count("help") )
|
||||
{
|
||||
std::cout << app_options << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
fc::path data_dir;
|
||||
if( options.count("data-dir") )
|
||||
{
|
||||
data_dir = options["data-dir"].as<boost::filesystem::path>();
|
||||
if( data_dir.is_relative() )
|
||||
data_dir = fc::current_path() / data_dir;
|
||||
}
|
||||
|
||||
fc::path config_ini_path = data_dir / "config.ini";
|
||||
if( fc::exists(config_ini_path) )
|
||||
{
|
||||
// get the basic options
|
||||
bpo::store(bpo::parse_config_file<char>(config_ini_path.preferred_string().c_str(), cfg_options, true), options);
|
||||
|
||||
// try to get logging options from the config file.
|
||||
try
|
||||
{
|
||||
fc::optional<fc::logging_config> logging_config = load_logging_config_from_ini_file(config_ini_path);
|
||||
if (logging_config)
|
||||
fc::configure_logging(*logging_config);
|
||||
}
|
||||
catch (const fc::exception&)
|
||||
{
|
||||
wlog("Error parsing logging config from config file ${config}, using default config", ("config", config_ini_path.preferred_string()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ilog("Writing new config file at ${path}", ("path", config_ini_path));
|
||||
if( !fc::exists(data_dir) )
|
||||
fc::create_directories(data_dir);
|
||||
|
||||
std::ofstream out_cfg(config_ini_path.preferred_string());
|
||||
for( const boost::shared_ptr<bpo::option_description> od : cfg_options.options() )
|
||||
{
|
||||
if( !od->description().empty() )
|
||||
out_cfg << "# " << od->description() << "\n";
|
||||
boost::any store;
|
||||
if( !od->semantic()->apply_default(store) )
|
||||
out_cfg << "# " << od->long_name() << " = \n";
|
||||
else {
|
||||
auto example = od->format_parameter();
|
||||
if( example.empty() )
|
||||
// This is a boolean switch
|
||||
out_cfg << od->long_name() << " = " << "false\n";
|
||||
else {
|
||||
// The string is formatted "arg (=<interesting part>)"
|
||||
example.erase(0, 6);
|
||||
example.erase(example.length()-1);
|
||||
out_cfg << od->long_name() << " = " << example << "\n";
|
||||
}
|
||||
}
|
||||
out_cfg << "\n";
|
||||
}
|
||||
write_default_logging_config_to_stream(out_cfg);
|
||||
out_cfg.close();
|
||||
// read the default logging config we just wrote out to the file and start using it
|
||||
fc::optional<fc::logging_config> logging_config = load_logging_config_from_ini_file(config_ini_path);
|
||||
if (logging_config)
|
||||
fc::configure_logging(*logging_config);
|
||||
}
|
||||
|
||||
bpo::notify(options);
|
||||
node->initialize(data_dir, options);
|
||||
node->initialize_plugins( options );
|
||||
|
||||
node->startup();
|
||||
node->startup_plugins();
|
||||
|
||||
fc::promise<int>::ptr exit_promise = new fc::promise<int>("UNIX Signal Handler");
|
||||
|
||||
fc::set_signal_handler([&exit_promise](int signal) {
|
||||
elog( "Caught SIGINT attempting to exit cleanly" );
|
||||
exit_promise->set_value(signal);
|
||||
}, SIGINT);
|
||||
|
||||
fc::set_signal_handler([&exit_promise](int signal) {
|
||||
elog( "Caught SIGTERM attempting to exit cleanly" );
|
||||
exit_promise->set_value(signal);
|
||||
}, SIGTERM);
|
||||
|
||||
ilog("Started witness node on a chain with ${h} blocks.", ("h", node->chain_database()->head_block_num()));
|
||||
ilog("Chain ID is ${id}", ("id", node->chain_database()->get_chain_id()) );
|
||||
|
||||
int signal = exit_promise->wait();
|
||||
ilog("Exiting from signal ${n}", ("n", signal));
|
||||
node->shutdown_plugins();
|
||||
node->shutdown();
|
||||
delete node;
|
||||
return 0;
|
||||
} catch( const fc::exception& e ) {
|
||||
// deleting the node can yield, so do this outside the exception handler
|
||||
unhandled_exception = e;
|
||||
}
|
||||
|
||||
if (unhandled_exception)
|
||||
{
|
||||
elog("Exiting with error:\n${e}", ("e", unhandled_exception->to_detail_string()));
|
||||
node->shutdown();
|
||||
delete node;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// logging config is too complicated to be parsed by boost::program_options,
|
||||
// so we do it by hand
|
||||
//
|
||||
// Currently, you can only specify the filenames and logging levels, which
|
||||
// are all most users would want to change. At a later time, options can
|
||||
// be added to control rotation intervals, compression, and other seldom-
|
||||
// used features
|
||||
void write_default_logging_config_to_stream(std::ostream& out)
|
||||
{
|
||||
out << "# declare an appender named \"stderr\" that writes messages to the console\n"
|
||||
"[log.console_appender.stderr]\n"
|
||||
"stream=std_error\n\n"
|
||||
"# declare an appender named \"p2p\" that writes messages to p2p.log\n"
|
||||
"[log.file_appender.p2p]\n"
|
||||
"filename=logs/p2p/p2p.log\n"
|
||||
"# filename can be absolute or relative to this config file\n\n"
|
||||
"# route any messages logged to the default logger to the \"stderr\" logger we\n"
|
||||
"# declared above, if they are info level are higher\n"
|
||||
"[logger.default]\n"
|
||||
"level=info\n"
|
||||
"appenders=stderr\n\n"
|
||||
"# route messages sent to the \"p2p\" logger to the p2p appender declared above\n"
|
||||
"[logger.p2p]\n"
|
||||
"level=debug\n"
|
||||
"appenders=p2p\n\n";
|
||||
}
|
||||
|
||||
fc::optional<fc::logging_config> load_logging_config_from_ini_file(const fc::path& config_ini_filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
fc::logging_config logging_config;
|
||||
bool found_logging_config = false;
|
||||
|
||||
boost::property_tree::ptree config_ini_tree;
|
||||
boost::property_tree::ini_parser::read_ini(config_ini_filename.preferred_string().c_str(), config_ini_tree);
|
||||
for (const auto& section : config_ini_tree)
|
||||
{
|
||||
const std::string& section_name = section.first;
|
||||
const boost::property_tree::ptree& section_tree = section.second;
|
||||
|
||||
const std::string console_appender_section_prefix = "log.console_appender.";
|
||||
const std::string file_appender_section_prefix = "log.file_appender.";
|
||||
const std::string logger_section_prefix = "logger.";
|
||||
|
||||
if (boost::starts_with(section_name, console_appender_section_prefix))
|
||||
{
|
||||
std::string console_appender_name = section_name.substr(console_appender_section_prefix.length());
|
||||
std::string stream_name = section_tree.get<std::string>("stream");
|
||||
|
||||
// construct a default console appender config here
|
||||
// stdout/stderr will be taken from ini file, everything else hard-coded here
|
||||
fc::console_appender::config console_appender_config;
|
||||
console_appender_config.level_colors.emplace_back(
|
||||
fc::console_appender::level_color(fc::log_level::debug,
|
||||
fc::console_appender::color::green));
|
||||
console_appender_config.level_colors.emplace_back(
|
||||
fc::console_appender::level_color(fc::log_level::warn,
|
||||
fc::console_appender::color::brown));
|
||||
console_appender_config.level_colors.emplace_back(
|
||||
fc::console_appender::level_color(fc::log_level::error,
|
||||
fc::console_appender::color::cyan));
|
||||
console_appender_config.stream = fc::variant(stream_name).as<fc::console_appender::stream::type>();
|
||||
logging_config.appenders.push_back(fc::appender_config(console_appender_name, "console", fc::variant(console_appender_config)));
|
||||
found_logging_config = true;
|
||||
}
|
||||
else if (boost::starts_with(section_name, file_appender_section_prefix))
|
||||
{
|
||||
std::string file_appender_name = section_name.substr(file_appender_section_prefix.length());
|
||||
fc::path file_name = section_tree.get<std::string>("filename");
|
||||
if (file_name.is_relative())
|
||||
file_name = fc::absolute(config_ini_filename).parent_path() / file_name;
|
||||
|
||||
|
||||
// construct a default file appender config here
|
||||
// filename will be taken from ini file, everything else hard-coded here
|
||||
fc::file_appender::config file_appender_config;
|
||||
file_appender_config.filename = file_name;
|
||||
file_appender_config.flush = true;
|
||||
file_appender_config.rotate = true;
|
||||
file_appender_config.rotation_interval = fc::hours(1);
|
||||
file_appender_config.rotation_limit = fc::days(1);
|
||||
logging_config.appenders.push_back(fc::appender_config(file_appender_name, "file", fc::variant(file_appender_config)));
|
||||
found_logging_config = true;
|
||||
}
|
||||
else if (boost::starts_with(section_name, logger_section_prefix))
|
||||
{
|
||||
std::string logger_name = section_name.substr(logger_section_prefix.length());
|
||||
std::string level_string = section_tree.get<std::string>("level");
|
||||
std::string appenders_string = section_tree.get<std::string>("appenders");
|
||||
fc::logger_config logger_config(logger_name);
|
||||
logger_config.level = fc::variant(level_string).as<fc::log_level>();
|
||||
boost::split(logger_config.appenders, appenders_string,
|
||||
boost::is_any_of(" ,"),
|
||||
boost::token_compress_on);
|
||||
logging_config.loggers.push_back(logger_config);
|
||||
found_logging_config = true;
|
||||
}
|
||||
}
|
||||
if (found_logging_config)
|
||||
return logging_config;
|
||||
else
|
||||
return fc::optional<fc::logging_config>();
|
||||
}
|
||||
FC_RETHROW_EXCEPTIONS(warn, "")
|
||||
}
|
||||
|
|
@ -9,8 +9,9 @@ if( GPERFTOOLS_FOUND )
|
|||
list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc )
|
||||
endif()
|
||||
|
||||
# We have to link against graphene_debug_witness because deficiency in our API infrastructure doesn't allow plugins to be fully abstracted #246
|
||||
target_link_libraries( witness_node
|
||||
PRIVATE graphene_app graphene_account_history graphene_market_history graphene_generate_genesis graphene_witness graphene_chain graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )
|
||||
PRIVATE graphene_app graphene_account_history graphene_market_history graphene_generate_genesis graphene_witness graphene_chain graphene_debug_witness graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} )
|
||||
|
||||
install( TARGETS
|
||||
witness_node
|
||||
|
|
|
|||
|
|
@ -1040,6 +1040,20 @@ int64_t database_fixture::get_balance( const account_object& account, const asse
|
|||
return db.get_balance(account.get_id(), a.get_id()).amount.value;
|
||||
}
|
||||
|
||||
int64_t database_fixture::get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type,
|
||||
account_id_type dividend_holder_account_id,
|
||||
asset_id_type dividend_payout_asset_type) const
|
||||
{
|
||||
const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index =
|
||||
db.get_index_type<pending_dividend_payout_balance_for_holder_object_index>();
|
||||
auto pending_payout_iter =
|
||||
pending_payout_balance_index.indices().get<by_dividend_payout_account>().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id));
|
||||
if (pending_payout_iter == pending_payout_balance_index.indices().get<by_dividend_payout_account>().end())
|
||||
return 0;
|
||||
else
|
||||
return pending_payout_iter->pending_balance.value;
|
||||
}
|
||||
|
||||
vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const
|
||||
{
|
||||
vector< operation_history_object > result;
|
||||
|
|
|
|||
|
|
@ -280,6 +280,9 @@ struct database_fixture {
|
|||
void print_joint_market( const string& syma, const string& symb )const;
|
||||
int64_t get_balance( account_id_type account, asset_id_type a )const;
|
||||
int64_t get_balance( const account_object& account, const asset_object& a )const;
|
||||
int64_t get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type,
|
||||
account_id_type dividend_holder_account_id,
|
||||
asset_id_type dividend_payout_asset_type) const;
|
||||
vector< operation_history_object > get_operation_history( account_id_type account_id )const;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -747,6 +747,7 @@ BOOST_AUTO_TEST_CASE( stealth_fba_test )
|
|||
generate_blocks( HARDFORK_555_TIME );
|
||||
generate_blocks( HARDFORK_563_TIME );
|
||||
generate_blocks( HARDFORK_572_TIME );
|
||||
generate_blocks( HARDFORK_599_TIME );
|
||||
|
||||
// Philbin (registrar who registers Rex)
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
#include <graphene/chain/vesting_balance_object.hpp>
|
||||
#include <graphene/chain/withdraw_permission_object.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
#include <graphene/account_history/account_history_plugin.hpp>
|
||||
|
||||
#include <fc/crypto/digest.hpp>
|
||||
|
||||
|
|
@ -1111,6 +1112,459 @@ BOOST_AUTO_TEST_CASE( uia_fees )
|
|||
}
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE( dividend_tests, database_fixture )
|
||||
|
||||
BOOST_AUTO_TEST_CASE( create_dividend_uia )
|
||||
{
|
||||
using namespace graphene;
|
||||
try {
|
||||
BOOST_TEST_MESSAGE("Creating dividend holder asset");
|
||||
{
|
||||
asset_create_operation creator;
|
||||
creator.issuer = account_id_type();
|
||||
creator.fee = asset();
|
||||
creator.symbol = "DIVIDEND";
|
||||
creator.common_options.max_supply = 100000000;
|
||||
creator.precision = 2;
|
||||
creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/
|
||||
creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK;
|
||||
creator.common_options.flags = charge_market_fee;
|
||||
creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))});
|
||||
trx.operations.push_back(std::move(creator));
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
}
|
||||
|
||||
BOOST_TEST_MESSAGE("Creating test accounts");
|
||||
create_account("alice");
|
||||
create_account("bob");
|
||||
create_account("carol");
|
||||
create_account("dave");
|
||||
create_account("frank");
|
||||
|
||||
BOOST_TEST_MESSAGE("Creating test asset");
|
||||
{
|
||||
asset_create_operation creator;
|
||||
creator.issuer = account_id_type();
|
||||
creator.fee = asset();
|
||||
creator.symbol = "TEST";
|
||||
creator.common_options.max_supply = 100000000;
|
||||
creator.precision = 2;
|
||||
creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/
|
||||
creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK;
|
||||
creator.common_options.flags = charge_market_fee;
|
||||
creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))});
|
||||
trx.operations.push_back(std::move(creator));
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
}
|
||||
generate_block();
|
||||
|
||||
BOOST_TEST_MESSAGE("Funding asset fee pool");
|
||||
{
|
||||
asset_fund_fee_pool_operation fund_op;
|
||||
fund_op.from_account = account_id_type();
|
||||
fund_op.asset_id = get_asset("TEST").id;
|
||||
fund_op.amount = 500000000;
|
||||
trx.operations.push_back(std::move(fund_op));
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
}
|
||||
|
||||
// our DIVIDEND asset should not yet be a divdend asset
|
||||
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||
BOOST_CHECK(!dividend_holder_asset_object.dividend_data_id);
|
||||
|
||||
BOOST_TEST_MESSAGE("Converting the new asset to a dividend holder asset");
|
||||
{
|
||||
asset_update_dividend_operation op;
|
||||
op.issuer = dividend_holder_asset_object.issuer;
|
||||
op.asset_to_update = dividend_holder_asset_object.id;
|
||||
op.new_options.next_payout_time = db.head_block_time() + fc::minutes(1);
|
||||
op.new_options.payout_interval = 60 * 60 * 24 * 3;
|
||||
|
||||
trx.operations.push_back(op);
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
}
|
||||
generate_block();
|
||||
|
||||
BOOST_TEST_MESSAGE("Verifying the dividend holder asset options");
|
||||
BOOST_REQUIRE(dividend_holder_asset_object.dividend_data_id);
|
||||
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||
{
|
||||
BOOST_REQUIRE(dividend_data.options.payout_interval);
|
||||
BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24 * 3);
|
||||
}
|
||||
|
||||
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||
BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution");
|
||||
|
||||
// db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
|
||||
// {
|
||||
// _gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_base_fee = 100;
|
||||
// _gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_fee_per_holder = 100;
|
||||
// } );
|
||||
|
||||
|
||||
} catch(fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( test_update_dividend_interval )
|
||||
{
|
||||
using namespace graphene;
|
||||
try {
|
||||
INVOKE( create_dividend_uia );
|
||||
|
||||
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||
|
||||
auto advance_to_next_payout_time = [&]() {
|
||||
// Advance to the next upcoming payout time
|
||||
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||
fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||
// generate blocks up to the next scheduled time
|
||||
generate_blocks(next_payout_scheduled_time);
|
||||
// if the scheduled time fell on a maintenance interval, then we should have paid out.
|
||||
// if not, we need to advance to the next maintenance interval to trigger the payout
|
||||
if (dividend_data.options.next_payout_time)
|
||||
{
|
||||
// we know there was a next_payout_time set when we entered this, so if
|
||||
// it has been cleared, we must have already processed payouts, no need to
|
||||
// further advance time.
|
||||
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||
if (*dividend_data.options.next_payout_time == next_payout_scheduled_time)
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_TEST_MESSAGE("Updating the payout interval");
|
||||
{
|
||||
asset_update_dividend_operation op;
|
||||
op.issuer = dividend_holder_asset_object.issuer;
|
||||
op.asset_to_update = dividend_holder_asset_object.id;
|
||||
op.new_options.next_payout_time = fc::time_point::now() + fc::minutes(1);
|
||||
op.new_options.payout_interval = 60 * 60 * 24; // 1 days
|
||||
trx.operations.push_back(op);
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
}
|
||||
generate_block();
|
||||
|
||||
BOOST_TEST_MESSAGE("Verifying the updated dividend holder asset options");
|
||||
{
|
||||
BOOST_REQUIRE(dividend_data.options.payout_interval);
|
||||
BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24);
|
||||
}
|
||||
|
||||
BOOST_TEST_MESSAGE("Removing the payout interval");
|
||||
{
|
||||
asset_update_dividend_operation op;
|
||||
op.issuer = dividend_holder_asset_object.issuer;
|
||||
op.asset_to_update = dividend_holder_asset_object.id;
|
||||
op.new_options.next_payout_time = dividend_data.options.next_payout_time;
|
||||
op.new_options.payout_interval = fc::optional<uint32_t>();
|
||||
trx.operations.push_back(op);
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
}
|
||||
generate_block();
|
||||
BOOST_CHECK(!dividend_data.options.payout_interval);
|
||||
advance_to_next_payout_time();
|
||||
BOOST_REQUIRE_MESSAGE(!dividend_data.options.next_payout_time, "A new payout was scheduled, but none should have been");
|
||||
} catch(fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution )
|
||||
{
|
||||
using namespace graphene;
|
||||
try {
|
||||
INVOKE( create_dividend_uia );
|
||||
|
||||
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||
const account_object& alice = get_account("alice");
|
||||
const account_object& bob = get_account("bob");
|
||||
const account_object& carol = get_account("carol");
|
||||
const account_object& dave = get_account("dave");
|
||||
const account_object& frank = get_account("frank");
|
||||
const auto& test_asset_object = get_asset("TEST");
|
||||
|
||||
auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue)
|
||||
{
|
||||
asset_issue_operation op;
|
||||
op.issuer = asset_to_issue.issuer;
|
||||
op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id);
|
||||
op.issue_to_account = destination_account.id;
|
||||
trx.operations.push_back( op );
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
};
|
||||
|
||||
auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) {
|
||||
int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id,
|
||||
holder_account_obj.id,
|
||||
payout_asset_obj.id);
|
||||
BOOST_CHECK_EQUAL(pending_balance, expected_balance);
|
||||
};
|
||||
|
||||
auto advance_to_next_payout_time = [&]() {
|
||||
// Advance to the next upcoming payout time
|
||||
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||
fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||
// generate blocks up to the next scheduled time
|
||||
generate_blocks(next_payout_scheduled_time);
|
||||
// if the scheduled time fell on a maintenance interval, then we should have paid out.
|
||||
// if not, we need to advance to the next maintenance interval to trigger the payout
|
||||
if (dividend_data.options.next_payout_time)
|
||||
{
|
||||
// we know there was a next_payout_time set when we entered this, so if
|
||||
// it has been cleared, we must have already processed payouts, no need to
|
||||
// further advance time.
|
||||
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||
if (*dividend_data.options.next_payout_time == next_payout_scheduled_time)
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
}
|
||||
};
|
||||
|
||||
// the first test will be testing pending balances, so we need to hit a
|
||||
// maintenance interval that isn't the payout interval. Payout is
|
||||
// every 3 days, maintenance interval is every 1 day.
|
||||
advance_to_next_payout_time();
|
||||
|
||||
// Set up the first test, issue alice, bob, and carol each 100 DIVIDEND.
|
||||
// Then deposit 300 TEST in the distribution account, and see that they
|
||||
// each are credited 100 TEST.
|
||||
issue_asset_to_account(dividend_holder_asset_object, alice, 100000);
|
||||
issue_asset_to_account(dividend_holder_asset_object, bob, 100000);
|
||||
issue_asset_to_account(dividend_holder_asset_object, carol, 100000);
|
||||
|
||||
BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account");
|
||||
issue_asset_to_account(test_asset_object, dividend_distribution_account, 30000);
|
||||
|
||||
generate_block();
|
||||
|
||||
BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" );
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
|
||||
verify_pending_balance(alice, test_asset_object, 10000);
|
||||
verify_pending_balance(bob, test_asset_object, 10000);
|
||||
verify_pending_balance(carol, test_asset_object, 10000);
|
||||
|
||||
// For the second test, issue carol more than the other two, so it's
|
||||
// alice: 100 DIVIDND, bob: 100 DIVIDEND, carol: 200 DIVIDEND
|
||||
// Then deposit 400 TEST in the distribution account, and see that alice
|
||||
// and bob are credited with 100 TEST, and carol gets 200 TEST
|
||||
BOOST_TEST_MESSAGE("Issuing carol twice as much of the holder asset");
|
||||
issue_asset_to_account(dividend_holder_asset_object, carol, 100000); // one thousand at two digits of precision
|
||||
issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000); // one thousand at two digits of precision
|
||||
BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" );
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
verify_pending_balance(alice, test_asset_object, 20000);
|
||||
verify_pending_balance(bob, test_asset_object, 20000);
|
||||
verify_pending_balance(carol, test_asset_object, 30000);
|
||||
|
||||
fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||
advance_to_next_payout_time();
|
||||
|
||||
|
||||
BOOST_REQUIRE_MESSAGE(dividend_data.options.next_payout_time, "No new payout was scheduled");
|
||||
BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time != *dividend_data.options.next_payout_time,
|
||||
"New payout was scheduled for the same time as the last payout");
|
||||
BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time + *dividend_data.options.payout_interval == *dividend_data.options.next_payout_time,
|
||||
"New payout was not scheduled for the expected time");
|
||||
|
||||
auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout)
|
||||
{
|
||||
BOOST_TEST_MESSAGE("Verifying the virtual op was created");
|
||||
const account_transaction_history_index& hist_idx = db.get_index_type<account_transaction_history_index>();
|
||||
auto account_history_range = hist_idx.indices().get<by_seq>().equal_range(boost::make_tuple(destination_account.id));
|
||||
BOOST_REQUIRE(account_history_range.first != account_history_range.second);
|
||||
const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db);
|
||||
const asset_dividend_distribution_operation& distribution_operation = history_object.op.get<asset_dividend_distribution_operation>();
|
||||
BOOST_CHECK(distribution_operation.account_id == destination_account.id);
|
||||
BOOST_CHECK(distribution_operation.amounts.find(expected_payout) != distribution_operation.amounts.end());
|
||||
};
|
||||
|
||||
BOOST_TEST_MESSAGE("Verifying the payouts");
|
||||
BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000);
|
||||
verify_dividend_payout_operations(alice, asset(20000, test_asset_object.id));
|
||||
verify_pending_balance(alice, test_asset_object, 0);
|
||||
|
||||
BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000);
|
||||
verify_dividend_payout_operations(bob, asset(20000, test_asset_object.id));
|
||||
verify_pending_balance(bob, test_asset_object, 0);
|
||||
|
||||
BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 30000);
|
||||
verify_dividend_payout_operations(carol, asset(30000, test_asset_object.id));
|
||||
verify_pending_balance(carol, test_asset_object, 0);
|
||||
} catch(fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE( test_dividend_distribution_interval )
|
||||
{
|
||||
using namespace graphene;
|
||||
try {
|
||||
INVOKE( create_dividend_uia );
|
||||
|
||||
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||
const account_object& alice = get_account("alice");
|
||||
const account_object& bob = get_account("bob");
|
||||
const account_object& carol = get_account("carol");
|
||||
const account_object& dave = get_account("dave");
|
||||
const account_object& frank = get_account("frank");
|
||||
const auto& test_asset_object = get_asset("TEST");
|
||||
} catch(fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE( check_dividend_corner_cases )
|
||||
{
|
||||
using namespace graphene;
|
||||
try {
|
||||
INVOKE( create_dividend_uia );
|
||||
|
||||
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||
const account_object& alice = get_account("alice");
|
||||
const account_object& bob = get_account("bob");
|
||||
const account_object& carol = get_account("carol");
|
||||
const account_object& dave = get_account("dave");
|
||||
const account_object& frank = get_account("frank");
|
||||
const auto& test_asset_object = get_asset("TEST");
|
||||
|
||||
auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue)
|
||||
{
|
||||
asset_issue_operation op;
|
||||
op.issuer = asset_to_issue.issuer;
|
||||
op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id);
|
||||
op.issue_to_account = destination_account.id;
|
||||
trx.operations.push_back( op );
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
};
|
||||
|
||||
auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) {
|
||||
int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id,
|
||||
holder_account_obj.id,
|
||||
payout_asset_obj.id);
|
||||
BOOST_CHECK_EQUAL(pending_balance, expected_balance);
|
||||
};
|
||||
|
||||
auto reserve_asset_from_account = [&](const asset_object& asset_to_reserve, const account_object& from_account, int64_t amount_to_reserve)
|
||||
{
|
||||
asset_reserve_operation reserve_op;
|
||||
reserve_op.payer = from_account.id;
|
||||
reserve_op.amount_to_reserve = asset(amount_to_reserve, asset_to_reserve.id);
|
||||
trx.operations.push_back(reserve_op);
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
};
|
||||
auto advance_to_next_payout_time = [&]() {
|
||||
// Advance to the next upcoming payout time
|
||||
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||
fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||
// generate blocks up to the next scheduled time
|
||||
generate_blocks(next_payout_scheduled_time);
|
||||
// if the scheduled time fell on a maintenance interval, then we should have paid out.
|
||||
// if not, we need to advance to the next maintenance interval to trigger the payout
|
||||
if (dividend_data.options.next_payout_time)
|
||||
{
|
||||
// we know there was a next_payout_time set when we entered this, so if
|
||||
// it has been cleared, we must have already processed payouts, no need to
|
||||
// further advance time.
|
||||
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||
if (*dividend_data.options.next_payout_time == next_payout_scheduled_time)
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
}
|
||||
};
|
||||
|
||||
// the first test will be testing pending balances, so we need to hit a
|
||||
// maintenance interval that isn't the payout interval. Payout is
|
||||
// every 3 days, maintenance interval is every 1 day.
|
||||
advance_to_next_payout_time();
|
||||
|
||||
BOOST_TEST_MESSAGE("Testing a payout interval when there are no users holding the dividend asset");
|
||||
BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0);
|
||||
BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0);
|
||||
BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0);
|
||||
issue_asset_to_account(test_asset_object, dividend_distribution_account, 1000);
|
||||
BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval");
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
BOOST_TEST_MESSAGE("Verify that no pending payments were scheduled");
|
||||
verify_pending_balance(alice, test_asset_object, 0);
|
||||
verify_pending_balance(bob, test_asset_object, 0);
|
||||
verify_pending_balance(carol, test_asset_object, 0);
|
||||
advance_to_next_payout_time();
|
||||
BOOST_TEST_MESSAGE("Verify that no actual payments took place");
|
||||
verify_pending_balance(alice, test_asset_object, 0);
|
||||
verify_pending_balance(bob, test_asset_object, 0);
|
||||
verify_pending_balance(carol, test_asset_object, 0);
|
||||
BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 0);
|
||||
BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 0);
|
||||
BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 0);
|
||||
BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, test_asset_object), 1000);
|
||||
|
||||
BOOST_TEST_MESSAGE("Now give alice a small balance and see that she takes it all");
|
||||
issue_asset_to_account(dividend_holder_asset_object, alice, 1);
|
||||
generate_block();
|
||||
BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval");
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
BOOST_TEST_MESSAGE("Verify that no alice received her payment of the entire amount");
|
||||
verify_pending_balance(alice, test_asset_object, 1000);
|
||||
|
||||
// Test that we can pay out the dividend asset itself
|
||||
issue_asset_to_account(dividend_holder_asset_object, bob, 1);
|
||||
issue_asset_to_account(dividend_holder_asset_object, carol, 1);
|
||||
issue_asset_to_account(dividend_holder_asset_object, dividend_distribution_account, 300);
|
||||
generate_block();
|
||||
BOOST_CHECK_EQUAL(get_balance(alice, dividend_holder_asset_object), 1);
|
||||
BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 1);
|
||||
BOOST_CHECK_EQUAL(get_balance(carol, dividend_holder_asset_object), 1);
|
||||
BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval");
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
BOOST_TEST_MESSAGE("Verify that the dividend asset was shared out");
|
||||
verify_pending_balance(alice, dividend_holder_asset_object, 100);
|
||||
verify_pending_balance(bob, dividend_holder_asset_object, 100);
|
||||
verify_pending_balance(carol, dividend_holder_asset_object, 100);
|
||||
} catch(fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
BOOST_AUTO_TEST_SUITE_END() // end dividend_tests suite
|
||||
|
||||
BOOST_AUTO_TEST_CASE( cancel_limit_order_test )
|
||||
{ try {
|
||||
INVOKE( issue_uia );
|
||||
|
|
|
|||
|
|
@ -1338,6 +1338,7 @@ BOOST_AUTO_TEST_CASE( top_n_special )
|
|||
ACTORS( (alice)(bob)(chloe)(dan)(izzy)(stan) );
|
||||
|
||||
generate_blocks( HARDFORK_516_TIME );
|
||||
generate_blocks( HARDFORK_599_TIME );
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -1490,6 +1491,7 @@ BOOST_AUTO_TEST_CASE( buyback )
|
|||
|
||||
generate_blocks( HARDFORK_538_TIME );
|
||||
generate_blocks( HARDFORK_555_TIME );
|
||||
generate_blocks( HARDFORK_599_TIME );
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -122,4 +122,30 @@ BOOST_AUTO_TEST_CASE( extended_public_key_type_test )
|
|||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( extension_serialization_test )
|
||||
{
|
||||
try
|
||||
{
|
||||
buyback_account_options bbo;
|
||||
bbo.asset_to_buy = asset_id_type(1000);
|
||||
bbo.asset_to_buy_issuer = account_id_type(2000);
|
||||
bbo.markets.emplace( asset_id_type() );
|
||||
bbo.markets.emplace( asset_id_type(777) );
|
||||
account_create_operation create_op = make_account( "rex" );
|
||||
create_op.registrar = account_id_type(1234);
|
||||
create_op.extensions.value.buyback_options = bbo;
|
||||
|
||||
auto packed = fc::raw::pack( create_op );
|
||||
account_create_operation unpacked = fc::raw::unpack<account_create_operation>(packed);
|
||||
|
||||
ilog( "original: ${x}", ("x", create_op) );
|
||||
ilog( "unpacked: ${x}", ("x", unpacked) );
|
||||
}
|
||||
catch ( const fc::exception& e )
|
||||
{
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
|||
Loading…
Reference in a new issue