diff --git a/.travis.yml b/.travis.yml index aace6349b6..c2282268c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ env: - DOCKERFILE=Dockerfile DOCKERNAME="" - DOCKERFILE=share/golosd/docker/Dockerfile-test DOCKERNAME="-test" - DOCKERFILE=share/golosd/docker/Dockerfile-testnet DOCKERNAME="-testnet" - - DOCKERFILE=share/golosd/docker/Dockerfile-lowmem DOCKERNAME="-lowmem" - DOCKERFILE=share/golosd/docker/Dockerfile-mongo DOCKERNAME="-mongo" matrix: @@ -26,12 +25,16 @@ script: elif [ -n "$TRAVIS_TAG" ]; then export DOCKERNAME="$TRAVIS_TAG""$DOCKERNAME"; export EXPORTNAME="$DOCKERNAME"; - else + elif [ "$DOCKERNAME" == "-testnet" ] || [ "$DOCKERNAME" == "-test" ]; then export DOCKERNAME=develop"$DOCKERNAME"; + else + export DOCKERNAME=""; fi - echo "$DOCKERFILE" - echo "$DOCKERNAME" - - docker build -t goloschain/golos:"$DOCKERNAME" -f "$DOCKERFILE" . + - if [ -n "$DOCKERNAME" ]; then + docker build -t goloschain/golos:"$DOCKERNAME" -f "$DOCKERFILE" .; + fi after_success: - echo "$EXPORTNAME" diff --git a/CMakeLists.txt b/CMakeLists.txt index e7d54dc2a8..6ac1ce119a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,16 +73,6 @@ if(MAX_19_VOTED_WITNESSES AND BUILD_GOLOS_TESTNET) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSTEEMIT_MAX_VOTED_WITNESSES=19") endif() -option(LOW_MEMORY_NODE "Build source for low memory node (ON OR OFF)" FALSE) -message(STATUS "LOW_MEMORY_NODE: ${LOW_MEMORY_NODE}") -if(LOW_MEMORY_NODE) - message(STATUS " ") - message(STATUS " CONFIGURING FOR LOW MEMORY NODE ") - message(STATUS " ") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DIS_LOW_MEM") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DIS_LOW_MEM") -endif() - option(CHAINBASE_CHECK_LOCKING "Check locks in chainbase (ON or OFF)" TRUE) message(STATUS "CHAINBASE_CHECK_LOCKING: ${CHAINBASE_CHECK_LOCKING}") if(CHAINBASE_CHECK_LOCKING) @@ -285,10 +275,3 @@ else() message(STATUS "\n\n CONFIGURED FOR GOLOS NETWORK \n\n") endif() -if(LOW_MEMORY_NODE) - message(STATUS "\n\n CONFIGURED FOR LOW MEMORY NODE \n\n") -else() - message(STATUS "\n\n CONFIGURED FOR FULL NODE \n\n") -endif() - - diff --git a/Dockerfile b/Dockerfile index f1c8d4b665..9d1040fbed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,7 +41,6 @@ RUN \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_GOLOS_TESTNET=FALSE \ -DBUILD_SHARED_LIBRARIES=FALSE \ - -DLOW_MEMORY_NODE=FALSE \ -DCHAINBASE_CHECK_LOCKING=FALSE \ -DENABLE_MONGO_PLUGIN=FALSE \ .. \ diff --git a/documentation/building.md b/documentation/building.md index 064b7db189..c90039d6a4 100644 --- a/documentation/building.md +++ b/documentation/building.md @@ -8,12 +8,6 @@ Specifies whether to build with or without optimization and without or with the symbol table for debugging. Unless you are specifically debugging or running tests, it is recommended to build as release. -### LOW_MEMORY_NODE=[FALSE/TRUE] - -Builds golosd to be a consensus-only low memory node. Data and fields not -needed for consensus are not stored in the object database. This option is -recommended for witnesses and seed-nodes. - ### BUILD_GOLOS_TESTNET=[FALSE/TRUE] Builds golos for use in a private testnet. Also required for building unit tests. diff --git a/libraries/api/CMakeLists.txt b/libraries/api/CMakeLists.txt index 8c38c3a064..b7efed09ef 100644 --- a/libraries/api/CMakeLists.txt +++ b/libraries/api/CMakeLists.txt @@ -14,7 +14,6 @@ list(APPEND CURRENT_TARGET_HEADERS list(APPEND CURRENT_TARGET_SOURCES account_api_object.cpp discussion_helper.cpp - comment_api_object.cpp chain_api_properties.cpp witness_api_object.cpp ) diff --git a/libraries/api/account_api_object.cpp b/libraries/api/account_api_object.cpp index 8558ae8490..9e1b344980 100644 --- a/libraries/api/account_api_object.cpp +++ b/libraries/api/account_api_object.cpp @@ -38,16 +38,16 @@ account_api_object::account_api_object(const account_object& a, const golos::cha proxied_vsf_votes.push_back(a.proxied_vsf_votes[i]); } - const auto& auth = db.get(name); + const auto& auth = db.get_authority(name); owner = authority(auth.owner); active = authority(auth.active); posting = authority(auth.posting); last_owner_update = auth.last_owner_update; -#ifndef IS_LOW_MEM - const auto& meta = db.get(name); - json_metadata = golos::chain::to_string(meta.json_metadata); -#endif + auto meta = db.find(name); + if (meta != nullptr) { + json_metadata = golos::chain::to_string(meta->json_metadata); + } auto post = db.find(std::make_tuple(name, bandwidth_type::post)); if (post != nullptr) { diff --git a/libraries/api/comment_api_object.cpp b/libraries/api/comment_api_object.cpp deleted file mode 100644 index 68fdb27ee1..0000000000 --- a/libraries/api/comment_api_object.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include - -namespace golos { namespace api { - - using namespace golos::chain; - - comment_api_object::comment_api_object(const golos::chain::comment_object &o, const golos::chain::database &db) - : id(o.id), - parent_author(o.parent_author), - parent_permlink(to_string(o.parent_permlink)), - author(o.author), - permlink(to_string(o.permlink)), - last_update(o.last_update), - created(o.created), - active(o.active), - last_payout(o.last_payout), - depth(o.depth), - children(o.children), - children_rshares2(o.children_rshares2), - net_rshares(o.net_rshares), - abs_rshares(o.abs_rshares), - vote_rshares(o.vote_rshares), - children_abs_rshares(o.children_abs_rshares), - cashout_time(o.cashout_time), - max_cashout_time(o.max_cashout_time), - total_vote_weight(o.total_vote_weight), - reward_weight(o.reward_weight), - total_payout_value(o.total_payout_value), - curator_payout_value(o.curator_payout_value), - author_rewards(o.author_rewards), - net_votes(o.net_votes), - mode(o.mode), - root_comment(o.root_comment), - max_accepted_payout(o.max_accepted_payout), - percent_steem_dollars(o.percent_steem_dollars), - allow_replies(o.allow_replies), - allow_votes(o.allow_votes), - allow_curation_rewards(o.allow_curation_rewards) { - - for (auto& route : o.beneficiaries) { - beneficiaries.push_back(route); - } -#ifndef IS_LOW_MEM - auto& content = db.get_comment_content(o.id); - - title = to_string(content.title); - body = to_string(content.body); - json_metadata = to_string(content.json_metadata); -#endif - if (o.parent_author == STEEMIT_ROOT_POST_PARENT) { - category = to_string(o.parent_permlink); - } else { - category = to_string(db.get(o.root_comment).parent_permlink); - } - } - - comment_api_object::comment_api_object() = default; - -} } // golos::api \ No newline at end of file diff --git a/libraries/api/discussion_helper.cpp b/libraries/api/discussion_helper.cpp index 6e71939570..a901d7664d 100644 --- a/libraries/api/discussion_helper.cpp +++ b/libraries/api/discussion_helper.cpp @@ -1,6 +1,6 @@ #include +#include #include -// #include #include #include #include @@ -8,43 +8,7 @@ namespace golos { namespace api { - comment_metadata get_metadata(const comment_api_object &c) { - - comment_metadata meta; - - if (!c.json_metadata.empty()) { - try { - meta = fc::json::from_string(c.json_metadata).as(); - } catch (const fc::exception& e) { - // Do nothing on malformed json_metadata - } - } - - std::set lower_tags; - - std::size_t tag_limit = 5; - for (const auto& name : meta.tags) { - if (lower_tags.size() > tag_limit) { - break; - } - auto value = boost::trim_copy(name); - if (value.empty()) { - continue; - } - boost::to_lower(value); - lower_tags.insert(value); - } - - meta.tags.swap(lower_tags); - - boost::trim(meta.language); - boost::to_lower(meta.language); - - return meta; - } - - - boost::multiprecision::uint256_t to256(const fc::uint128_t& t) { + boost::multiprecision::uint256_t to256(const fc::uint128_t& t) { boost::multiprecision::uint256_t result(t.high_bits()); result <<= 65; result += t.low_bits(); @@ -57,10 +21,12 @@ namespace golos { namespace api { impl( golos::chain::database& db, std::function&)> fill_reputation, - std::function fill_promoted) + std::function fill_promoted, + std::function fill_comment_info) : database_(db), fill_reputation_(fill_reputation), - fill_promoted_(fill_promoted) { + fill_promoted_(fill_promoted), + fill_comment_info_(fill_comment_info) { } ~impl() = default; @@ -73,6 +39,8 @@ namespace golos { namespace api { const std::string& author, const std::string& permlink, uint32_t limit ) const ; + share_type get_curator_unclaimed_rewards(const discussion& d, share_type max_rewards) const; + void set_pending_payout(discussion& d) const; void set_url(discussion& d) const; @@ -85,14 +53,81 @@ namespace golos { namespace api { return database_; } + comment_api_object create_comment_api_object(const comment_object& o) const; + discussion get_discussion(const comment_object& c, uint32_t vote_limit) const; + void fill_comment_api_object(const comment_object& o, comment_api_object& d) const; + private: golos::chain::database& database_; std::function&)> fill_reputation_; std::function fill_promoted_; + std::function fill_comment_info_; }; +// create_comment_api_object + comment_api_object discussion_helper::create_comment_api_object(const comment_object& o) const { + return pimpl->create_comment_api_object(o); + } + + comment_api_object discussion_helper::impl::create_comment_api_object(const comment_object& o) const { + comment_api_object result; + + fill_comment_api_object(o, result); + + return result; + } + +// fill_comment_api_object + + void discussion_helper::fill_comment_api_object(const comment_object& o, comment_api_object& d) const { + pimpl->fill_comment_api_object(o, d); + } + + void discussion_helper::impl::fill_comment_api_object(const comment_object& o, comment_api_object& d) const { + d.id = o.id; + d.parent_author = o.parent_author; + d.parent_permlink = to_string(o.parent_permlink); + d.author = o.author; + d.permlink = to_string(o.permlink); + d.created = o.created; + d.last_payout = o.last_payout; + d.depth = o.depth; + d.children = o.children; + d.children_rshares2 = o.children_rshares2; + d.net_rshares = o.net_rshares; + d.abs_rshares = o.abs_rshares; + d.vote_rshares = o.vote_rshares; + d.children_abs_rshares = o.children_abs_rshares; + d.cashout_time = o.cashout_time; + d.max_cashout_time = o.max_cashout_time; + d.total_vote_weight = o.total_vote_weight; + d.reward_weight = o.reward_weight; + d.net_votes = o.net_votes; + d.mode = o.mode; + d.root_comment = o.root_comment; + d.max_accepted_payout = o.max_accepted_payout; + d.percent_steem_dollars = o.percent_steem_dollars; + d.allow_replies = o.allow_replies; + d.allow_votes = o.allow_votes; + d.allow_curation_rewards = o.allow_curation_rewards; + + for (auto& route : o.beneficiaries) { + d.beneficiaries.push_back(route); + } + + if (fill_comment_info_) { + fill_comment_info_(database(), o, d); + } + + if (o.parent_author == STEEMIT_ROOT_POST_PARENT) { + d.category = to_string(o.parent_permlink); + } else { + d.category = to_string(database().get(o.root_comment).parent_permlink); + } + } + // get_discussion discussion discussion_helper::impl::get_discussion(const comment_object& c, uint32_t vote_limit) const { discussion d = create_discussion(c); @@ -138,6 +173,33 @@ namespace golos { namespace api { ) const { pimpl->select_active_votes(result, total_count, author, permlink, limit); } + + share_type discussion_helper::impl::get_curator_unclaimed_rewards(const discussion& d, share_type max_rewards) const { + share_type unclaimed_rewards = max_rewards; + auto& db = database(); + try { + uint128_t total_weight(d.total_vote_weight); + + if (d.allow_curation_rewards) { + if (d.total_vote_weight > 0) { + const auto &cvidx = db.get_index().indices().get(); + for (auto itr = cvidx.lower_bound(d.id); itr != cvidx.end() && itr->comment == d.id; ++itr) { + auto claim = ((max_rewards.value * uint128_t(itr->weight)) / total_weight).to_uint64(); + if (claim > 0) { // min_amt is non-zero satoshis + unclaimed_rewards -= claim; + } else { + break; + } + } + } + } else { + unclaimed_rewards = 0; + } + + return unclaimed_rewards; + } FC_CAPTURE_AND_RETHROW() + } + // // set_pending_payout void discussion_helper::impl::set_pending_payout(discussion& d) const { @@ -148,8 +210,9 @@ namespace golos { namespace api { const auto& props = db.get_dynamic_global_properties(); const auto& hist = db.get_feed_history(); asset pot = props.total_reward_fund_steem; - if (!hist.current_median_history.is_null()) { - pot = pot * hist.current_median_history; + + if (hist.current_median_history.is_null()) { + return; } u256 total_r2 = to256(props.total_reward_shares2); @@ -158,21 +221,63 @@ namespace golos { namespace api { auto vshares = db.calculate_vshares(d.net_rshares.value > 0 ? d.net_rshares.value : 0); u256 r2 = to256(vshares); //to256(abs_net_rshares); + r2 = (r2 * d.reward_weight) / STEEMIT_100_PERCENT; r2 *= pot.amount.value; r2 /= total_r2; + uint64_t payout = static_cast(r2); + + payout = std::min(payout, uint64_t(d.max_accepted_payout.amount.value)); + + uint128_t reward_tokens = uint128_t(payout); + + share_type curation_tokens = ((reward_tokens * db.get_curation_rewards_percent()) + / STEEMIT_100_PERCENT).to_uint64(); + auto crs_unclaimed = get_curator_unclaimed_rewards(d, curation_tokens); + auto crs_claim = curation_tokens - crs_unclaimed; + share_type author_tokens = reward_tokens.to_uint64() - crs_claim; + if (d.allow_curation_rewards) { + d.pending_curator_payout_value = db.to_sbd(asset(crs_claim, STEEM_SYMBOL)); + d.pending_curator_payout_gests_value = asset(crs_claim, STEEM_SYMBOL) * props.get_vesting_share_price(); + d.pending_payout_value += d.pending_curator_payout_value; + } + + uint32_t benefactor_weights = 0; + for (auto &b : d.beneficiaries) { + benefactor_weights += b.weight; + } + if (benefactor_weights != 0) { + auto total_beneficiary = (author_tokens * benefactor_weights) / STEEMIT_100_PERCENT; + author_tokens -= total_beneficiary; + d.pending_benefactor_payout_value = db.to_sbd(asset(total_beneficiary, STEEM_SYMBOL)); + d.pending_benefactor_payout_gests_value = (asset(total_beneficiary, STEEM_SYMBOL) * props.get_vesting_share_price()); + d.pending_payout_value += d.pending_benefactor_payout_value; + } + + auto sbd_steem = (author_tokens * d.percent_steem_dollars) / (2 * STEEMIT_100_PERCENT); + auto vesting_steem = asset(author_tokens - sbd_steem, STEEM_SYMBOL); + d.pending_author_payout_gests_value = vesting_steem * props.get_vesting_share_price(); + auto to_sbd = asset((props.sbd_print_rate * sbd_steem) / STEEMIT_100_PERCENT, STEEM_SYMBOL); + auto to_steem = asset(sbd_steem, STEEM_SYMBOL) - to_sbd; + + d.pending_author_payout_golos_value = to_steem; + d.pending_author_payout_gbg_value = db.to_sbd(to_sbd); + d.pending_author_payout_value = d.pending_author_payout_gbg_value + db.to_sbd(to_steem + vesting_steem); + d.pending_payout_value += d.pending_author_payout_value; + + // End of main calculation + u256 tpp = to256(d.children_rshares2); tpp *= pot.amount.value; tpp /= total_r2; - d.pending_payout_value = asset(static_cast(r2), pot.symbol); - d.total_pending_payout_value = asset(static_cast(tpp), pot.symbol); + d.total_pending_payout_value = db.to_sbd(asset(static_cast(tpp), pot.symbol)); } fill_reputation_(db, d.author, d.author_reputation); if (d.parent_author != STEEMIT_ROOT_POST_PARENT) { - d.cashout_time = db.calculate_discussion_payout_time(db.get(d.id)); + d.cashout_time = db.calculate_discussion_payout_time(db.get_comment(d.id)); } if (d.body.size() > 1024 * 128) { @@ -191,12 +296,11 @@ namespace golos { namespace api { // // set_url void discussion_helper::impl::set_url(discussion& d) const { - const comment_api_object root(database().get(d.root_comment), database()); + comment_object cm = database().get(d.root_comment); - d.root_title = root.title; - d.url = "/" + root.category + "/@" + root.author + "/" + root.permlink; + d.url = "/" + d.category + "/@" + std::string(cm.author) + "/" + to_string(cm.permlink); - if (root.id != d.id) { + if (cm.id != d.id) { d.url += "#@" + d.author + "/" + d.permlink; } } @@ -209,11 +313,15 @@ namespace golos { namespace api { discussion discussion_helper::impl::create_discussion(const std::string& author) const { auto dis = discussion(); fill_reputation_(database_, author, dis.author_reputation); + dis.active = time_point_sec::min(); + dis.last_update = time_point_sec::min(); return dis; } discussion discussion_helper::impl::create_discussion(const comment_object& o) const { - return discussion(o, database_); + discussion d; + fill_comment_api_object(o, d); + return d; } discussion discussion_helper::create_discussion(const std::string& author) const { @@ -227,9 +335,10 @@ namespace golos { namespace api { discussion_helper::discussion_helper( golos::chain::database& db, std::function&)> fill_reputation, - std::function fill_promoted + std::function fill_promoted, + std::function fill_comment_info ) { - pimpl = std::make_unique(db, fill_reputation, fill_promoted); + pimpl = std::make_unique(db, fill_reputation, fill_promoted, fill_comment_info); } discussion_helper::~discussion_helper() = default; diff --git a/libraries/api/include/golos/api/comment_api_object.hpp b/libraries/api/include/golos/api/comment_api_object.hpp index bafe4e68cb..10d0bc2336 100644 --- a/libraries/api/include/golos/api/comment_api_object.hpp +++ b/libraries/api/include/golos/api/comment_api_object.hpp @@ -8,11 +8,9 @@ namespace golos { namespace api { using namespace golos::chain; + using namespace golos::protocol; struct comment_api_object { - comment_api_object(const comment_object &o, const database &db); - comment_api_object(); - comment_object::id_type id; std::string title; @@ -26,9 +24,9 @@ namespace golos { namespace api { std::string category; - time_point_sec last_update; + fc::optional last_update; time_point_sec created; - time_point_sec active; + fc::optional active; time_point_sec last_payout; uint8_t depth = 0; @@ -47,16 +45,24 @@ namespace golos { namespace api { uint16_t reward_weight = 0; - protocol::asset total_payout_value; - protocol::asset curator_payout_value; + asset total_payout_value = asset(0, SBD_SYMBOL); + asset beneficiary_payout_value = asset(0, SBD_SYMBOL); + asset beneficiary_gests_payout_value = asset(0, VESTS_SYMBOL); + asset curator_payout_value = asset(0, SBD_SYMBOL); + asset curator_gests_payout_value = asset(0, VESTS_SYMBOL); share_type author_rewards; + asset author_gbg_payout_value = asset(0, SBD_SYMBOL); + asset author_golos_payout_value = asset(0, STEEM_SYMBOL); + asset author_gests_payout_value = asset(0, VESTS_SYMBOL); int32_t net_votes = 0; comment_mode mode = not_set; comment_object::id_type root_comment; + + string root_title; protocol::asset max_accepted_payout; uint16_t percent_steem_dollars = 0; @@ -74,8 +80,9 @@ FC_REFLECT( (id)(author)(permlink)(parent_author)(parent_permlink)(category)(title)(body)(json_metadata)(last_update) (created)(active)(last_payout)(depth)(children)(children_rshares2)(net_rshares)(abs_rshares) (vote_rshares)(children_abs_rshares)(cashout_time)(max_cashout_time)(total_vote_weight) - (reward_weight)(total_payout_value)(curator_payout_value)(author_rewards)(net_votes) - (mode)(root_comment)(max_accepted_payout)(percent_steem_dollars)(allow_replies)(allow_votes) + (reward_weight)(total_payout_value)(beneficiary_payout_value)(beneficiary_gests_payout_value)(curator_payout_value)(curator_gests_payout_value) + (author_rewards)(author_gbg_payout_value)(author_golos_payout_value)(author_gests_payout_value)(net_votes) + (mode)(root_comment)(root_title)(max_accepted_payout)(percent_steem_dollars)(allow_replies)(allow_votes) (allow_curation_rewards)(beneficiaries)) #endif //GOLOS_COMMENT_API_OBJ_H diff --git a/libraries/api/include/golos/api/discussion.hpp b/libraries/api/include/golos/api/discussion.hpp index ae43ded569..583ffe87e0 100644 --- a/libraries/api/include/golos/api/discussion.hpp +++ b/libraries/api/include/golos/api/discussion.hpp @@ -7,17 +7,27 @@ namespace golos { namespace api { struct discussion : public comment_api_object { - discussion(const comment_object& o, const golos::chain::database &db) - : comment_api_object(o, db) { + discussion(const comment_api_object& o) + : comment_api_object(o) { + } discussion() { } string url; /// /category/@rootauthor/root_permlink#author/permlink - string root_title; + + asset pending_author_payout_value = asset(0, SBD_SYMBOL); + asset pending_author_payout_gbg_value = asset(0, SBD_SYMBOL); + asset pending_author_payout_gests_value = asset(0, VESTS_SYMBOL); + asset pending_author_payout_golos_value = asset(0, STEEM_SYMBOL); + asset pending_benefactor_payout_value = asset(0, SBD_SYMBOL); + asset pending_benefactor_payout_gests_value = asset(0, VESTS_SYMBOL); + asset pending_curator_payout_value = asset(0, SBD_SYMBOL); + asset pending_curator_payout_gests_value = asset(0, VESTS_SYMBOL); asset pending_payout_value = asset(0, SBD_SYMBOL); ///< sbd asset total_pending_payout_value = asset(0, SBD_SYMBOL); ///< sbd including replies + std::vector active_votes; uint32_t active_votes_count = 0; std::vector replies; ///< author/slug mapping @@ -34,5 +44,9 @@ namespace golos { namespace api { } } // golos::api FC_REFLECT_DERIVED( (golos::api::discussion), ((golos::api::comment_api_object)), - (url)(root_title)(pending_payout_value)(total_pending_payout_value)(active_votes)(active_votes_count)(replies) + (url)(pending_author_payout_value)(pending_author_payout_gbg_value) + (pending_author_payout_gests_value)(pending_author_payout_golos_value) + (pending_benefactor_payout_value)(pending_benefactor_payout_gests_value) + (pending_curator_payout_value)(pending_curator_payout_gests_value) + (pending_payout_value)(total_pending_payout_value)(active_votes)(active_votes_count)(replies) (author_reputation)(promoted)(body_length)(reblogged_by)(first_reblogged_by)(first_reblogged_on)) diff --git a/libraries/api/include/golos/api/discussion_helper.hpp b/libraries/api/include/golos/api/discussion_helper.hpp index de42a47e17..e7058aa3c5 100644 --- a/libraries/api/include/golos/api/discussion_helper.hpp +++ b/libraries/api/include/golos/api/discussion_helper.hpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace golos { namespace api { struct comment_metadata { @@ -9,15 +10,15 @@ namespace golos { namespace api { std::string language; }; - comment_metadata get_metadata(const comment_api_object &c); - class discussion_helper { public: discussion_helper() = delete; discussion_helper( golos::chain::database& db, std::function&)> fill_reputation, - std::function fill_promoted); + std::function fill_promoted, + std::function fill_comment_info + ); ~discussion_helper(); @@ -36,6 +37,11 @@ namespace golos { namespace api { discussion get_discussion(const comment_object& c, uint32_t vote_limit) const; + comment_api_object create_comment_api_object(const comment_object& o) const; + + void fill_comment_api_object(const comment_object& o, comment_api_object& d) const; + + private: struct impl; std::unique_ptr pimpl; diff --git a/libraries/chain/block_log.cpp b/libraries/chain/block_log.cpp index 6b5bc11908..751d6b7335 100644 --- a/libraries/chain/block_log.cpp +++ b/libraries/chain/block_log.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,11 @@ namespace golos { namespace chain { uint64_t get_uint64(const boost::iostreams::mapped_file& mapped_file, std::size_t pos) const { uint64_t value; - FC_ASSERT(get_mapped_size(mapped_file) >= pos + sizeof(value)); + auto file_size = get_mapped_size(mapped_file); + GOLOS_CHECK_DATABASE(pos + sizeof(value) <= file_size, + database_corrupted::reading_data_beyond_end_of_file, + "Reading data beyond end of file", + ("pos", pos)("size", sizeof(value))("file_size", file_size)); auto* ptr = mapped_file.data() + pos; value = *reinterpret_cast(ptr); @@ -52,10 +57,13 @@ namespace golos { namespace chain { uint64_t get_last_uint64(const boost::iostreams::mapped_file& mapped_file) const { uint64_t value; - auto size = get_mapped_size(mapped_file); - FC_ASSERT(size >= sizeof(value)); + auto file_size = get_mapped_size(mapped_file); + GOLOS_CHECK_DATABASE(sizeof(value) <= file_size, + database_corrupted::reading_data_beyond_end_of_file, + "Reading data beyond end of file", + ("size", sizeof(value))("file_size", file_size)); - auto* ptr = mapped_file.data() + size - sizeof(value); + auto* ptr = mapped_file.data() + file_size - sizeof(value); value = *reinterpret_cast(ptr); return value; } @@ -72,7 +80,10 @@ namespace golos { namespace chain { uint64_t read_block(uint64_t pos, signed_block& block) const { const auto file_size = get_mapped_size(block_mapped_file); - FC_ASSERT(file_size > pos); + GOLOS_CHECK_DATABASE(pos < file_size, + database_corrupted::reading_data_beyond_end_of_file, + "Reading data beyond end of file", + ("pos", pos)("file_size", file_size)); const auto* ptr = block_mapped_file.data() + pos; const auto available_size = file_size - pos; @@ -82,7 +93,11 @@ namespace golos { namespace chain { fc::raw::unpack(ds, block); const auto end_pos = pos + ds.tellp(); - FC_ASSERT(get_uint64(block_mapped_file, end_pos) == pos); + const auto block_pos = get_uint64(block_mapped_file, end_pos); + GOLOS_CHECK_DATABASE(block_pos == pos, + database_corrupted::wrong_position_marker_was_read, + "Wrong position makers was read (read ${block_pos}, expected ${expected})", + ("block_pos", block_pos)("expected", pos)); return end_pos + sizeof(uint64_t); } @@ -195,8 +210,8 @@ namespace golos { namespace chain { uint64_t append(const signed_block& b, const std::vector& data) { try { const auto index_pos = get_mapped_size(index_mapped_file); - FC_ASSERT( - index_pos == sizeof(uint64_t) * (b.block_num() - 1), + GOLOS_CHECK_DATABASE(index_pos == sizeof(uint64_t) * (b.block_num() - 1), + database_corrupted::append_index_file_at_wrong_position, "Append to index file occuring at wrong position.", ("position", index_pos) ("expected", (b.block_num() - 1) * sizeof(uint64_t))); @@ -274,11 +289,10 @@ namespace golos { namespace chain { if (pos != npos) { signed_block block; my->read_block(pos, block); - FC_ASSERT( - block.block_num() == block_num, - "Wrong block was read from block log (${returned} != ${expected}).", - ("returned", block.block_num()) - ("expected", block_num)); + GOLOS_CHECK_DATABASE(block.block_num() == block_num, + database_corrupted::wrong_block_num_was_read, + "Wrong block was read from block log (read ${block_num}, expected ${expected}).", + ("block_num", block.block_num())("expected", block_num)); result = std::move(block); } return result; diff --git a/libraries/chain/chain_properties_evaluators.cpp b/libraries/chain/chain_properties_evaluators.cpp index 073505ad62..0d0cca5e91 100644 --- a/libraries/chain/chain_properties_evaluators.cpp +++ b/libraries/chain/chain_properties_evaluators.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace golos { namespace chain { @@ -8,53 +9,33 @@ namespace golos { namespace chain { _db.get_account(o.owner); // verify owner exists if (_db.has_hardfork(STEEMIT_HARDFORK_0_1)) { - FC_ASSERT(o.url.size() <= STEEMIT_MAX_WITNESS_URL_LENGTH, "URL is too long"); + GOLOS_CHECK_OP_PARAM(o, url, { + GOLOS_CHECK_VALUE_MAX_SIZE(o.url, STEEMIT_MAX_WITNESS_URL_LENGTH); + }); } else if (o.url.size() > STEEMIT_MAX_WITNESS_URL_LENGTH) { // after HF, above check can be moved to validate() if reindex doesn't show this warning wlog("URL is too long in block ${b}", ("b", _db.head_block_num() + 1)); } - if (_db.has_hardfork(STEEMIT_HARDFORK_0_14__410)) { - FC_ASSERT(o.props.account_creation_fee.symbol == STEEM_SYMBOL); - } else if (o.props.account_creation_fee.symbol != STEEM_SYMBOL) { - // after HF, above check can be moved to validate() if reindex doesn't show this warning - wlog("Wrong fee symbol in block ${b}", ("b", _db.head_block_num() + 1)); - } - const bool has_hf18 = _db.has_hardfork(STEEMIT_HARDFORK_0_18__673); - // TODO: remove this after HF 18 - if (has_hf18) { - if (o.props.account_creation_fee.amount.value != STEEMIT_MIN_ACCOUNT_CREATION_FEE) { - wlog("The chain_properties_update_operation should be used to update account_creation_fee"); - } - if (o.props.sbd_interest_rate != STEEMIT_DEFAULT_SBD_INTEREST_RATE) { - wlog("The chain_properties_update_operation should be used to update sbd_interest_rate"); + auto update_witness = [&](witness_object& w) { + from_string(w.url, o.url); + w.signing_key = o.block_signing_key; + if (!has_hf18) { + w.props = o.props; } - if (o.props.maximum_block_size != STEEMIT_MIN_BLOCK_SIZE_LIMIT * 2) { - wlog("The chain_properties_update_operation should be used to update maximum_block_size"); - } - } + }; - const auto &idx = _db.get_index().indices().get(); + const auto& idx = _db.get_index().indices().get(); auto itr = idx.find(o.owner); if (itr != idx.end()) { - _db.modify(*itr, [&](witness_object& w) { - from_string(w.url, o.url); - w.signing_key = o.block_signing_key; - if (!has_hf18) { - w.props = o.props; - } - }); + _db.modify(*itr, update_witness); } else { _db.create([&](witness_object& w) { w.owner = o.owner; - from_string(w.url, o.url); - w.signing_key = o.block_signing_key; w.created = _db.head_block_time(); - if (!has_hf18) { - w.props = o.props; - } + update_witness(w); }); } } @@ -62,7 +43,7 @@ namespace golos { namespace chain { struct chain_properties_convert { using result_type = chain_properties_18; - template + template result_type operator()(Props&& p) const { result_type r; r = p; @@ -71,10 +52,9 @@ namespace golos { namespace chain { }; void chain_properties_update_evaluator::do_apply(const chain_properties_update_operation& o) { - ASSERT_REQ_HF(STEEMIT_HARDFORK_0_18__673, "Chain properties"); // remove after hf _db.get_account(o.owner); // verify owner exists - const auto &idx = _db.get_index().indices().get(); + const auto& idx = _db.get_index().indices().get(); auto itr = idx.find(o.owner); if (itr != idx.end()) { _db.modify(*itr, [&](witness_object& w) { @@ -89,4 +69,4 @@ namespace golos { namespace chain { } } -} } // golos::chain \ No newline at end of file +} } // golos::chain diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index c1690e3659..3da8706bde 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -344,12 +344,29 @@ namespace golos { namespace chain { _block_num_check_free_memory = value; } - void database::set_clear_votes(uint32_t clear_votes_block) { - _clear_votes_block = clear_votes_block; + + void database::set_store_account_metadata(store_metadata_modes store_account_metadata) { + _store_account_metadata = store_account_metadata; } - bool database::clear_votes() { - return _clear_votes_block > head_block_num(); + void database::set_accounts_to_store_metadata(const std::vector& accounts_to_store_metadata) { + _accounts_to_store_metadata = accounts_to_store_metadata; + } + + bool database::store_metadata_for_account(const std::string& name) const { + if (_store_account_metadata != store_metadata_for_listed) { + return _store_account_metadata == store_metadata_for_all; + } + auto& v = _accounts_to_store_metadata; + return std::find(v.begin(), v.end(), name) != v.end(); + } + + void database::set_store_memo_in_savings_withdraws(bool store_memo_in_savings_withdraws) { + _store_memo_in_savings_withdraws = store_memo_in_savings_withdraws; + } + + bool database::store_memo_in_savings_withdraws() const { + return _store_memo_in_savings_withdraws; } void database::set_skip_virtual_ops() { @@ -413,7 +430,7 @@ namespace golos { namespace chain { if (free_gb == 0) { uint32_t free_mb = uint32_t(free_mem / (1024 * 1024)); - if (free_mb <= 500 && current_block_num % 10 == 0) { + if (free_mb <= (_is_testing ? 2 : 500) && current_block_num % 10 == 0) { elog("Free memory is now ${n}M. Increase shared file size immediately!", ("n", free_mb)); } } @@ -574,9 +591,23 @@ namespace golos { namespace chain { return STEEMIT_CHAIN_ID; } + void database::throw_if_exists_limit_order(const account_name_type& owner, uint32_t id) const { + if (nullptr != find_limit_order(owner, id)) { + GOLOS_THROW_OBJECT_ALREADY_EXIST("limit_order", fc::mutable_variant_object()("account",owner)("order_id",id)); + } + } + + void database::throw_if_exists_account(const account_name_type& account) const { + if (nullptr != find_account(account)) { + GOLOS_THROW_OBJECT_ALREADY_EXIST("account", account); + } + } + const witness_object &database::get_witness(const account_name_type &name) const { try { return get(name); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("witness", name); } FC_CAPTURE_AND_RETHROW((name)) } @@ -587,7 +618,10 @@ namespace golos { namespace chain { const account_object &database::get_account(const account_name_type &name) const { try { return get(name); - } FC_CAPTURE_AND_RETHROW((name)) + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("account", name); + } + FC_CAPTURE_AND_RETHROW((name)) } const account_object *database::find_account(const account_name_type &name) const { @@ -597,6 +631,8 @@ namespace golos { namespace chain { const comment_object &database::get_comment(const account_name_type &author, const shared_string &permlink) const { try { return get(boost::make_tuple(author, permlink)); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("comment", fc::mutable_variant_object()("account", author)("permlink", permlink)); } FC_CAPTURE_AND_RETHROW((author)(permlink)) } @@ -607,6 +643,8 @@ namespace golos { namespace chain { const comment_object &database::get_comment(const account_name_type &author, const string &permlink) const { try { return get(boost::make_tuple(author, permlink)); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("comment", fc::mutable_variant_object()("account", author)("permlink", permlink)); } FC_CAPTURE_AND_RETHROW((author)(permlink)) } @@ -614,20 +652,19 @@ namespace golos { namespace chain { return find(boost::make_tuple(author, permlink)); } - - const comment_content_object &database::get_comment_content(const comment_id_type &comment) const { + const comment_object &database::get_comment(const comment_id_type &comment_id) const { try { - return get(comment); - } FC_CAPTURE_AND_RETHROW((comment)) - } - - const comment_content_object *database::find_comment_content(const comment_id_type &comment) const { - return find(comment); + return get(comment_id); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("comment", comment_id); + } FC_CAPTURE_AND_RETHROW((comment_id)) } const escrow_object &database::get_escrow(const account_name_type &name, uint32_t escrow_id) const { try { return get(boost::make_tuple(name, escrow_id)); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("escrow", fc::mutable_variant_object()("account", name)("escrow", escrow_id)); } FC_CAPTURE_AND_RETHROW((name)(escrow_id)) } @@ -642,6 +679,8 @@ namespace golos { namespace chain { } return get(boost::make_tuple(name, orderid)); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("limit_order", fc::mutable_variant_object()("account",name)("order_id", orderid)); } FC_CAPTURE_AND_RETHROW((name)(orderid)) } @@ -653,37 +692,88 @@ namespace golos { namespace chain { return find(boost::make_tuple(name, orderid)); } - const savings_withdraw_object &database::get_savings_withdraw(const account_name_type &owner, uint32_t request_id) const { + const convert_request_object& database::get_convert_request(const account_name_type& name, uint32_t id) const { + try { + return get(boost::make_tuple(name, id)); + } catch(const std::out_of_range& e) { + GOLOS_THROW_MISSING_OBJECT("convert_request", fc::mutable_variant_object()("account",name)("request_id", id)); + } FC_CAPTURE_AND_RETHROW((name)(id)) + } + + const convert_request_object* database::find_convert_request(const account_name_type& name, uint32_t id) const { + return find(boost::make_tuple(name, id)); + } + + void database::throw_if_exists_convert_request(const account_name_type &name, uint32_t id) const { + if (nullptr != find_convert_request(name, id)) { + GOLOS_THROW_OBJECT_ALREADY_EXIST("convert_request", fc::mutable_variant_object()("account",name)("request_id",id)); + } + } + + const savings_withdraw_object& database::get_savings_withdraw(const account_name_type& owner, uint32_t request_id) const { try { return get(boost::make_tuple(owner, request_id)); + } catch(const std::out_of_range& e) { + GOLOS_THROW_MISSING_OBJECT("savings_withdraw", fc::mutable_variant_object()("account",owner)("request_id", request_id)); } FC_CAPTURE_AND_RETHROW((owner)(request_id)) } - const savings_withdraw_object *database::find_savings_withdraw(const account_name_type &owner, uint32_t request_id) const { + const account_authority_object &database::get_authority(const account_name_type &name) const { + try { + return get(name); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("authority", name); + } FC_CAPTURE_AND_RETHROW((name)) + } + + const savings_withdraw_object* database::find_savings_withdraw(const account_name_type& owner, uint32_t request_id) const { return find(boost::make_tuple(owner, request_id)); } - const dynamic_global_property_object &database::get_dynamic_global_properties() const { + const dynamic_global_property_object& database::get_dynamic_global_properties() const { try { return get(); + } catch(const std::out_of_range& e) { + GOLOS_THROW_INTERNAL_ERROR("Missing dynamic_global_properties"); } FC_CAPTURE_AND_RETHROW() } + void database::throw_if_exists_savings_withdraw(const account_name_type& owner, uint32_t request_id) const { + if (nullptr != find_savings_withdraw(owner, request_id)) { + GOLOS_THROW_OBJECT_ALREADY_EXIST("savings_withdraw", + fc::mutable_variant_object()("owner",owner)("request_id",request_id)); + } + } + const feed_history_object &database::get_feed_history() const { try { return get(); + } catch(const std::out_of_range &e) { + GOLOS_THROW_INTERNAL_ERROR("Missing feed_history"); } FC_CAPTURE_AND_RETHROW() } const witness_schedule_object &database::get_witness_schedule_object() const { try { return get(); + } catch(const std::out_of_range &e) { + GOLOS_THROW_INTERNAL_ERROR("Missing witness_schedule"); } FC_CAPTURE_AND_RETHROW() } const hardfork_property_object &database::get_hardfork_property_object() const { try { return get(); + } catch(const std::out_of_range &e) { + GOLOS_THROW_INTERNAL_ERROR("Missing hardfork_property"); + } FC_CAPTURE_AND_RETHROW() + } + + const block_summary_object &database::get_block_summary(const block_summary_id_type &ref_block_num) const { + try { + return get(ref_block_num); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("block_summary", ref_block_num); } FC_CAPTURE_AND_RETHROW() } @@ -691,13 +781,12 @@ namespace golos { namespace chain { if (has_hardfork(STEEMIT_HARDFORK_0_17__431) || comment.parent_author == STEEMIT_ROOT_POST_PARENT) { return comment.cashout_time; } else { - return get(comment.root_comment).cashout_time; + return get_comment(comment.root_comment).cashout_time; } } void database::pay_fee(const account_object &account, asset fee) { - FC_ASSERT(fee.amount >= - 0); /// NOTE if this fails then validate() on some operation is probably wrong + FC_ASSERT(fee.amount >= 0); /// NOTE if this fails then validate() on some operation is probably wrong if (fee.amount == 0) { return; } @@ -750,7 +839,9 @@ namespace golos { namespace chain { has_bandwidth = (account_vshares * max_virtual_bandwidth) > (account_average_bandwidth * total_vshares); if (is_producing()) - FC_ASSERT(has_bandwidth, "Account exceeded maximum allowed bandwidth per vesting share.", + GOLOS_CHECK_LOGIC(has_bandwidth, + logic_exception::account_exceeded_bandwidth_per_vestring_share, + "Account exceeded maximum allowed bandwidth per vesting share.", ("account_vshares", account_vshares) ("account_average_bandwidth", account_average_bandwidth) ("max_virtual_bandwidth", max_virtual_bandwidth) @@ -979,7 +1070,9 @@ namespace golos { namespace chain { */ void database::push_transaction(const signed_transaction &trx, uint32_t skip) { try { - FC_ASSERT(fc::raw::pack_size(trx) <= (get_dynamic_global_properties().maximum_block_size - 256)); + GOLOS_ASSERT(fc::raw::pack_size(trx) <= (get_dynamic_global_properties().maximum_block_size - 256), + golos::protocol::tx_too_long, "Transaction data is too long. Maximum transaction size ${max} bytes", + ("max",get_dynamic_global_properties().maximum_block_size - 256)); with_weak_write_lock([&]() { detail::with_producing(*this, [&]() { _push_transaction(trx, skip); @@ -1489,13 +1582,14 @@ namespace golos { namespace chain { new_virtual_time = fc::uint128_t(); reset_virtual_schedule_time(); } - +#ifndef STEEMIT_BUILD_TESTNET size_t expected_active_witnesses = std::min(size_t(STEEMIT_MAX_WITNESSES), widx.size()); if (head_block_num() > 14400) { FC_ASSERT(active_witnesses.size() == expected_active_witnesses, "number of active witnesses does not equal expected_active_witnesses=${expected_active_witnesses}", ("active_witnesses.size()", active_witnesses.size())("STEEMIT_MAX_WITNESSES", STEEMIT_MAX_WITNESSES)("expected_active_witnesses", expected_active_witnesses)); } +#endif auto majority_version = wso.majority_version; @@ -1999,12 +2093,12 @@ namespace golos { namespace chain { STEEMIT_OWNER_AUTH_HISTORY_TRACKING_START_BLOCK_NUM) { create([&](owner_authority_history_object &hist) { hist.account = account.name; - hist.previous_owner_authority = get(account.name).owner; + hist.previous_owner_authority = get_authority(account.name).owner; hist.last_valid_time = head_block_time(); }); } - modify(get(account.name), [&](account_authority_object &auth) { + modify(get_authority(account.name), [&](account_authority_object &auth) { auth.owner = owner_authority; auth.last_owner_update = head_block_time(); }); @@ -2144,22 +2238,6 @@ namespace golos { namespace chain { } } - void database::adjust_total_payout( - const comment_object &cur, - const asset &sbd_created, - const asset &curator_sbd_value, - const asset &beneficiary_value - ) { - modify(cur, [&](comment_object &c) { - if (c.total_payout_value.symbol == sbd_created.symbol) { - c.total_payout_value += sbd_created; - c.beneficiary_payout_value += beneficiary_value; - c.curator_payout_value += curator_sbd_value; - } - }); - /// TODO: potentially modify author's total payout numbers as well - } - /** * This method will iterate through all comment_vote_objects and give them * (max_rewards * weight) / c.total_vote_weight. @@ -2182,16 +2260,17 @@ namespace golos { namespace chain { if (claim > 0) // min_amt is non-zero satoshis { unclaimed_rewards -= claim; + const auto &voter = get(itr->voter); auto reward = create_vesting(voter, asset(claim, STEEM_SYMBOL)); push_virtual_operation(curation_reward_operation(voter.name, reward, c.author, to_string(c.permlink))); -#ifndef IS_LOW_MEM modify(voter, [&](account_object &a) { a.curation_rewards += claim; }); -#endif + } else { + break; } ++itr; } @@ -2241,44 +2320,22 @@ namespace golos { namespace chain { author_tokens -= total_beneficiary; - auto sbd_steem = (author_tokens * - comment.percent_steem_dollars) / - (2 * STEEMIT_100_PERCENT); + auto sbd_steem = (author_tokens * comment.percent_steem_dollars) / (2 * STEEMIT_100_PERCENT); auto vesting_steem = author_tokens - sbd_steem; const auto &author = get_account(comment.author); auto vest_created = create_vesting(author, vesting_steem); auto sbd_payout = create_sbd(author, sbd_steem); - adjust_total_payout( - comment, - sbd_payout.first + to_sbd(sbd_payout.second + asset(vesting_steem, STEEM_SYMBOL)), - to_sbd(asset(curation_tokens, STEEM_SYMBOL)), - to_sbd(asset(total_beneficiary, STEEM_SYMBOL)) - ); - - /*if( sbd_created.symbol == SBD_SYMBOL ) - adjust_total_payout( comment, sbd_created + to_sbd( asset( vesting_steem, STEEM_SYMBOL ) ), to_sbd( asset( reward_tokens.to_uint64() - author_tokens, STEEM_SYMBOL ) ) ); - else - adjust_total_payout( comment, to_sbd( asset( vesting_steem + sbd_steem, STEEM_SYMBOL ) ), to_sbd( asset( reward_tokens.to_uint64() - author_tokens, STEEM_SYMBOL ) ) ); - */ - // stats only.. TODO: Move to plugin... total_payout = to_sbd(asset(reward_tokens.to_uint64(), STEEM_SYMBOL)); push_virtual_operation(author_reward_operation(comment.author, to_string(comment.permlink), sbd_payout.first, sbd_payout.second, vest_created)); push_virtual_operation(comment_reward_operation(comment.author, to_string(comment.permlink), total_payout)); -#ifndef IS_LOW_MEM - modify(comment, [&](comment_object &c) { - c.author_rewards += author_tokens; - }); - modify(get_account(comment.author), [&](account_object &a) { a.posting_rewards += author_tokens; }); -#endif - } fc::uint128_t old_rshares2 = calculate_vshares(comment.net_rshares.value); @@ -2302,15 +2359,14 @@ namespace golos { namespace chain { if (has_hardfork(STEEMIT_HARDFORK_0_17__431)) { c.cashout_time = fc::time_point_sec::maximum(); } else if (c.parent_author == STEEMIT_ROOT_POST_PARENT) { - if (has_hardfork(STEEMIT_HARDFORK_0_12__177) && c.last_payout == fc::time_point_sec::min()) { + if (c.last_payout == fc::time_point_sec::min()) { c.cashout_time = head_block_time() + STEEMIT_SECOND_CASHOUT_WINDOW; } else { c.cashout_time = fc::time_point_sec::maximum(); } } - if (calculate_discussion_payout_time(c) == - fc::time_point_sec::maximum()) { + if (calculate_discussion_payout_time(c) == fc::time_point_sec::maximum()) { c.mode = archived; } else { c.mode = second_payout; @@ -2321,22 +2377,15 @@ namespace golos { namespace chain { push_virtual_operation(comment_payout_update_operation(comment.author, to_string(comment.permlink))); - const auto &vote_idx = get_index().indices().get(); - auto vote_itr = vote_idx.lower_bound(comment.id); - while (vote_itr != vote_idx.end() && - vote_itr->comment == comment.id) { - const auto &cur_vote = *vote_itr; - ++vote_itr; - if (!has_hardfork(STEEMIT_HARDFORK_0_12__177) || - calculate_discussion_payout_time(comment) != - fc::time_point_sec::maximum()) { - modify(cur_vote, [&](comment_vote_object &cvo) { - cvo.num_changes = -1; + if (comment.mode == archived) { + const auto& vote_idx = get_index().indices().get(); + auto vote_itr = vote_idx.lower_bound(comment.id); + while (vote_itr != vote_idx.end() && vote_itr->comment == comment.id) { + const auto& cur_vote = *vote_itr; + ++vote_itr; + modify(cur_vote, [&](comment_vote_object& cvo) { + cvo.num_changes = -1; // mark vote that it's ready to be removed (archived comment) }); - } else { - if(clear_votes()) { - remove(cur_vote); - } } } } FC_CAPTURE_AND_RETHROW() @@ -2387,8 +2436,11 @@ namespace golos { namespace chain { * This method pays out vesting, reward shares and witnesses every block. */ void database::process_funds() { - const auto &props = get_dynamic_global_properties(); - const auto &wso = get_witness_schedule_object(); + const auto& wso = get_witness_schedule_object(); + const auto& props = get_dynamic_global_properties(); + const auto& cwit = get_witness(props.current_witness); + const auto& wacc = get_account(cwit.owner); + optional producer_reward; if (has_hardfork(STEEMIT_HARDFORK_0_16__551)) { /** @@ -2415,8 +2467,6 @@ namespace golos { namespace chain { (new_steem * STEEMIT_VESTING_FUND_PERCENT) / STEEMIT_100_PERCENT; /// 26.67% to vesting fund auto witness_reward = new_steem - content_reward - vesting_reward; /// Remaining 6.66% to witness pay - - const auto &cwit = get_witness(props.current_witness); witness_reward *= STEEMIT_MAX_WITNESSES; if (cwit.schedule == witness_object::timeshare) { @@ -2440,13 +2490,21 @@ namespace golos { namespace chain { p.virtual_supply += asset(new_steem, STEEM_SYMBOL); }); - create_vesting(get_account(cwit.owner), asset(witness_reward, STEEM_SYMBOL)); + producer_reward = create_vesting(wacc, asset(witness_reward, STEEM_SYMBOL)); } else { + auto witness_pay = get_producer_reward(); + /// pay witness in vesting shares + if (props.head_block_number >= STEEMIT_START_MINER_VOTING_BLOCK || (wacc.vesting_shares.amount == 0)) { + producer_reward = create_vesting(wacc, witness_pay); + } else { + modify(wacc, [&](account_object &a) { + a.balance += witness_pay; + }); + } + auto content_reward = get_content_reward(); auto curate_reward = get_curation_reward(); - auto witness_pay = get_producer_reward(); auto vesting_reward = content_reward + curate_reward + witness_pay; - content_reward = content_reward + curate_reward; if (props.head_block_number < STEEMIT_START_VESTING_BLOCK) { @@ -2455,13 +2513,16 @@ namespace golos { namespace chain { vesting_reward.amount.value *= 9; } - modify(props, [&](dynamic_global_property_object &p) { + modify(props, [&](dynamic_global_property_object& p) { p.total_vesting_fund_steem += vesting_reward; p.total_reward_fund_steem += content_reward; p.current_supply += content_reward + witness_pay + vesting_reward; p.virtual_supply += content_reward + witness_pay + vesting_reward; }); } + + if (producer_reward) + push_virtual_operation(producer_reward_operation(wacc.name, *producer_reward)); } void database::process_savings_withdraws() { @@ -2522,47 +2583,15 @@ namespace golos { namespace chain { return reward; } - asset database::get_producer_reward() { - const auto &props = get_dynamic_global_properties(); - static_assert(STEEMIT_BLOCK_INTERVAL == - 3, "this code assumes a 3-second time interval"); + asset database::get_producer_reward() const { + static_assert(STEEMIT_BLOCK_INTERVAL == 3, "this code assumes a 3-second time interval"); + const auto& props = get_dynamic_global_properties(); asset percent(protocol::calc_percent_reward_per_block(props.virtual_supply.amount), STEEM_SYMBOL); - const auto &witness_account = get_account(props.current_witness); - - if (has_hardfork(STEEMIT_HARDFORK_0_16)) { - auto pay = std::max(percent, STEEMIT_MIN_PRODUCER_REWARD); - - /// pay witness in vesting shares - if (props.head_block_number >= - STEEMIT_START_MINER_VOTING_BLOCK || - (witness_account.vesting_shares.amount.value == 0)) { - // const auto& witness_obj = get_witness( props.current_witness ); - create_vesting(witness_account, pay); - } else { - modify(get_account(witness_account.name), [&](account_object &a) { - a.balance += pay; - }); - } - - return pay; - } else { - auto pay = std::max(percent, STEEMIT_MIN_PRODUCER_REWARD_PRE_HF_16); - - /// pay witness in vesting shares - if (props.head_block_number >= - STEEMIT_START_MINER_VOTING_BLOCK || - (witness_account.vesting_shares.amount.value == 0)) { - // const auto& witness_obj = get_witness( props.current_witness ); - create_vesting(witness_account, pay); - } else { - modify(get_account(witness_account.name), [&](account_object &a) { - a.balance += pay; - }); - } - - return pay; - } + // why this needed? the method called only before hf16 + auto min_reward = has_hardfork(STEEMIT_HARDFORK_0_16) ? STEEMIT_MIN_PRODUCER_REWARD : STEEMIT_MIN_PRODUCER_REWARD_PRE_HF_16; + auto pay = std::max(percent, min_reward); + return pay; } asset database::get_pow_reward() const { @@ -2754,36 +2783,34 @@ namespace golos { namespace chain { void database::account_recovery_processing() { // Clear expired recovery requests - const auto &rec_req_idx = get_index().indices().get(); + const auto& rec_req_idx = get_index().indices().get(); auto rec_req = rec_req_idx.begin(); - while (rec_req != rec_req_idx.end() && - rec_req->expires <= head_block_time()) { + while (rec_req != rec_req_idx.end() && rec_req->expires <= head_block_time()) { remove(*rec_req); rec_req = rec_req_idx.begin(); } // Clear invalid historical authorities - const auto &hist_idx = get_index().indices(); //by id + const auto& hist_idx = get_index().indices(); //by id auto hist = hist_idx.begin(); - while (hist != hist_idx.end() && time_point_sec( - hist->last_valid_time + - STEEMIT_OWNER_AUTH_RECOVERY_PERIOD) < head_block_time()) { + while ( + hist != hist_idx.end() && + time_point_sec(hist->last_valid_time + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD) < head_block_time() + ) { remove(*hist); hist = hist_idx.begin(); } // Apply effective recovery_account changes - const auto &change_req_idx = get_index().indices().get(); + const auto& change_req_idx = get_index().indices().get(); auto change_req = change_req_idx.begin(); - while (change_req != change_req_idx.end() && - change_req->effective_on <= head_block_time()) { - modify(get_account(change_req->account_to_recover), [&](account_object &a) { + while (change_req != change_req_idx.end() && change_req->effective_on <= head_block_time()) { + modify(get_account(change_req->account_to_recover), [&](account_object& a) { a.recovery_account = change_req->recovery_account; }); - remove(*change_req); change_req = change_req_idx.begin(); } @@ -2926,7 +2953,6 @@ namespace golos { namespace chain { add_core_index(*this); add_core_index(*this); add_core_index(*this); - add_core_index(*this); add_core_index(*this); add_core_index(*this); add_core_index(*this); @@ -3022,11 +3048,13 @@ namespace golos { namespace chain { create([&](account_object &a) { a.name = STEEMIT_MINER_ACCOUNT; }); -#ifndef IS_LOW_MEM - create([&](account_metadata_object& m) { - m.account = STEEMIT_MINER_ACCOUNT; - }); -#endif + + if (store_metadata_for_account(STEEMIT_MINER_ACCOUNT)) { + create([&](account_metadata_object& m) { + m.account = STEEMIT_MINER_ACCOUNT; + }); + } + create([&](account_authority_object &auth) { auth.account = STEEMIT_MINER_ACCOUNT; auth.owner.weight_threshold = 1; @@ -3036,11 +3064,13 @@ namespace golos { namespace chain { create([&](account_object &a) { a.name = STEEMIT_NULL_ACCOUNT; }); -#ifndef IS_LOW_MEM - create([&](account_metadata_object& m) { - m.account = STEEMIT_NULL_ACCOUNT; - }); -#endif + + if (store_metadata_for_account(STEEMIT_NULL_ACCOUNT)) { + create([&](account_metadata_object& m) { + m.account = STEEMIT_NULL_ACCOUNT; + }); + } + create([&](account_authority_object &auth) { auth.account = STEEMIT_NULL_ACCOUNT; auth.owner.weight_threshold = 1; @@ -3050,11 +3080,13 @@ namespace golos { namespace chain { create([&](account_object &a) { a.name = STEEMIT_TEMP_ACCOUNT; }); -#ifndef IS_LOW_MEM - create([&](account_metadata_object& m) { - m.account = STEEMIT_TEMP_ACCOUNT; - }); -#endif + + if (store_metadata_for_account(STEEMIT_TEMP_ACCOUNT)) { + create([&](account_metadata_object& m) { + m.account = STEEMIT_TEMP_ACCOUNT; + }); + } + create([&](account_authority_object &auth) { auth.account = STEEMIT_TEMP_ACCOUNT; auth.owner.weight_threshold = 0; @@ -3068,11 +3100,13 @@ namespace golos { namespace chain { a.memo_key = init_public_key; a.balance = asset(i ? 0 : init_supply, STEEM_SYMBOL); }); -#ifndef IS_LOW_MEM - create([&](account_metadata_object& m) { - m.account = name; - }); -#endif + + if (store_metadata_for_account(name)) { + create([&](account_metadata_object& m) { + m.account = name; + }); + } + create([&](account_authority_object &auth) { auth.account = name; auth.owner.add_authority(init_public_key, 1); @@ -3131,12 +3165,14 @@ namespace golos { namespace chain { a.memo_key = account.keys.memo_key; a.recovery_account = STEEMIT_INIT_MINER_NAME; }); -#ifndef IS_LOW_MEM - create([&](account_metadata_object& m) { - m.account = account.name; - m.json_metadata = "{created_at: 'GENESIS'}"; - }); -#endif + + if (store_metadata_for_account(account.name)) { + create([&](account_metadata_object& m) { + m.account = account.name; + m.json_metadata = "{created_at: 'GENESIS'}"; + }); + } + create([&](account_authority_object& auth) { auth.account = account.name; auth.owner.weight_threshold = 1; @@ -3221,15 +3257,15 @@ namespace golos { namespace chain { const chain_id_type &chain_id = STEEMIT_CHAIN_ID; auto get_active = [&](const account_name_type& name) { - return authority(get(name).active); + return authority(get_authority(name).active); }; auto get_owner = [&](const account_name_type& name) { - return authority(get(name).owner); + return authority(get_authority(name).owner); }; auto get_posting = [&](const account_name_type& name) { - return authority(get(name).posting); + return authority(get_authority(name).posting); }; try { @@ -3246,28 +3282,37 @@ namespace golos { namespace chain { // It's impossible that the transaction is expired, and TaPoS makes no sense as no blocks exist. if (BOOST_LIKELY(head_block_num() > 0)) { if (!(skip & skip_tapos_check)) { - const auto &tapos_block_summary = get(trx.ref_block_num); + const auto &tapos_block_summary = get_block_summary(trx.ref_block_num); //Verify TaPoS block summary has correct ID prefix, // and that this block's time is not past the expiration - FC_ASSERT( - trx.ref_block_prefix == tapos_block_summary.block_id._hash[1], "", + GOLOS_ASSERT(trx.ref_block_prefix == tapos_block_summary.block_id._hash[1], + tx_invalid_field, "Transaction field ${field} has invalid value", + ("field","ref_block_prefix") ("trx.ref_block_prefix", trx.ref_block_prefix) ("tapos_block_summary", tapos_block_summary.block_id._hash[1])); } fc::time_point_sec now = head_block_time(); + fc::time_point_sec maximum = now + fc::seconds(STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - FC_ASSERT( - trx.expiration <= now + fc::seconds(STEEMIT_MAX_TIME_UNTIL_EXPIRATION), "", - ("trx.expiration", trx.expiration)("now", now) - ("max_til_exp", STEEMIT_MAX_TIME_UNTIL_EXPIRATION)); + GOLOS_ASSERT(trx.expiration <= maximum, + tx_invalid_field, "Transaction field ${field} has invalid value", + ("field", "expiration") + ("trx.expiration", trx.expiration) + ("now", now)("maximum", maximum)); // Simple solution to pending trx bug when now == trx.expiration if (is_producing() || has_hardfork(STEEMIT_HARDFORK_0_9)) { - FC_ASSERT(now < trx.expiration, "", ("now", now)("trx.exp", trx.expiration)); + GOLOS_ASSERT(now < trx.expiration, tx_expired, + "Transaction is expired. Now ${now}, expired ${expired}", + ("now", now)("expired", trx.expiration-1) + ("trx.expiration", trx.expiration)); } - FC_ASSERT(now <= trx.expiration, "", ("now", now)("trx.exp", trx.expiration)); + GOLOS_ASSERT(now <= trx.expiration, tx_expired, + "Transaction is expired. Now ${now}, expired ${expired}", + ("now", now)("expired", trx.expiration) + ("trx.expiration", trx.expiration)); } } @@ -3592,9 +3637,11 @@ namespace golos { namespace chain { auto &trx_idx = get_index(); auto trx_id = trx.id(); // idump((trx_id)(skip&skip_transaction_dupe_check)); - FC_ASSERT((skip & skip_transaction_dupe_check) || - trx_idx.indices().get().find(trx_id) == trx_idx.indices().get().end(), + if (!(skip & skip_transaction_dupe_check) && + trx_idx.indices().get().find(trx_id) != trx_idx.indices().get().end()) { + FC_THROW_EXCEPTION(tx_duplicate_transaction, "Duplicate transaction check failed", ("trx_ix", trx_id)); + } _validate_transaction(trx, skip); @@ -3628,8 +3675,16 @@ namespace golos { namespace chain { _current_op_in_trx = 0; for (const auto &op : trx.operations) { try { - apply_operation(op); - ++_current_op_in_trx; + try { + apply_operation(op); + ++_current_op_in_trx; + } catch(const fc::exception& e) { + FC_THROW_EXCEPTION(tx_invalid_operation, + "Invalid operation ${index} in transaction: ${errmsg}", + ("index", _current_op_in_trx) + ("errmsg", e.to_string()) + ("error", e)); + } } FC_CAPTURE_AND_RETHROW((op)); } _current_trx_id = transaction_id_type(); @@ -3677,7 +3732,7 @@ namespace golos { namespace chain { void database::create_block_summary(const signed_block &next_block) { try { block_summary_id_type sid(next_block.block_num() & 0xffff); - modify(get(sid), [&](block_summary_object &p) { + modify(get_block_summary(sid), [&](block_summary_object &p) { p.block_id = next_block.id(); }); } FC_CAPTURE_AND_RETHROW() @@ -4144,7 +4199,7 @@ namespace golos { namespace chain { acnt.sbd_balance += delta; break; default: - FC_ASSERT(false, "invalid symbol"); + GOLOS_CHECK_VALUE(false, "invalid symbol"); } }); } @@ -4190,7 +4245,7 @@ namespace golos { namespace chain { acnt.savings_sbd_balance += delta; break; default: - FC_ASSERT(!"invalid symbol"); + GOLOS_CHECK_VALUE(false, "invalid symbol"); } }); } @@ -4222,7 +4277,7 @@ namespace golos { namespace chain { assert(props.current_sbd_supply.amount.value >= 0); break; default: - FC_ASSERT(false, "invalid symbol"); + GOLOS_CHECK_VALUE(false, "invalid symbol"); } }); } @@ -4235,7 +4290,7 @@ namespace golos { namespace chain { case SBD_SYMBOL: return a.sbd_balance; default: - FC_ASSERT(false, "invalid symbol"); + GOLOS_CHECK_VALUE(false, "invalid symbol"); } } @@ -4246,7 +4301,7 @@ namespace golos { namespace chain { case SBD_SYMBOL: return a.savings_sbd_balance; default: - FC_ASSERT(!"invalid symbol"); + GOLOS_CHECK_VALUE(false, "invalid symbol"); } } @@ -4470,17 +4525,17 @@ namespace golos { namespace chain { } } - modify(get(STEEMIT_MINER_ACCOUNT), [&](account_authority_object &auth) { + modify(get_authority(STEEMIT_MINER_ACCOUNT), [&](account_authority_object &auth) { auth.posting = authority(); auth.posting.weight_threshold = 1; }); - modify(get(STEEMIT_NULL_ACCOUNT), [&](account_authority_object &auth) { + modify(get_authority(STEEMIT_NULL_ACCOUNT), [&](account_authority_object &auth) { auth.posting = authority(); auth.posting.weight_threshold = 1; }); - modify(get(STEEMIT_TEMP_ACCOUNT), [&](account_authority_object &auth) { + modify(get_authority(STEEMIT_TEMP_ACCOUNT), [&](account_authority_object &auth) { auth.posting = authority(); auth.posting.weight_threshold = 1; }); @@ -4508,7 +4563,7 @@ namespace golos { namespace chain { update_owner_authority(*account, authority(1, public_key_type("GLS8hLtc7rC59Ed7uNVVTXtF578pJKQwMfdTvuzYLwUi8GkNTh5F6"), 1)); - modify(get(account->name), [&](account_authority_object &auth) { + modify(get_authority(account->name), [&](account_authority_object &auth) { auth.active = authority(1, public_key_type("GLS8hLtc7rC59Ed7uNVVTXtF578pJKQwMfdTvuzYLwUi8GkNTh5F6"), 1); auth.posting = authority(1, public_key_type("GLS8hLtc7rC59Ed7uNVVTXtF578pJKQwMfdTvuzYLwUi8GkNTh5F6"), 1); }); @@ -4778,12 +4833,7 @@ namespace golos { namespace chain { for (auto itr = cidx.begin(); itr != cidx.end(); ++itr) { if (itr->parent_author != STEEMIT_ROOT_POST_PARENT) { -// Low memory nodes only need immediate child count, full nodes track total children -#ifdef IS_LOW_MEM - modify(get_comment(itr->parent_author, itr->parent_permlink), [&](comment_object &c) { - c.children++; - }); -#else + const comment_object *parent = &get_comment(itr->parent_author, itr->parent_permlink); while (parent) { modify(*parent, [&](comment_object &c) { @@ -4796,7 +4846,7 @@ namespace golos { namespace chain { parent = nullptr; } } -#endif + } } } diff --git a/libraries/chain/database_proposal_object.cpp b/libraries/chain/database_proposal_object.cpp index 94e3d0c493..2eed410e41 100644 --- a/libraries/chain/database_proposal_object.cpp +++ b/libraries/chain/database_proposal_object.cpp @@ -1,6 +1,7 @@ #include #include #include +#include namespace golos { namespace chain { @@ -9,6 +10,8 @@ namespace golos { namespace chain { const std::string& title ) const { try { return get(std::make_tuple(author, title)); + } catch (const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("proposal", fc::mutable_variant_object()("account",author)("proposal",title)); } FC_CAPTURE_AND_RETHROW((author)(title)) } const proposal_object *database::find_proposal( @@ -18,6 +21,18 @@ namespace golos { namespace chain { return find(std::make_tuple(author, title)); } +// Yet another temporary PoC +// TODO: This can be generalized to generate all 3: get_, find_ and throw_if_exists_ +// methods with variable number of params (like DEFINE_/DECLARE_API + PLUGIN_API_VALIDATE_ARGS) +#define DB_DEFINE_THROW_IF_EXIST(O, T1, N1, T2, N2) \ + void database::throw_if_exists_##O(T1 N1, T2 N2) const { \ + if (nullptr != find_##O(N1, N2)) { \ + GOLOS_THROW_OBJECT_ALREADY_EXIST(#O, fc::mutable_variant_object()(#N1,N1)(#N2,N2)); \ + } \ + } + + DB_DEFINE_THROW_IF_EXIST(proposal, const account_name_type&, author, const std::string&, title); + void database::push_proposal(const proposal_object& proposal) { try { auto ops = proposal.operations(); auto session = start_undo_session(); @@ -68,4 +83,4 @@ namespace golos { namespace chain { } } -} } // golos::chain \ No newline at end of file +} } // golos::chain diff --git a/libraries/chain/include/golos/chain/comment_object.hpp b/libraries/chain/include/golos/chain/comment_object.hpp index c3171709a2..24660d3aba 100644 --- a/libraries/chain/include/golos/chain/comment_object.hpp +++ b/libraries/chain/include/golos/chain/comment_object.hpp @@ -40,25 +40,6 @@ namespace golos { archived }; - class comment_content_object - : public object { - public: - comment_content_object() = delete; - - template - comment_content_object(Constructor &&c, allocator a) - :title(a), body(a), json_metadata(a) { - c(*this); - } - - id_type id; - - comment_id_type comment; - - shared_string title; - shared_string body; - shared_string json_metadata; - }; class comment_object : public object { @@ -78,9 +59,7 @@ namespace golos { account_name_type author; shared_string permlink; - time_point_sec last_update; time_point_sec created; - time_point_sec active; ///< the last time this post was "touched" by voting or reply time_point_sec last_payout; uint16_t depth = 0; ///< used to track max nested depth @@ -106,13 +85,6 @@ namespace golos { uint16_t reward_weight = 0; - /** tracks the total payout this comment has received over time, measured in SBD */ - asset total_payout_value = asset(0, SBD_SYMBOL); - asset curator_payout_value = asset(0, SBD_SYMBOL); - asset beneficiary_payout_value = asset(0, SBD_SYMBOL); - - share_type author_rewards = 0; - int32_t net_votes = 0; id_type root_comment; @@ -149,62 +121,53 @@ namespace golos { int64_t rshares = 0; ///< The number of rshares this vote is responsible for int16_t vote_percent = 0; ///< The percent weight of the vote time_point_sec last_update; ///< The time of the last update of the vote - int8_t num_changes = 0; + int8_t num_changes = 0; ///< Count of vote changes (while consensus). If = -1 then related post is archived & vote no more needed for consensus }; struct by_comment_voter; struct by_voter_comment; struct by_comment_weight_voter; - struct by_voter_last_update; - typedef multi_index_container < - comment_vote_object, - indexed_by< - ordered_unique < tag < - by_id>, member>, - ordered_unique , - composite_key, - member - > - >, - ordered_unique , - composite_key, - member - > - >, - ordered_unique , - composite_key, - member, - member - >, - composite_key_compare , std::greater, std::less> - >, - ordered_unique , - composite_key, - member, - member - >, - composite_key_compare , std::greater, std::less> - > - >, - allocator - > - comment_vote_index; + struct by_vote_last_update; + using comment_vote_index = multi_index_container< + comment_vote_object, + indexed_by< + ordered_unique, + member>, + ordered_unique, + composite_key, + member + > + >, + ordered_unique, + composite_key, + member + > + >, + ordered_non_unique, + composite_key, + member + > + >, + ordered_unique, + composite_key, + member, + member + >, + composite_key_compare, std::greater, std::less> + > + >, + allocator + >; struct by_cashout_time; /// cashout_time struct by_permlink; /// author, perm struct by_root; struct by_parent; - struct by_last_update; /// parent_auth, last_update - struct by_author_last_update; /** * @ingroup object_index @@ -213,7 +176,6 @@ namespace golos { comment_object, indexed_by< - /// CONSENUSS INDICIES - used by evaluators ordered_unique < tag , member>, ordered_unique < @@ -238,41 +200,12 @@ namespace golos { member , member, member>, - composite_key_compare , strcmp_less, std::less> > - /// NON_CONSENSUS INDICIES - used by APIs -#ifndef IS_LOW_MEM - , - ordered_unique < - tag, - composite_key, - member, - member>, - composite_key_compare , std::greater, std::less>>, - ordered_unique < - tag, - composite_key, - member, - member>, - composite_key_compare , std::greater, std::less>> -#endif + composite_key_compare , strcmp_less, std::less> > >, allocator > comment_index; - - struct by_comment; - - typedef multi_index_container< - comment_content_object, - indexed_by< - ordered_unique< tag< by_id >, member< comment_content_object, comment_content_id_type, &comment_content_object::id > >, - ordered_unique< tag< by_comment >, member< comment_content_object, comment_id_type, &comment_content_object::comment > > >, - allocator< comment_content_object > - > comment_content_index; - } } // golos::chain @@ -280,7 +213,5 @@ FC_REFLECT_ENUM(golos::chain::comment_mode, (not_set)(first_payout)(second_payou CHAINBASE_SET_INDEX_TYPE(golos::chain::comment_object, golos::chain::comment_index) -CHAINBASE_SET_INDEX_TYPE(golos::chain::comment_content_object, golos::chain::comment_content_index) - CHAINBASE_SET_INDEX_TYPE(golos::chain::comment_vote_object, golos::chain::comment_vote_index) diff --git a/libraries/chain/include/golos/chain/database.hpp b/libraries/chain/include/golos/chain/database.hpp index bee9b10fa9..0ebc031fc3 100644 --- a/libraries/chain/include/golos/chain/database.hpp +++ b/libraries/chain/include/golos/chain/database.hpp @@ -49,7 +49,7 @@ namespace golos { namespace chain { } bool _is_producing = false; - + bool _is_testing = false; ///< set for tests to avoid low free memory spam bool _log_hardforks = true; enum validation_steps { @@ -72,6 +72,12 @@ namespace golos { namespace chain { skip_database_locking = 1 << 15 ///< used to skip locking of database }; + enum store_metadata_modes { + store_metadata_for_all, + store_metadata_for_listed, + store_metadata_for_nobody + }; + /** * @brief Open a database, creating a new one if necessary * @@ -96,9 +102,14 @@ namespace golos { namespace chain { void set_block_num_check_free_size(uint32_t); void check_free_memory(bool skip_print, uint32_t current_block_num); - void set_clear_votes(uint32_t clear_votes_block); void set_skip_virtual_ops(); - bool clear_votes(); + + void set_store_account_metadata(store_metadata_modes store_account_metadata); + void set_accounts_to_store_metadata(const std::vector& accounts_to_store_metadata); + bool store_metadata_for_account(const std::string& name) const; + + void set_store_memo_in_savings_withdraws(bool store_memo_in_savings_withdraws); + bool store_memo_in_savings_withdraws() const; /** * @brief wipe Delete database from disk, and potentially the raw chain as well. @@ -138,6 +149,9 @@ namespace golos { namespace chain { chain_id_type get_chain_id() const; + void throw_if_exists_limit_order(const account_name_type &account, uint32_t id) const; + + void throw_if_exists_account(const account_name_type &account) const; const witness_object &get_witness(const account_name_type &name) const; @@ -148,8 +162,8 @@ namespace golos { namespace chain { const account_object *find_account(const account_name_type &name) const; const proposal_object& get_proposal(const account_name_type&, const std::string&) const; - const proposal_object* find_proposal(const account_name_type&, const std::string&) const; + void throw_if_exists_proposal(const account_name_type&, const std::string&) const; const comment_object &get_comment(const account_name_type &author, const shared_string &permlink) const; @@ -159,9 +173,7 @@ namespace golos { namespace chain { const comment_object *find_comment(const account_name_type &author, const string &permlink) const; - const comment_content_object &get_comment_content(const comment_id_type &comment) const; - - const comment_content_object *find_comment_content(const comment_id_type &comment) const; + const comment_object &get_comment(const comment_id_type &comment) const; const escrow_object &get_escrow(const account_name_type &name, uint32_t escrow_id) const; @@ -171,9 +183,17 @@ namespace golos { namespace chain { const limit_order_object *find_limit_order(const account_name_type &owner, uint32_t id) const; - const savings_withdraw_object &get_savings_withdraw(const account_name_type &owner, uint32_t request_id) const; + const convert_request_object &get_convert_request(const account_name_type &owner, uint32_t id) const; + + const convert_request_object *find_convert_request(const account_name_type &owner, uint32_t id) const; + + void throw_if_exists_convert_request(const account_name_type &owner, uint32_t id) const; - const savings_withdraw_object *find_savings_withdraw(const account_name_type &owner, uint32_t request_id) const; + const savings_withdraw_object& get_savings_withdraw(const account_name_type& owner, uint32_t request_id) const; + const savings_withdraw_object* find_savings_withdraw(const account_name_type& owner, uint32_t request_id) const; + void throw_if_exists_savings_withdraw(const account_name_type& owner, uint32_t request_id) const; + + const account_authority_object &get_authority(const account_name_type &name) const; const dynamic_global_property_object &get_dynamic_global_properties() const; @@ -183,6 +203,8 @@ namespace golos { namespace chain { const hardfork_property_object &get_hardfork_property_object() const; + const block_summary_object &get_block_summary(const block_summary_id_type &ref_block_num) const; + const time_point_sec calculate_discussion_payout_time(const comment_object &comment) const; @@ -349,8 +371,6 @@ namespace golos { namespace chain { asset create_vesting(const account_object &to_account, asset steem); - void adjust_total_payout(const comment_object &a, const asset &sbd, const asset &curator_sbd_value, const asset& beneficiary_value); - void update_witness_schedule(); void adjust_liquidity_reward(const account_object &owner, const asset &volume, bool is_bid); @@ -419,13 +439,9 @@ namespace golos { namespace chain { share_type claim_rshare_reward(share_type rshares, uint16_t reward_weight, asset max_steem); asset get_liquidity_reward() const; - asset get_content_reward() const; - - asset get_producer_reward(); - + asset get_producer_reward() const; asset get_curation_reward() const; - asset get_pow_reward() const; uint16_t get_curation_rewards_percent() const; @@ -595,6 +611,8 @@ namespace golos { namespace chain { template friend void add_plugin_index(database &db); + friend struct database_fixture; + fc::signal _plugin_index_signal; transaction_id_type _current_trx_id; @@ -619,6 +637,11 @@ namespace golos { namespace chain { bool _skip_virtual_ops = false; bool _enable_plugins_on_push_transaction = true; + store_metadata_modes _store_account_metadata = store_metadata_for_all; + std::vector _accounts_to_store_metadata; + + bool _store_memo_in_savings_withdraws = true; + flat_map> _custom_operation_interpreters; std::string _json_schema; }; diff --git a/libraries/chain/include/golos/chain/database_exceptions.hpp b/libraries/chain/include/golos/chain/database_exceptions.hpp index 544717fc4b..d13f689010 100644 --- a/libraries/chain/include/golos/chain/database_exceptions.hpp +++ b/libraries/chain/include/golos/chain/database_exceptions.hpp @@ -2,46 +2,6 @@ #include -#define STEEMIT_DECLARE_OP_BASE_EXCEPTIONS(op_name) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - op_name ## _validate_exception, \ - golos::chain::operation_validate_exception, \ - 4040000 + 100 * protocol::operation::tag< protocol::op_name ## _operation >::value, \ - #op_name "_operation validation exception" \ - ) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - op_name ## _evaluate_exception, \ - golos::chain::operation_evaluate_exception, \ - 4050000 + 100 * protocol::operation::tag< protocol::op_name ## _operation >::value, \ - #op_name "_operation evaluation exception" \ - ) - -#define STEEMIT_DECLARE_OP_VALIDATE_EXCEPTION(exc_name, op_name, seqnum, msg) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - op_name ## _ ## exc_name, \ - golos::chain::op_name ## _validate_exception, \ - 4040000 + 100 * protocol::operation::tag< protocol::op_name ## _operation >::value \ - + seqnum, \ - msg \ - ) - -#define STEEMIT_DECLARE_OP_EVALUATE_EXCEPTION(exc_name, op_name, seqnum, msg) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - op_name ## _ ## exc_name, \ - golos::chain::op_name ## _evaluate_exception, \ - 4050000 + 100 * protocol::operation::tag< protocol::op_name ## _operation >::value \ - + seqnum, \ - msg \ - ) - -#define STEEMIT_DECLARE_INTERNAL_EXCEPTION(exc_name, seqnum, msg) \ - FC_DECLARE_DERIVED_EXCEPTION( \ - internal_ ## exc_name, \ - golos::chain::internal_exception, \ - 4990000 + seqnum, \ - msg \ - ) - #define STEEMIT_TRY_NOTIFY(signal, ...) \ try \ { \ @@ -66,18 +26,6 @@ namespace golos { FC_DECLARE_EXCEPTION(chain_exception, 4000000, "blockchain exception") - FC_DECLARE_DERIVED_EXCEPTION(database_query_exception, golos::chain::chain_exception, 4010000, "database query exception") - - FC_DECLARE_DERIVED_EXCEPTION(block_validate_exception, golos::chain::chain_exception, 4020000, "block validation exception") - - FC_DECLARE_DERIVED_EXCEPTION(transaction_exception, golos::chain::chain_exception, 4030000, "transaction validation exception") - - FC_DECLARE_DERIVED_EXCEPTION(operation_validate_exception, golos::chain::chain_exception, 4040000, "operation validation exception") - - FC_DECLARE_DERIVED_EXCEPTION(operation_evaluate_exception, golos::chain::chain_exception, 4050000, "operation evaluation exception") - - FC_DECLARE_DERIVED_EXCEPTION(utility_exception, golos::chain::chain_exception, 4060000, "utility method exception") - FC_DECLARE_DERIVED_EXCEPTION(undo_database_exception, golos::chain::chain_exception, 4070000, "undo database exception") FC_DECLARE_DERIVED_EXCEPTION(unlinkable_block_exception, golos::chain::chain_exception, 4080000, "unlinkable block") @@ -90,27 +38,6 @@ namespace golos { FC_DECLARE_DERIVED_EXCEPTION(pop_empty_chain, golos::chain::undo_database_exception, 4070001, "there are no blocks to pop") - STEEMIT_DECLARE_OP_BASE_EXCEPTIONS(transfer); -// STEEMIT_DECLARE_OP_EVALUATE_EXCEPTION( from_account_not_whitelisted, transfer, 1, "owner mismatch" ) - - STEEMIT_DECLARE_OP_BASE_EXCEPTIONS(account_create); - - STEEMIT_DECLARE_OP_EVALUATE_EXCEPTION(max_auth_exceeded, account_create, 1, "Exceeds max authority fan-out") - - STEEMIT_DECLARE_OP_EVALUATE_EXCEPTION(auth_account_not_found, account_create, 2, "Auth account not found") - - STEEMIT_DECLARE_OP_BASE_EXCEPTIONS(account_update); - - STEEMIT_DECLARE_OP_EVALUATE_EXCEPTION(max_auth_exceeded, account_update, 1, "Exceeds max authority fan-out") - - STEEMIT_DECLARE_OP_EVALUATE_EXCEPTION(auth_account_not_found, account_update, 2, "Auth account not found") - - FC_DECLARE_DERIVED_EXCEPTION(internal_exception, golos::chain::chain_exception, 4990000, "internal exception") - - STEEMIT_DECLARE_INTERNAL_EXCEPTION(verify_auth_max_auth_exceeded, 1, "Exceeds max authority fan-out") - - STEEMIT_DECLARE_INTERNAL_EXCEPTION(verify_auth_account_not_found, 2, "Auth account not found") - FC_DECLARE_DERIVED_EXCEPTION(database_revision_exception, golos::chain::chain_exception, 4120000, "database revision exception") FC_DECLARE_DERIVED_EXCEPTION(database_signal_exception, golos::chain::chain_exception, 4130000, "database signal exception") diff --git a/libraries/chain/include/golos/chain/generic_custom_operation_interpreter.hpp b/libraries/chain/include/golos/chain/generic_custom_operation_interpreter.hpp index 05b53f56dc..3a5a0ab99c 100644 --- a/libraries/chain/include/golos/chain/generic_custom_operation_interpreter.hpp +++ b/libraries/chain/include/golos/chain/generic_custom_operation_interpreter.hpp @@ -47,10 +47,22 @@ namespace golos { operation_get_required_authorities(inner_o, inner_active, inner_owner, inner_posting, inner_other); } - FC_ASSERT(inner_owner == outer_owner); - FC_ASSERT(inner_active == outer_active); - FC_ASSERT(inner_posting == outer_posting); - FC_ASSERT(inner_other == outer_other); + GOLOS_CHECK_LOGIC(inner_owner == outer_owner, + logic_exception::inner_authorities_does_not_match_outer, + "Owner authorities for inner operations are not equal to those for operation", + ("authority","owner")("inner",inner_owner)("outer",outer_owner)); + GOLOS_CHECK_LOGIC(inner_active == outer_active, + logic_exception::inner_authorities_does_not_match_outer, + "Active authorities for inner operations are not equal to those for operation", + ("authority","active")("inner",inner_active)("outer",outer_active)); + GOLOS_CHECK_LOGIC(inner_posting == outer_posting, + logic_exception::inner_authorities_does_not_match_outer, + "Posting authorities for inner operations are not equal to those for operation", + ("authority","posting")("inner",inner_posting)("outer",outer_posting)); + GOLOS_CHECK_LOGIC(inner_other == outer_other, + logic_exception::inner_authorities_does_not_match_outer, + "Other authorities for inner operations are not equal to those for operation", + ("authority","other")("inner",inner_other)("outer",outer_other)); for (const CustomOperationType &inner_o : custom_operations) { // gcc errors if this-> is not here diff --git a/libraries/chain/include/golos/chain/operation_notification.hpp b/libraries/chain/include/golos/chain/operation_notification.hpp index a235c77db5..254709e839 100644 --- a/libraries/chain/include/golos/chain/operation_notification.hpp +++ b/libraries/chain/include/golos/chain/operation_notification.hpp @@ -1,26 +1,24 @@ #pragma once #include - #include -namespace golos { - using protocol::operation; - namespace chain { - - struct operation_notification { - operation_notification(const operation &o) : op(o) { - } +namespace golos { namespace chain { - bool stored_in_db = false; - int64_t db_id = 0; - transaction_id_type trx_id; - uint32_t block = 0; - uint32_t trx_in_block = 0; - uint16_t op_in_trx = 0; - uint32_t virtual_op = 0; - const operation &op; - }; +using protocol::operation; +struct operation_notification { + operation_notification(const operation &o) : op(o) { } -} + + bool stored_in_db = false; + int64_t db_id = 0; + transaction_id_type trx_id; + uint32_t block = 0; + uint32_t trx_in_block = 0; + uint16_t op_in_trx = 0; + uint32_t virtual_op = 0; + const operation& op; +}; + +} } // golos::chain diff --git a/libraries/chain/include/golos/chain/steem_evaluator.hpp b/libraries/chain/include/golos/chain/steem_evaluator.hpp index 8a698df583..0522ef2552 100644 --- a/libraries/chain/include/golos/chain/steem_evaluator.hpp +++ b/libraries/chain/include/golos/chain/steem_evaluator.hpp @@ -5,7 +5,9 @@ #include #define ASSERT_REQ_HF(HF, FEATURE) \ - FC_ASSERT(db().has_hardfork(HF), FEATURE " is not enabled until HF " BOOST_PP_STRINGIZE(HF)); + GOLOS_ASSERT(db().has_hardfork(HF), unsupported_operation, \ + "${feature} is not enabled until HF ${hardfork}", \ + ("feature",FEATURE)("hardfork",BOOST_PP_STRINGIZE(HF))); namespace golos { namespace chain { using namespace golos::protocol; diff --git a/libraries/chain/include/golos/chain/steem_object_types.hpp b/libraries/chain/include/golos/chain/steem_object_types.hpp index 350625a480..481f38efb7 100644 --- a/libraries/chain/include/golos/chain/steem_object_types.hpp +++ b/libraries/chain/include/golos/chain/steem_object_types.hpp @@ -33,7 +33,7 @@ namespace golos { namespace chain { return std::string(str.begin(), str.end()); } - inline void from_string(shared_string &out, const string &in) { + inline void from_string(shared_string &out, const std::string &in) { out.assign(in.begin(), in.end()); } @@ -51,7 +51,6 @@ namespace golos { namespace chain { block_summary_object_type, witness_schedule_object_type, comment_object_type, - comment_content_object_type, comment_vote_object_type, witness_vote_object_type, limit_order_object_type, @@ -85,7 +84,6 @@ namespace golos { namespace chain { class proposal_object; class required_approval_object; class comment_object; - class comment_content_object; class comment_vote_object; class witness_vote_object; class limit_order_object; @@ -115,7 +113,6 @@ namespace golos { namespace chain { typedef object_id block_summary_id_type; typedef object_id witness_schedule_id_type; typedef object_id comment_id_type; - typedef object_id comment_content_id_type; typedef object_id comment_vote_id_type; typedef object_id witness_vote_id_type; typedef object_id limit_order_id_type; @@ -216,7 +213,6 @@ FC_REFLECT_ENUM(golos::chain::object_type, (block_summary_object_type) (witness_schedule_object_type) (comment_object_type) - (comment_content_object_type) (comment_vote_object_type) (witness_vote_object_type) (limit_order_object_type) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 8486462e18..bc593390e0 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -97,32 +98,32 @@ namespace golos { namespace chain { } void proposal_create_evaluator::do_apply(const proposal_create_operation& o) { try { - ASSERT_REQ_HF(STEEMIT_HARDFORK_0_18__542, "Proposal transaction creating"); // remove after hf - safe_int_increment depth_increment(depth_); if (_db.is_producing()) { - FC_ASSERT( + GOLOS_CHECK_LOGIC( depth_ <= STEEMIT_MAX_PROPOSAL_DEPTH, + logic_exception::proposal_depth_too_high, "You can't create more than ${depth} nested proposals", ("depth", STEEMIT_MAX_PROPOSAL_DEPTH)); } - FC_ASSERT(nullptr == _db.find_proposal(o.author, o.title), "Proposal already exists."); + GOLOS_CHECK_OBJECT_MISSING(_db, proposal, o.author, o.title); const auto now = _db.head_block_time(); - FC_ASSERT( - o.expiration_time > now, - "Proposal has already expired on creation."); - FC_ASSERT( - o.expiration_time <= now + STEEMIT_MAX_PROPOSAL_LIFETIME_SEC, - "Proposal expiration time is too far in the future."); - FC_ASSERT( - !o.review_period_time || *o.review_period_time > now, - "Proposal review period has expired on creation."); - FC_ASSERT( - !o.review_period_time || *o.review_period_time < o.expiration_time, - "Proposal review period must be less than its overall lifetime."); + GOLOS_CHECK_OP_PARAM(o, expiration_time, { + GOLOS_CHECK_VALUE(o.expiration_time > now, "Proposal has already expired on creation."); + GOLOS_CHECK_VALUE(o.expiration_time <= now + STEEMIT_MAX_PROPOSAL_LIFETIME_SEC, + "Proposal expiration time is too far i n the future."); + }); + if (o.review_period_time) { + GOLOS_CHECK_OP_PARAM(o, review_period_time, { + GOLOS_CHECK_VALUE(*o.review_period_time > now, + "Proposal review period has expired on creation."); + GOLOS_CHECK_VALUE(*o.review_period_time < o.expiration_time, + "Proposal review period must be less than its overall lifetime."); + }); + } //Populate the required approval sets flat_set required_owner; @@ -134,7 +135,7 @@ namespace golos { namespace chain { for (const auto& op : o.proposed_operations) { operation_get_required_authorities(op.op, required_active, required_owner, required_posting, other); } - FC_ASSERT(other.size() == 0); // TODO: what about other??? + GOLOS_ASSERT(other.size() == 0, golos::internal_error, "other size > 0"); // TODO: what about other??? // All accounts which must provide both owner and active authority should be omitted from // the active authority set. Owner authority approval implies active authority approval. @@ -143,20 +144,21 @@ namespace golos { namespace chain { required_total.insert(required_active.begin(), required_active.end()); // For more information, see transaction.cpp - FC_ASSERT( + GOLOS_CHECK_LOGIC( required_posting.empty() != required_total.empty(), + logic_exception::tx_with_both_posting_active_ops, "Can't combine operations required posting authority and active or owner authority"); required_total.insert(required_posting.begin(), required_posting.end()); // Doesn't allow proposal with combination of create_account() + some_operation() // because it will be never approved. for (const auto& account: required_total) { - FC_ASSERT( - nullptr != _db.find_account(account), - "Account '${account}' for proposed operation doesn't exist", ("account", account)); + _db.get_account(account); // will throw if no account + // "Account '${account}' for proposed operation doesn't exist", ("account", account)); } - FC_ASSERT(required_total.size(), "No operations require approvals"); + // Looks like it's impossible to fail because other = 0 and proposed_ops.size > 0 + GOLOS_ASSERT(required_total.size(), golos::internal_error, "No operations require approvals"); transaction trx; for (const auto& op : o.proposed_operations) { @@ -170,6 +172,7 @@ namespace golos { namespace chain { golos::chain::database::skip_tapos_check | golos::chain::database::skip_database_locking; + // This not only validates proposal operations but also pre-apply them to execute evaluators' checks _db.validate_transaction(trx, skip_steps); auto ops_size = fc::raw::pack_size(trx.operations); @@ -204,13 +207,12 @@ namespace golos { namespace chain { } void proposal_update_evaluator::do_apply(const proposal_update_operation& o) { try { - ASSERT_REQ_HF(STEEMIT_HARDFORK_0_18__542, "Proposal transaction updating"); // remove after hf - safe_int_increment depth_increment(depth_); if (_db.is_producing()) { - FC_ASSERT( + GOLOS_CHECK_LOGIC( depth_ <= STEEMIT_MAX_PROPOSAL_DEPTH, + logic_exception::proposal_depth_too_high, "You can't create more than ${depth} nested proposals", ("depth", STEEMIT_MAX_PROPOSAL_DEPTH)); } @@ -219,17 +221,20 @@ namespace golos { namespace chain { const auto now = _db.head_block_time(); if (proposal.review_period_time && now >= *proposal.review_period_time) { - FC_ASSERT( - o.active_approvals_to_add.empty() && + GOLOS_CHECK_LOGIC( o.owner_approvals_to_add.empty() && + o.active_approvals_to_add.empty() && o.posting_approvals_to_add.empty() && o.key_approvals_to_add.empty(), - "This proposal is in its review period. No new approvals may be added."); + logic_exception::cannot_add_approval_in_review_period, + "This proposal is in it's review period. No new approvals may be added."); } auto check_existing = [&](const auto& to_remove, const auto& dst) { for (const auto& a: to_remove) { - FC_ASSERT(dst.find(a) != dst.end(), "Can't remove the non existing approval '${id}'", ("id", a)); + GOLOS_CHECK_LOGIC(dst.find(a) != dst.end(), + logic_exception::non_existing_approval, + "Can't remove the non existing approval '${id}'", ("id", a)); } }; @@ -240,7 +245,9 @@ namespace golos { namespace chain { auto check_duplicate = [&](const auto& to_add, const auto& dst) { for (const auto& a: to_add) { - FC_ASSERT(dst.find(a) == dst.end(), "Can't add already exist approval '${id}'", ("id", a)); + GOLOS_CHECK_LOGIC(dst.find(a) == dst.end(), + logic_exception::already_existing_approval, + "Can't add already exist approval '${id}'", ("id", a)); } }; @@ -291,14 +298,13 @@ namespace golos { namespace chain { } FC_CAPTURE_AND_RETHROW((o)) } void proposal_delete_evaluator::do_apply(const proposal_delete_operation& o) { try { - ASSERT_REQ_HF(STEEMIT_HARDFORK_0_18__542, "Proposal transaction deleting"); // remove after hf const auto& proposal = _db.get_proposal(o.author, o.title); - - FC_ASSERT( + GOLOS_CHECK_LOGIC( proposal.author == o.requester || proposal.required_active_approvals.count(o.requester) || proposal.required_owner_approvals.count(o.requester) || proposal.required_posting_approvals.count(o.requester), + logic_exception::proposal_delete_not_allowed, // or should it be auth-related? "Provided authority is not authoritative for this proposal.", ("author", o.author)("title", o.title)("requester", o.requester)); @@ -306,4 +312,4 @@ namespace golos { namespace chain { } FC_CAPTURE_AND_RETHROW((o)) } -} } // golos::chain \ No newline at end of file +} } // golos::chain diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 25d67bc465..bf185bea3a 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -59,15 +59,15 @@ namespace golos { namespace chain { auto ops = operations(); auto get_active = [&](const account_name_type& name) { - return authority(db.get(name).active); + return authority(db.get_authority(name).active); }; auto get_owner = [&](const account_name_type& name) { - return authority(db.get(name).owner); + return authority(db.get_authority(name).owner); }; auto get_posting = [&](const account_name_type& name) { - return authority(db.get(name).posting); + return authority(db.get_authority(name).posting); }; golos::protocol::verify_authority( @@ -76,4 +76,4 @@ namespace golos { namespace chain { active_approvals, owner_approvals, posting_approvals); } -} } // golos::chain \ No newline at end of file +} } // golos::chain diff --git a/libraries/chain/shared_authority.cpp b/libraries/chain/shared_authority.cpp index 2b090b0504..3b235467a5 100644 --- a/libraries/chain/shared_authority.cpp +++ b/libraries/chain/shared_authority.cpp @@ -1,4 +1,5 @@ #include +#include namespace golos { namespace chain { @@ -76,7 +77,8 @@ namespace golos { void shared_authority::validate() const { for (const auto &item : account_auths) { - FC_ASSERT(protocol::is_valid_account_name(item.first)); + GOLOS_CHECK_VALUE(protocol::is_valid_account_name(item.first), + "Account name \"${account}\" is invalid", ("account",item.first)); } } diff --git a/libraries/chain/steem_evaluator.cpp b/libraries/chain/steem_evaluator.cpp index 2016d98eda..98f50681cc 100644 --- a/libraries/chain/steem_evaluator.cpp +++ b/libraries/chain/steem_evaluator.cpp @@ -4,31 +4,87 @@ #include #include -#ifndef IS_LOW_MEM - -#include -#include - -using boost::locale::conv::utf_to_utf; - -std::wstring utf8_to_wstring(const std::string &str) { - return utf_to_utf(str.c_str(), str.c_str() + str.size()); -} - -std::string wstring_to_utf8(const std::wstring &str) { - return utf_to_utf(str.c_str(), str.c_str() + str.size()); -} - -#endif - +#define GOLOS_CHECK_BALANCE(ACCOUNT, TYPE, REQUIRED ...) \ + FC_EXPAND_MACRO( \ + FC_MULTILINE_MACRO_BEGIN \ + asset exist = get_balance(ACCOUNT, TYPE, (REQUIRED).symbol); \ + if( UNLIKELY( exist < (REQUIRED) )) { \ + FC_THROW_EXCEPTION( golos::insufficient_funds, \ + "Account \"${account}\" does not have enough ${balance}: required ${required}, exist ${exist}", \ + ("account",ACCOUNT.name)("balance",get_balance_name(TYPE))("required",REQUIRED)("exist",exist)); \ + } \ + FC_MULTILINE_MACRO_END \ + ) + +#define GOLOS_CHECK_BANDWIDTH(NOW, NEXT, TYPE, MSG, ...) \ + GOLOS_ASSERT((NOW) > (NEXT), golos::bandwidth_exception, MSG, \ + ("bandwidth",TYPE)("now",NOW)("next",NEXT) __VA_ARGS__) namespace golos { namespace chain { using fc::uint128_t; + enum balance_type { + MAIN_BALANCE, + SAVINGS, + VESTING, + EFFECTIVE_VESTING, + HAVING_VESTING, + AVAILABLE_VESTING + }; + + asset get_balance(const account_object &account, balance_type type, asset_symbol_type symbol) { + switch(type) { + case MAIN_BALANCE: + switch (symbol) { + case STEEM_SYMBOL: + return account.balance; + case SBD_SYMBOL: + return account.sbd_balance; + default: + GOLOS_CHECK_VALUE(false, "invalid symbol"); + } + case SAVINGS: + switch (symbol) { + case STEEM_SYMBOL: + return account.savings_balance; + case SBD_SYMBOL: + return account.savings_sbd_balance; + default: + GOLOS_CHECK_VALUE(false, "invalid symbol"); + } + case VESTING: + GOLOS_CHECK_VALUE(symbol == VESTS_SYMBOL, "invalid symbol"); + return account.vesting_shares; + case EFFECTIVE_VESTING: + GOLOS_CHECK_VALUE(symbol == VESTS_SYMBOL, "invalid symbol"); + return account.effective_vesting_shares(); + case HAVING_VESTING: + GOLOS_CHECK_VALUE(symbol == VESTS_SYMBOL, "invalid symbol"); + return account.available_vesting_shares(false); + case AVAILABLE_VESTING: + GOLOS_CHECK_VALUE(symbol == VESTS_SYMBOL, "invalid symbol"); + return account.available_vesting_shares(true); + default: FC_ASSERT(false, "invalid balance type"); + } + } + + std::string get_balance_name(balance_type type) { + switch(type) { + case MAIN_BALANCE: return "fund"; + case SAVINGS: return "savings"; + case VESTING: return "vesting shares"; + case EFFECTIVE_VESTING: return "effective vesting shares"; + case HAVING_VESTING: return "having vesting shares"; + case AVAILABLE_VESTING: return "available vesting shares"; + default: FC_ASSERT(false, "invalid balance type"); + } + } + inline void validate_permlink_0_1(const string &permlink) { - FC_ASSERT(permlink.size() > STEEMIT_MIN_PERMLINK_LENGTH && - permlink.size() < - STEEMIT_MAX_PERMLINK_LENGTH, "Permlink is not a valid size."); + GOLOS_CHECK_VALUE(permlink.size() > STEEMIT_MIN_PERMLINK_LENGTH && + permlink.size() < STEEMIT_MAX_PERMLINK_LENGTH, + "Permlink is not a valid size. Permlink length should be more ${min} and less ${max}", + ("min", STEEMIT_MIN_PERMLINK_LENGTH)("max", STEEMIT_MAX_PERMLINK_LENGTH)); for (auto c : permlink) { switch (c) { @@ -71,8 +127,8 @@ namespace golos { namespace chain { case '-': break; default: - FC_ASSERT(false, "Invalid permlink character: ${s}", ("s", - std::string() + c)); + FC_THROW_EXCEPTION(invalid_value, "Invalid permlink character: ${s}", + ("s", std::string() + c)); } } } @@ -87,7 +143,14 @@ namespace golos { namespace chain { void store_account_json_metadata( database& db, const account_name_type& account, const string& json_metadata, bool skip_empty = false ) { -#ifndef IS_LOW_MEM + if (!db.store_metadata_for_account(account)) { + auto meta = db.find(account); + if (meta != nullptr) { + db.remove(*meta); + } + return; + } + if (skip_empty && json_metadata.size() == 0) return; @@ -104,20 +167,20 @@ namespace golos { namespace chain { from_string(a.json_metadata, json_metadata); }); } -#endif } void account_create_evaluator::do_apply(const account_create_operation &o) { const auto& creator = _db.get_account(o.creator); - FC_ASSERT(creator.balance >= o.fee, - "Insufficient balance to create account.", ("creator.balance", creator.balance)("required", o.fee)); + GOLOS_CHECK_BALANCE(creator, MAIN_BALANCE, o.fee); if (_db.has_hardfork(STEEMIT_HARDFORK_0_1)) { const auto& median_props = _db.get_witness_schedule_object().median_props; auto min_fee = median_props.account_creation_fee; - FC_ASSERT(o.fee >= min_fee, - "Insufficient Fee: ${f} required, ${p} provided.", ("f", min_fee)("p", o.fee)); + GOLOS_CHECK_OP_PARAM(o, fee, + GOLOS_CHECK_VALUE(o.fee >= min_fee, + "Insufficient Fee: ${f} required, ${p} provided.", ("f", min_fee)("p", o.fee)); + ); } if (_db.is_producing() || @@ -139,6 +202,8 @@ namespace golos { namespace chain { c.balance -= o.fee; }); + GOLOS_CHECK_OBJECT_MISSING(_db, account, o.new_account_name); + const auto& props = _db.get_dynamic_global_properties(); const auto& new_account = _db.create([&](account_object& acc) { acc.name = o.new_account_name; @@ -169,16 +234,9 @@ namespace golos { namespace chain { } void account_create_with_delegation_evaluator::do_apply(const account_create_with_delegation_operation& o) { - ASSERT_REQ_HF(STEEMIT_HARDFORK_0_18__535, "Account creation with delegation"); - const auto& creator = _db.get_account(o.creator); - FC_ASSERT(creator.balance >= o.fee, "Insufficient balance to create account.", - ("creator.balance", creator.balance)("required", o.fee)); - FC_ASSERT(creator.available_vesting_shares(true) >= o.delegation, - "Insufficient vesting shares to delegate to new account.", - ("creator.vesting_shares", creator.vesting_shares) - ("creator.delegated_vesting_shares", creator.delegated_vesting_shares) - ("required", o.delegation)); + GOLOS_CHECK_BALANCE(creator, MAIN_BALANCE, o.fee); + GOLOS_CHECK_BALANCE(creator, AVAILABLE_VESTING, o.delegation); const auto& v_share_price = _db.get_dynamic_global_properties().get_vesting_share_price(); const auto& median_props = _db.get_witness_schedule_object().median_props; @@ -191,11 +249,15 @@ namespace golos { namespace chain { #endif auto current_delegation = o.fee * target.amount.value / min_fee * v_share_price + o.delegation; - FC_ASSERT(current_delegation >= target_delegation, - "Inssufficient Delegation ${f} required, ${p} provided.", + GOLOS_CHECK_LOGIC(current_delegation >= target_delegation, + logic_exception::not_enough_delegation, + "Insufficient Delegation ${f} required, ${p} provided.", ("f", target_delegation)("p", current_delegation)("o.fee", o.fee) ("o.delegation", o.delegation)); - FC_ASSERT(o.fee >= median_props.create_account_min_golos_fee, - "Insufficient Fee: ${f} required, ${p} provided.", ("f", median_props.create_account_min_golos_fee)("p", o.fee)); + auto min_golos = median_props.create_account_min_golos_fee; + GOLOS_CHECK_OP_PARAM(o, fee, { + GOLOS_CHECK_VALUE(o.fee >= min_golos, + "Insufficient Fee: ${f} required, ${p} provided.", ("f", min_golos)("p", o.fee)); + }); for (auto& a : o.owner.account_auths) { _db.get_account(a.first); @@ -231,7 +293,7 @@ namespace golos { namespace chain { auth.posting = o.posting; auth.last_owner_update = fc::time_point_sec::min(); }); - if (o.delegation.amount > 0) { // Is it needed to allow zero delegation in this method ? + if (o.delegation.amount > 0) { _db.create([&](vesting_delegation_object& d) { d.delegator = o.creator; d.delegatee = o.new_account_name; @@ -245,10 +307,10 @@ namespace golos { namespace chain { } void account_update_evaluator::do_apply(const account_update_operation &o) { - database &_db = db(); if (_db.has_hardfork(STEEMIT_HARDFORK_0_1)) - FC_ASSERT(o.account != - STEEMIT_TEMP_ACCOUNT, "Cannot update temp account."); + GOLOS_CHECK_OP_PARAM(o, account, + GOLOS_CHECK_VALUE(o.account != STEEMIT_TEMP_ACCOUNT, + "Cannot update temp account.")); if ((_db.has_hardfork(STEEMIT_HARDFORK_0_15__465) || _db.is_producing()) && o.posting) { // TODO: Add HF 15 @@ -256,14 +318,15 @@ namespace golos { namespace chain { } const auto &account = _db.get_account(o.account); - const auto &account_auth = _db.get(o.account); + const auto &account_auth = _db.get_authority(o.account); if (o.owner) { #ifndef STEEMIT_BUILD_TESTNET if (_db.has_hardfork(STEEMIT_HARDFORK_0_11)) - FC_ASSERT(_db.head_block_time() - - account_auth.last_owner_update > - STEEMIT_OWNER_UPDATE_LIMIT, "Owner authority can only be updated once an hour."); + GOLOS_CHECK_BANDWIDTH(_db.head_block_time(), + account_auth.last_owner_update + STEEMIT_OWNER_UPDATE_LIMIT, + bandwidth_exception::change_owner_authority_bandwidth, + "Owner authority can only be updated once an hour."); #endif if ((_db.has_hardfork(STEEMIT_HARDFORK_0_15__465) || @@ -320,7 +383,6 @@ namespace golos { namespace chain { } void account_metadata_evaluator::do_apply(const account_metadata_operation& o) { - ASSERT_REQ_HF(STEEMIT_HARDFORK_0_18__196, "account_metadata_operation"); //TODO: Delete after hardfork const auto& account = _db.get_account(o.account); _db.modify(account, [&](account_object& a) { a.last_account_update = _db.head_block_time(); @@ -332,20 +394,22 @@ namespace golos { namespace chain { * Because net_rshares is 0 there is no need to update any pending payout calculations or parent posts. */ void delete_comment_evaluator::do_apply(const delete_comment_operation &o) { - database &_db = db(); if (_db.has_hardfork(STEEMIT_HARDFORK_0_10)) { const auto &auth = _db.get_account(o.author); - FC_ASSERT(!(auth.owner_challenged || - auth.active_challenged), "Operation cannot be processed because account is currently challenged."); + GOLOS_CHECK_LOGIC(!(auth.owner_challenged || auth.active_challenged), + logic_exception::account_is_currently_challenged, + "Operation cannot be processed because account is currently challenged."); } const auto &comment = _db.get_comment(o.author, o.permlink); - FC_ASSERT(comment.children == - 0, "Cannot delete a comment with replies."); + GOLOS_CHECK_LOGIC(comment.children == 0, + logic_exception::cannot_delete_comment_with_replies, + "Cannot delete a comment with replies."); if (_db.is_producing()) { - FC_ASSERT(comment.net_rshares <= - 0, "Cannot delete a comment with network positive votes."); + GOLOS_CHECK_LOGIC(comment.net_rshares <= 0, + logic_exception::cannot_delete_comment_with_positive_votes, + "Cannot delete a comment with network positive votes."); } if (comment.net_rshares > 0) { return; @@ -365,26 +429,18 @@ namespace golos { namespace chain { if (_db.has_hardfork(STEEMIT_HARDFORK_0_6__80) && comment.parent_author != STEEMIT_ROOT_POST_PARENT) { auto parent = &_db.get_comment(comment.parent_author, comment.parent_permlink); - auto now = _db.head_block_time(); while (parent) { _db.modify(*parent, [&](comment_object &p) { p.children--; - p.active = now; }); -#ifndef IS_LOW_MEM if (parent->parent_author != STEEMIT_ROOT_POST_PARENT) { parent = &_db.get_comment(parent->parent_author, parent->parent_permlink); } else -#endif { parent = nullptr; } } } -#ifndef IS_LOW_MEM - auto& content = _db.get_comment_content(comment.id); - _db.remove(content); -#endif _db.remove(comment); } @@ -400,17 +456,21 @@ namespace golos { namespace chain { void operator()(const comment_payout_beneficiaries &cpb) const { if (_db.is_producing()) { - FC_ASSERT(cpb.beneficiaries.size() <= STEEMIT_MAX_COMMENT_BENEFICIARIES, - "Cannot specify more than ${m} beneficiaries.", ("m", STEEMIT_MAX_COMMENT_BENEFICIARIES)); + GOLOS_CHECK_LOGIC(cpb.beneficiaries.size() <= STEEMIT_MAX_COMMENT_BENEFICIARIES, + logic_exception::cannot_specify_more_beneficiaries, + "Cannot specify more than ${m} beneficiaries.", ("m", STEEMIT_MAX_COMMENT_BENEFICIARIES)); } - FC_ASSERT(_c.beneficiaries.size() == 0, "Comment already has beneficiaries specified."); - FC_ASSERT(_c.abs_rshares == 0, "Comment must not have been voted on before specifying beneficiaries."); + GOLOS_CHECK_LOGIC(_c.beneficiaries.size() == 0, + logic_exception::comment_already_has_beneficiaries, + "Comment already has beneficiaries specified."); + GOLOS_CHECK_LOGIC(_c.abs_rshares == 0, + logic_exception::comment_must_not_have_been_voted, + "Comment must not have been voted on before specifying beneficiaries."); _db.modify(_c, [&](comment_object &c) { for (auto &b : cpb.beneficiaries) { - auto acc = _db.find< account_object, by_name >( b.account ); - FC_ASSERT( acc != nullptr, "Beneficiary \"${a}\" must exist.", ("a", b.account) ); + _db.get_account(b.account); // check beneficiary exists c.beneficiaries.push_back(b); } }); @@ -421,30 +481,31 @@ namespace golos { namespace chain { database &_db = db(); if (_db.has_hardfork(STEEMIT_HARDFORK_0_10)) { const auto &auth = _db.get_account(o.author); - FC_ASSERT(!(auth.owner_challenged || auth.active_challenged), - "Operation cannot be processed because account is currently challenged."); + GOLOS_CHECK_LOGIC(!(auth.owner_challenged || auth.active_challenged), + logic_exception::account_is_currently_challenged, + "Operation cannot be processed because account is currently challenged."); } const auto &comment = _db.get_comment(o.author, o.permlink); if (!o.allow_curation_rewards || !o.allow_votes || o.max_accepted_payout < comment.max_accepted_payout) { - FC_ASSERT(comment.abs_rshares == 0, - "One of the included comment options requires the comment to have no rshares allocated to it."); + GOLOS_CHECK_LOGIC(comment.abs_rshares == 0, + logic_exception::comment_options_requires_no_rshares, + "One of the included comment options requires the comment to have no rshares allocated to it."); } - if (!_db.has_hardfork(STEEMIT_HARDFORK_0_17__432)) {// TODO: Remove after hardfork 17 - FC_ASSERT(o.extensions.size() == 0, - "Operation extensions for the comment_options_operation are not currently supported."); - } - - FC_ASSERT(comment.allow_curation_rewards >= o.allow_curation_rewards, - "Curation rewards cannot be re-enabled."); - FC_ASSERT(comment.allow_votes >= o.allow_votes, - "Voting cannot be re-enabled."); - FC_ASSERT(comment.max_accepted_payout >= o.max_accepted_payout, - "A comment cannot accept a greater payout."); - FC_ASSERT(comment.percent_steem_dollars >= o.percent_steem_dollars, - "A comment cannot accept a greater percent SBD."); + GOLOS_CHECK_LOGIC(comment.allow_curation_rewards >= o.allow_curation_rewards, + logic_exception::curation_rewards_cannot_be_reenabled, + "Curation rewards cannot be re-enabled."); + GOLOS_CHECK_LOGIC(comment.allow_votes >= o.allow_votes, + logic_exception::voting_cannot_be_reenabled, + "Voting cannot be re-enabled."); + GOLOS_CHECK_LOGIC(comment.max_accepted_payout >= o.max_accepted_payout, + logic_exception::comment_cannot_accept_greater_payout, + "A comment cannot accept a greater payout."); + GOLOS_CHECK_LOGIC(comment.percent_steem_dollars >= o.percent_steem_dollars, + logic_exception::comment_cannot_accept_greater_percent_GBG, + "A comment cannot accept a greater percent SBD."); _db.modify(comment, [&](comment_object &c) { c.max_accepted_payout = o.max_accepted_payout; @@ -460,12 +521,11 @@ namespace golos { namespace chain { void comment_evaluator::do_apply(const comment_operation &o) { try { - database &_db = db(); - if (_db.is_producing() || _db.has_hardfork(STEEMIT_HARDFORK_0_5__55)) - FC_ASSERT(o.title.size() + o.body.size() + - o.json_metadata.size(), "Cannot update comment because nothing appears to be changing."); + GOLOS_CHECK_LOGIC(o.title.size() + o.body.size() + o.json_metadata.size(), + logic_exception::cannot_update_comment_because_nothing_changed, + "Cannot update comment because nothing appears to be changing."); const auto &by_permlink_idx = _db.get_index().indices().get(); auto itr = by_permlink_idx.find(boost::make_tuple(o.author, o.permlink)); @@ -473,8 +533,9 @@ namespace golos { namespace chain { const auto &auth = _db.get_account(o.author); /// prove it exists if (_db.has_hardfork(STEEMIT_HARDFORK_0_10)) - FC_ASSERT(!(auth.owner_challenged || - auth.active_challenged), "Operation cannot be processed because account is currently challenged."); + GOLOS_CHECK_LOGIC(!(auth.owner_challenged || auth.active_challenged), + logic_exception::account_is_currently_challenged, + "Operation cannot be processed because account is currently challenged."); comment_id_type id; @@ -487,20 +548,18 @@ namespace golos { namespace chain { } else if (_db.is_producing()) { max_depth = STEEMIT_SOFT_MAX_COMMENT_DEPTH; } - FC_ASSERT(parent->depth < max_depth, - "Comment is nested ${x} posts deep, maximum depth is ${y}.", - ("x", parent->depth)("y", max_depth)); + GOLOS_CHECK_LOGIC(parent->depth < max_depth, + logic_exception::reached_comment_max_depth, + "Comment is nested ${x} posts deep, maximum depth is ${y}.", + ("x", parent->depth)("y", max_depth)); } auto now = _db.head_block_time(); if (itr == by_permlink_idx.end()) { if (o.parent_author != STEEMIT_ROOT_POST_PARENT) { - FC_ASSERT(_db.get(parent->root_comment).allow_replies, "The parent comment has disabled replies."); - if (_db.has_hardfork(STEEMIT_HARDFORK_0_12__177) && !_db.has_hardfork( STEEMIT_HARDFORK_0_18__536) ) { - FC_ASSERT( - _db.calculate_discussion_payout_time(*parent) != - fc::time_point_sec::maximum(), "Discussion is frozen."); - } + GOLOS_CHECK_LOGIC(_db.get(parent->root_comment).allow_replies, + logic_exception::replies_are_not_allowed, + "The parent comment has disabled replies."); } auto band = _db.find(std::make_tuple(o.author, bandwidth_type::post)); @@ -513,21 +572,26 @@ namespace golos { namespace chain { if (_db.has_hardfork(STEEMIT_HARDFORK_0_12__176)) { if (o.parent_author == STEEMIT_ROOT_POST_PARENT) - FC_ASSERT((now - band->last_bandwidth_update) > - STEEMIT_MIN_ROOT_COMMENT_INTERVAL, "You may only post once every 5 minutes.", ("now", now)("last_root_post", band->last_bandwidth_update)); + GOLOS_CHECK_BANDWIDTH(now, band->last_bandwidth_update + STEEMIT_MIN_ROOT_COMMENT_INTERVAL, + bandwidth_exception::post_bandwidth, + "You may only post once every 5 minutes."); else - FC_ASSERT((now - auth.last_post) > - STEEMIT_MIN_REPLY_INTERVAL, "You may only comment once every 20 seconds.", ("now", now)("auth.last_post", auth.last_post)); + GOLOS_CHECK_BANDWIDTH(now, auth.last_post + STEEMIT_MIN_REPLY_INTERVAL, + golos::bandwidth_exception::comment_bandwidth, + "You may only comment once every 20 seconds."); } else if (_db.has_hardfork(STEEMIT_HARDFORK_0_6__113)) { if (o.parent_author == STEEMIT_ROOT_POST_PARENT) - FC_ASSERT((now - auth.last_post) > - STEEMIT_MIN_ROOT_COMMENT_INTERVAL, "You may only post once every 5 minutes.", ("now", now)("auth.last_post", auth.last_post)); + GOLOS_CHECK_BANDWIDTH(now, auth.last_post + STEEMIT_MIN_ROOT_COMMENT_INTERVAL, + bandwidth_exception::post_bandwidth, + "You may only post once every 5 minutes."); else - FC_ASSERT((now - auth.last_post) > - STEEMIT_MIN_REPLY_INTERVAL, "You may only comment once every 20 seconds.", ("now", now)("auth.last_post", auth.last_post)); + GOLOS_CHECK_BANDWIDTH(now, auth.last_post + STEEMIT_MIN_REPLY_INTERVAL, + bandwidth_exception::comment_bandwidth, + "You may only comment once every 20 seconds."); } else { - FC_ASSERT((now - auth.last_post) > - fc::seconds(60), "You may only post once per minute.", ("now", now)("auth.last_post", auth.last_post)); + GOLOS_CHECK_BANDWIDTH(now, auth.last_post + 60, + bandwidth_exception::post_bandwidth, + "You may only post once per minute."); } uint16_t reward_weight = STEEMIT_100_PERCENT; @@ -562,17 +626,15 @@ namespace golos { namespace chain { a.post_count++; }); - const auto &new_comment = _db.create([&](comment_object &com) { + _db.create([&](comment_object &com) { if (_db.has_hardfork(STEEMIT_HARDFORK_0_1)) { - validate_permlink_0_1(o.parent_permlink); - validate_permlink_0_1(o.permlink); + GOLOS_CHECK_OP_PARAM(o, parent_permlink, validate_permlink_0_1(o.parent_permlink)); + GOLOS_CHECK_OP_PARAM(o, permlink, validate_permlink_0_1(o.permlink)); } com.author = o.author; from_string(com.permlink, o.permlink); - com.last_update = _db.head_block_time(); - com.created = com.last_update; - com.active = com.last_update; + com.created = _db.head_block_time(); com.last_payout = fc::time_point_sec::min(); com.max_cashout_time = fc::time_point_sec::maximum(); com.reward_weight = reward_weight; @@ -599,120 +661,50 @@ namespace golos { namespace chain { } }); - id = new_comment.id; -#ifndef IS_LOW_MEM - _db.create([&](comment_content_object& con) { - con.comment = id; - from_string(con.title, o.title); - if (o.body.size() < 1024*1024*128) { - from_string(con.body, o.body); - } - if (fc::is_utf8(o.json_metadata)) { - from_string(con.json_metadata, o.json_metadata); - } else { - wlog("Comment ${a}/${p} contains invalid UTF-8 metadata", - ("a", o.author)("p", o.permlink)); - } - }); -#endif -/// this loop can be skiped for validate-only nodes as it is merely gathering stats for indicies - auto now = _db.head_block_time(); + while (parent) { _db.modify(*parent, [&](comment_object &p) { p.children++; - p.active = now; }); -#ifndef IS_LOW_MEM if (parent->parent_author != STEEMIT_ROOT_POST_PARENT) { parent = &_db.get_comment(parent->parent_author, parent->parent_permlink); } else -#endif { parent = nullptr; } } - } else // start edit case - { - const auto &comment = *itr; - if ( !_db.has_hardfork( STEEMIT_HARDFORK_0_18__536 ) ) { - if (_db.has_hardfork(STEEMIT_HARDFORK_0_14__306)) { - FC_ASSERT(_db.calculate_discussion_payout_time(comment) != fc::time_point_sec::maximum(), - "The comment is archived."); - } else if (_db.has_hardfork(STEEMIT_HARDFORK_0_10)) { - FC_ASSERT(comment.last_payout == fc::time_point_sec::min(), - "Can only edit during the first 24 hours."); - } - } - - _db.modify(comment, [&](comment_object &com) { - com.last_update = _db.head_block_time(); - com.active = com.last_update; + } else { + // start edit case + const auto& comment = *itr; + _db.modify(comment, [&](comment_object& com) { strcmp_equal equal; - if (!parent) { - FC_ASSERT(com.parent_author == - account_name_type(), "The parent of a comment cannot change."); - FC_ASSERT(equal(com.parent_permlink, o.parent_permlink), "The permlink of a comment cannot change."); - } else { - FC_ASSERT(com.parent_author == - o.parent_author, "The parent of a comment cannot change."); - FC_ASSERT(equal(com.parent_permlink, o.parent_permlink), "The permlink of a comment cannot change."); - } + GOLOS_CHECK_LOGIC(com.parent_author == (parent ? o.parent_author : account_name_type()), + logic_exception::parent_of_comment_cannot_change, + "The parent of a comment cannot change."); + GOLOS_CHECK_LOGIC(equal(com.parent_permlink, o.parent_permlink), + logic_exception::parent_perlink_of_comment_cannot_change, + "The parent permlink of a comment cannot change."); }); -#ifndef IS_LOW_MEM - _db.modify(_db.get< comment_content_object, by_comment >( comment.id ), [&]( comment_content_object& con ) { - if (o.title.size()) - from_string(con.title, o.title); - if (o.json_metadata.size()) { - if (fc::is_utf8(o.json_metadata)) - from_string(con.json_metadata, o.json_metadata ); - else - wlog("Comment ${a}/${p} contains invalid UTF-8 metadata", ("a", o.author)("p", o.permlink)); - } - if (o.body.size()) { - try { - diff_match_patch dmp; - auto patch = dmp.patch_fromText(utf8_to_wstring(o.body)); - if (patch.size()) { - auto result = dmp.patch_apply(patch, utf8_to_wstring(to_string(con.body))); - auto patched_body = wstring_to_utf8(result.first); - if(!fc::is_utf8(patched_body)) { - idump(("invalid utf8")(patched_body)); - from_string(con.body, fc::prune_invalid_utf8(patched_body)); - } - else { - from_string(con.body, patched_body); - } - } - else { // replace - from_string(con.body, o.body); - } - } catch ( ... ) { - from_string(con.body, o.body); - } - } - }); -#endif - } // end EDIT case } FC_CAPTURE_AND_RETHROW((o)) } - void escrow_transfer_evaluator::do_apply(const escrow_transfer_operation &o) { + void escrow_transfer_evaluator::do_apply(const escrow_transfer_operation& o) { try { - database &_db = db(); - - const auto &from_account = _db.get_account(o.from); + const auto& from_account = _db.get_account(o.from); _db.get_account(o.to); _db.get_account(o.agent); - FC_ASSERT(o.ratification_deadline > - _db.head_block_time(), "The escorw ratification deadline must be after head block time."); - FC_ASSERT(o.escrow_expiration > - _db.head_block_time(), "The escrow expiration must be after head block time."); + GOLOS_CHECK_LOGIC(o.ratification_deadline > _db.head_block_time(), + logic_exception::escrow_time_in_past, + "The escrow ratification deadline must be after head block time."); + GOLOS_CHECK_LOGIC(o.escrow_expiration > _db.head_block_time(), + logic_exception::escrow_time_in_past, + "The escrow expiration must be after head block time."); asset steem_spent = o.steem_amount; asset sbd_spent = o.sbd_amount; @@ -721,16 +713,12 @@ namespace golos { namespace chain { } else { sbd_spent += o.fee; } - - FC_ASSERT(from_account.balance >= - steem_spent, "Account cannot cover STEEM costs of escrow. Required: ${r} Available: ${a}", ("r", steem_spent)("a", from_account.balance)); - FC_ASSERT(from_account.sbd_balance >= - sbd_spent, "Account cannot cover SBD costs of escrow. Required: ${r} Available: ${a}", ("r", sbd_spent)("a", from_account.sbd_balance)); - + GOLOS_CHECK_BALANCE(from_account, MAIN_BALANCE, steem_spent); + GOLOS_CHECK_BALANCE(from_account, MAIN_BALANCE, sbd_spent); _db.adjust_balance(from_account, -steem_spent); _db.adjust_balance(from_account, -sbd_spent); - _db.create([&](escrow_object &esc) { + _db.create([&](escrow_object& esc) { esc.escrow_id = o.escrow_id; esc.from = o.from; esc.to = o.to; @@ -745,34 +733,36 @@ namespace golos { namespace chain { FC_CAPTURE_AND_RETHROW((o)) } - void escrow_approve_evaluator::do_apply(const escrow_approve_operation &o) { + void escrow_approve_evaluator::do_apply(const escrow_approve_operation& o) { try { - database &_db = db(); - const auto &escrow = _db.get_escrow(o.from, o.escrow_id); - - FC_ASSERT(escrow.to == - o.to, "Operation 'to' (${o}) does not match escrow 'to' (${e}).", ("o", o.to)("e", escrow.to)); - FC_ASSERT(escrow.agent == - o.agent, "Operation 'agent' (${a}) does not match escrow 'agent' (${e}).", ("o", o.agent)("e", escrow.agent)); - FC_ASSERT(escrow.ratification_deadline >= - _db.head_block_time(), "The escrow ratification deadline has passed. Escrow can no longer be ratified."); + const auto& escrow = _db.get_escrow(o.from, o.escrow_id); + GOLOS_CHECK_LOGIC(escrow.to == o.to, + logic_exception::escrow_bad_to, + "Operation 'to' (${o}) does not match escrow 'to' (${e}).", ("o",o.to)("e",escrow.to)); + GOLOS_CHECK_LOGIC(escrow.agent == o.agent, + logic_exception::escrow_bad_agent, + "Operation 'agent' (${a}) does not match escrow 'agent' (${e}).", ("o",o.agent)("e",escrow.agent)); + GOLOS_CHECK_LOGIC(escrow.ratification_deadline >= _db.head_block_time(), + logic_exception::ratification_deadline_passed, + "The escrow ratification deadline has passed. Escrow can no longer be ratified."); bool reject_escrow = !o.approve; - if (o.who == o.to) { - FC_ASSERT(!escrow.to_approved, "Account 'to' (${t}) has already approved the escrow.", ("t", o.to)); - + GOLOS_CHECK_LOGIC(!escrow.to_approved, + logic_exception::account_already_approved_escrow, + "Account 'to' (${t}) has already approved the escrow.", ("t",o.to)); if (!reject_escrow) { - _db.modify(escrow, [&](escrow_object &esc) { + _db.modify(escrow, [&](escrow_object& esc) { esc.to_approved = true; }); } } if (o.who == o.agent) { - FC_ASSERT(!escrow.agent_approved, "Account 'agent' (${a}) has already approved the escrow.", ("a", o.agent)); - + GOLOS_CHECK_LOGIC(!escrow.agent_approved, + logic_exception::account_already_approved_escrow, + "Account 'agent' (${a}) has already approved the escrow.", ("a",o.agent)); if (!reject_escrow) { - _db.modify(escrow, [&](escrow_object &esc) { + _db.modify(escrow, [&](escrow_object& esc) { esc.agent_approved = true; }); } @@ -783,13 +773,11 @@ namespace golos { namespace chain { _db.adjust_balance(from_account, escrow.steem_balance); _db.adjust_balance(from_account, escrow.sbd_balance); _db.adjust_balance(from_account, escrow.pending_fee); - _db.remove(escrow); } else if (escrow.to_approved && escrow.agent_approved) { const auto &agent_account = _db.get_account(o.agent); _db.adjust_balance(agent_account, escrow.pending_fee); - - _db.modify(escrow, [&](escrow_object &esc) { + _db.modify(escrow, [&](escrow_object& esc) { esc.pending_fee.amount = 0; }); } @@ -797,65 +785,81 @@ namespace golos { namespace chain { FC_CAPTURE_AND_RETHROW((o)) } - void escrow_dispute_evaluator::do_apply(const escrow_dispute_operation &o) { + void escrow_dispute_evaluator::do_apply(const escrow_dispute_operation& o) { try { - database &_db = db(); _db.get_account(o.from); // Verify from account exists - - const auto &e = _db.get_escrow(o.from, o.escrow_id); - FC_ASSERT(_db.head_block_time() < - e.escrow_expiration, "Disputing the escrow must happen before expiration."); - FC_ASSERT(e.to_approved && - e.agent_approved, "The escrow must be approved by all parties before a dispute can be raised."); - FC_ASSERT(!e.disputed, "The escrow is already under dispute."); - FC_ASSERT(e.to == - o.to, "Operation 'to' (${o}) does not match escrow 'to' (${e}).", ("o", o.to)("e", e.to)); - FC_ASSERT(e.agent == - o.agent, "Operation 'agent' (${a}) does not match escrow 'agent' (${e}).", ("o", o.agent)("e", e.agent)); - - _db.modify(e, [&](escrow_object &esc) { + const auto& e = _db.get_escrow(o.from, o.escrow_id); + GOLOS_CHECK_LOGIC(_db.head_block_time() < e.escrow_expiration, + logic_exception::cannot_dispute_expired_escrow, + "Disputing the escrow must happen before expiration."); + GOLOS_CHECK_LOGIC(e.to_approved && e.agent_approved, + logic_exception::escrow_must_be_approved_first, + "The escrow must be approved by all parties before a dispute can be raised."); + GOLOS_CHECK_LOGIC(!e.disputed, + logic_exception::escrow_already_disputed, + "The escrow is already under dispute."); + GOLOS_CHECK_LOGIC(e.to == o.to, + logic_exception::escrow_bad_to, + "Operation 'to' (${o}) does not match escrow 'to' (${e}).", ("o",o.to)("e",e.to)); + GOLOS_CHECK_LOGIC(e.agent == o.agent, + logic_exception::escrow_bad_agent, + "Operation 'agent' (${a}) does not match escrow 'agent' (${e}).", ("o",o.agent)("e",e.agent)); + + _db.modify(e, [&](escrow_object& esc) { esc.disputed = true; }); } FC_CAPTURE_AND_RETHROW((o)) } - void escrow_release_evaluator::do_apply(const escrow_release_operation &o) { + void escrow_release_evaluator::do_apply(const escrow_release_operation& o) { try { - database &_db = db(); _db.get_account(o.from); // Verify from account exists - const auto &receiver_account = _db.get_account(o.receiver); - - const auto &e = _db.get_escrow(o.from, o.escrow_id); - FC_ASSERT(e.steem_balance >= - o.steem_amount, "Release amount exceeds escrow balance. Amount: ${a}, Balance: ${b}", ("a", o.steem_amount)("b", e.steem_balance)); - FC_ASSERT(e.sbd_balance >= - o.sbd_amount, "Release amount exceeds escrow balance. Amount: ${a}, Balance: ${b}", ("a", o.sbd_amount)("b", e.sbd_balance)); - FC_ASSERT(e.to == - o.to, "Operation 'to' (${o}) does not match escrow 'to' (${e}).", ("o", o.to)("e", e.to)); - FC_ASSERT(e.agent == - o.agent, "Operation 'agent' (${a}) does not match escrow 'agent' (${e}).", ("o", o.agent)("e", e.agent)); - FC_ASSERT(o.receiver == e.from || o.receiver == - e.to, "Funds must be released to 'from' (${f}) or 'to' (${t})", ("f", e.from)("t", e.to)); - FC_ASSERT(e.to_approved && - e.agent_approved, "Funds cannot be released prior to escrow approval."); + const auto& receiver_account = _db.get_account(o.receiver); + + const auto& e = _db.get_escrow(o.from, o.escrow_id); + GOLOS_CHECK_LOGIC(e.steem_balance >= o.steem_amount, + logic_exception::release_amount_exceeds_escrow_balance, + "Release amount exceeds escrow balance. Amount: ${a}, Balance: ${b}", + ("a",o.steem_amount)("b",e.steem_balance)); + GOLOS_CHECK_LOGIC(e.sbd_balance >= o.sbd_amount, + logic_exception::release_amount_exceeds_escrow_balance, + "Release amount exceeds escrow balance. Amount: ${a}, Balance: ${b}", + ("a",o.sbd_amount)("b",e.sbd_balance)); + GOLOS_CHECK_LOGIC(e.to == o.to, + logic_exception::escrow_bad_to, + "Operation 'to' (${o}) does not match escrow 'to' (${e}).", ("o",o.to)("e",e.to)); + GOLOS_CHECK_LOGIC(e.agent == o.agent, + logic_exception::escrow_bad_agent, + "Operation 'agent' (${a}) does not match escrow 'agent' (${e}).", ("o",o.agent)("e",e.agent)); + GOLOS_CHECK_LOGIC(o.receiver == e.from || o.receiver == e.to, + logic_exception::escrow_bad_receiver, + "Funds must be released to 'from' (${f}) or 'to' (${t})", ("f",e.from)("t",e.to)); + GOLOS_CHECK_LOGIC(e.to_approved && e.agent_approved, + logic_exception::escrow_must_be_approved_first, + "Funds cannot be released prior to escrow approval."); // If there is a dispute regardless of expiration, the agent can release funds to either party if (e.disputed) { - FC_ASSERT(o.who == - e.agent, "Only 'agent' (${a}) can release funds in a disputed escrow.", ("a", e.agent)); + GOLOS_CHECK_LOGIC(o.who == e.agent, + logic_exception::only_agent_can_release_disputed, + "Only 'agent' (${a}) can release funds in a disputed escrow.", ("a",e.agent)); } else { - FC_ASSERT(o.who == e.from || o.who == - e.to, "Only 'from' (${f}) and 'to' (${t}) can release funds from a non-disputed escrow", ("f", e.from)("t", e.to)); + GOLOS_CHECK_LOGIC(o.who == e.from || o.who == e.to, + logic_exception::only_from_to_can_release_non_disputed, + "Only 'from' (${f}) and 'to' (${t}) can release funds from a non-disputed escrow", + ("f",e.from)("t",e.to)); if (e.escrow_expiration > _db.head_block_time()) { // If there is no dispute and escrow has not expired, either party can release funds to the other. if (o.who == e.from) { - FC_ASSERT(o.receiver == - e.to, "Only 'from' (${f}) can release funds to 'to' (${t}).", ("f", e.from)("t", e.to)); + GOLOS_CHECK_LOGIC(o.receiver == e.to, + logic_exception::from_can_release_only_to_to, + "Only 'from' (${f}) can release funds to 'to' (${t}).", ("f",e.from)("t",e.to)); } else if (o.who == e.to) { - FC_ASSERT(o.receiver == - e.from, "Only 'to' (${t}) can release funds to 'from' (${t}).", ("f", e.from)("t", e.to)); + GOLOS_CHECK_LOGIC(o.receiver == e.from, + logic_exception::to_can_release_only_to_from, + "Only 'to' (${t}) can release funds to 'from' (${t}).", ("f",e.from)("t",e.to)); } } } @@ -864,7 +868,7 @@ namespace golos { namespace chain { _db.adjust_balance(receiver_account, o.steem_amount); _db.adjust_balance(receiver_account, o.sbd_amount); - _db.modify(e, [&](escrow_object &esc) { + _db.modify(e, [&](escrow_object& esc) { esc.steem_balance -= o.steem_amount; esc.sbd_balance -= o.sbd_amount; }); @@ -876,8 +880,8 @@ namespace golos { namespace chain { FC_CAPTURE_AND_RETHROW((o)) } + void transfer_evaluator::do_apply(const transfer_operation &o) { - database &_db = db(); const auto &from_account = _db.get_account(o.from); const auto &to_account = _db.get_account(o.to); @@ -888,34 +892,30 @@ namespace golos { namespace chain { }); } - FC_ASSERT(_db.get_balance(from_account, o.amount.symbol) >= - o.amount, "Account does not have sufficient funds for transfer."); - _db.adjust_balance(from_account, -o.amount); - _db.adjust_balance(to_account, o.amount); + GOLOS_CHECK_OP_PARAM(o, amount, { + GOLOS_CHECK_BALANCE(from_account, MAIN_BALANCE, o.amount); + _db.adjust_balance(from_account, -o.amount); + _db.adjust_balance(to_account, o.amount); + }); } void transfer_to_vesting_evaluator::do_apply(const transfer_to_vesting_operation &o) { - database &_db = db(); - const auto &from_account = _db.get_account(o.from); const auto &to_account = o.to.size() ? _db.get_account(o.to) : from_account; - FC_ASSERT(_db.get_balance(from_account, STEEM_SYMBOL) >= - o.amount, "Account does not have sufficient GOLOS for transfer."); - _db.adjust_balance(from_account, -o.amount); - _db.create_vesting(to_account, o.amount); + GOLOS_CHECK_OP_PARAM(o, amount, { + GOLOS_CHECK_BALANCE(from_account, MAIN_BALANCE, o.amount); + _db.adjust_balance(from_account, -o.amount); + _db.create_vesting(to_account, o.amount); + }); } void withdraw_vesting_evaluator::do_apply(const withdraw_vesting_operation &o) { - database &_db = db(); - const auto &account = _db.get_account(o.account); - FC_ASSERT(account.vesting_shares.amount >= 0, - "Account does not have sufficient Golos Power for withdraw."); - FC_ASSERT(account.available_vesting_shares() >= o.vesting_shares, - "Account does not have sufficient Golos Power for withdraw."); + GOLOS_CHECK_BALANCE(account, VESTING, asset(0, VESTS_SYMBOL)); + GOLOS_CHECK_BALANCE(account, HAVING_VESTING, o.vesting_shares); if (!account.mined && _db.has_hardfork(STEEMIT_HARDFORK_0_1)) { const auto &props = _db.get_dynamic_global_properties(); @@ -925,17 +925,19 @@ namespace golos { namespace chain { props.get_vesting_share_price(); min_vests.amount.value *= 10; - FC_ASSERT(account.vesting_shares.amount > min_vests.amount || + GOLOS_CHECK_LOGIC(account.vesting_shares.amount > min_vests.amount || (_db.has_hardfork(STEEMIT_HARDFORK_0_16__562) && o.vesting_shares.amount == 0), + logic_exception::insufficient_fee_for_powerdown_registered_account, "Account registered by another account requires 10x account creation fee worth of Golos Power before it can be powered down."); } if (o.vesting_shares.amount == 0) { if (_db.is_producing() || _db.has_hardfork(STEEMIT_HARDFORK_0_5__57)) - FC_ASSERT(account.vesting_withdraw_rate.amount != - 0, "This operation would not change the vesting withdraw rate."); + GOLOS_CHECK_LOGIC(account.vesting_withdraw_rate.amount != 0, + logic_exception::operation_would_not_change_vesting_withdraw_rate, + "This operation would not change the vesting withdraw rate."); _db.modify(account, [&](account_object &a) { a.vesting_withdraw_rate = asset(0, VESTS_SYMBOL); @@ -959,8 +961,9 @@ namespace golos { namespace chain { if (_db.is_producing() || _db.has_hardfork(STEEMIT_HARDFORK_0_5__57)) - FC_ASSERT(account.vesting_withdraw_rate != - new_vesting_withdraw_rate, "This operation would not change the vesting withdraw rate."); + GOLOS_CHECK_LOGIC(account.vesting_withdraw_rate != new_vesting_withdraw_rate, + logic_exception::operation_would_not_change_vesting_withdraw_rate, + "This operation would not change the vesting withdraw rate."); a.vesting_withdraw_rate = new_vesting_withdraw_rate; a.next_vesting_withdrawal = _db.head_block_time() + @@ -973,17 +976,19 @@ namespace golos { namespace chain { void set_withdraw_vesting_route_evaluator::do_apply(const set_withdraw_vesting_route_operation &o) { try { - database &_db = db(); const auto &from_account = _db.get_account(o.from_account); const auto &to_account = _db.get_account(o.to_account); const auto &wd_idx = _db.get_index().indices().get(); auto itr = wd_idx.find(boost::make_tuple(from_account.id, to_account.id)); if (itr == wd_idx.end()) { - FC_ASSERT( - o.percent != 0, "Cannot create a 0% destination."); - FC_ASSERT(from_account.withdraw_routes < - STEEMIT_MAX_WITHDRAW_ROUTES, "Account already has the maximum number of routes."); + GOLOS_CHECK_LOGIC(o.percent != 0, + logic_exception::cannot_create_zero_percent_destination, + "Cannot create a 0% destination."); + GOLOS_CHECK_LOGIC(from_account.withdraw_routes < STEEMIT_MAX_WITHDRAW_ROUTES, + logic_exception::reached_maxumum_number_of_routes, + "Account already has the maximum number of routes (${max}).", + ("max",STEEMIT_MAX_WITHDRAW_ROUTES)); _db.create([&](withdraw_vesting_route_object &wvdo) { wvdo.from_account = from_account.id; @@ -1011,7 +1016,7 @@ namespace golos { namespace chain { } itr = wd_idx.upper_bound(boost::make_tuple(from_account.id, account_id_type())); - uint16_t total_percent = 0; + fc::safe total_percent = 0; while (itr->from_account == from_account.id && itr != wd_idx.end()) { @@ -1019,18 +1024,22 @@ namespace golos { namespace chain { ++itr; } - FC_ASSERT(total_percent <= - STEEMIT_100_PERCENT, "More than 100% of vesting withdrawals allocated to destinations."); + GOLOS_CHECK_LOGIC(total_percent <= STEEMIT_100_PERCENT, + logic_exception::more_100percent_allocated_to_destinations, + "More than 100% of vesting withdrawals allocated to destinations."); } FC_CAPTURE_AND_RETHROW() } void account_witness_proxy_evaluator::do_apply(const account_witness_proxy_operation &o) { - database &_db = db(); const auto &account = _db.get_account(o.account); - FC_ASSERT(account.proxy != o.proxy, "Proxy must change."); + GOLOS_CHECK_LOGIC(account.proxy != o.proxy, + logic_exception::proxy_must_change, + "Proxy must change."); - FC_ASSERT(account.can_vote, "Account has declined the ability to vote and cannot proxy votes."); + GOLOS_CHECK_LOGIC(account.can_vote, + logic_exception::voter_declined_voting_rights, + "Account has declined the ability to vote and cannot proxy votes."); /// remove all current votes std::array delta; @@ -1050,10 +1059,13 @@ namespace golos { namespace chain { auto cprox = &new_proxy; while (cprox->proxy.size() != 0) { const auto next_proxy = _db.get_account(cprox->proxy); - FC_ASSERT(proxy_chain.insert(next_proxy.id).second, "This proxy would create a proxy loop."); + GOLOS_CHECK_LOGIC(proxy_chain.insert(next_proxy.id).second, + logic_exception::proxy_would_create_loop, + "This proxy would create a proxy loop."); cprox = &next_proxy; - FC_ASSERT(proxy_chain.size() <= - STEEMIT_MAX_PROXY_RECURSION_DEPTH, "Proxy chain is too long."); + GOLOS_CHECK_LOGIC(proxy_chain.size() <= STEEMIT_MAX_PROXY_RECURSION_DEPTH, + logic_exception::proxy_chain_is_too_long, + "Proxy chain is too long."); } /// clear all individual vote records @@ -1077,13 +1089,15 @@ namespace golos { namespace chain { void account_witness_vote_evaluator::do_apply(const account_witness_vote_operation &o) { - database &_db = db(); const auto &voter = _db.get_account(o.account); - FC_ASSERT(voter.proxy.size() == - 0, "A proxy is currently set, please clear the proxy before voting for a witness."); + GOLOS_CHECK_LOGIC(voter.proxy.size() == 0, + logic_exception::cannot_vote_when_route_are_set, + "A proxy is currently set, please clear the proxy before voting for a witness."); if (o.approve) - FC_ASSERT(voter.can_vote, "Account has declined its voting rights."); + GOLOS_CHECK_LOGIC(voter.can_vote, + logic_exception::voter_declined_voting_rights, + "Account has declined its voting rights."); const auto &witness = _db.get_witness(o.witness); @@ -1091,11 +1105,15 @@ namespace golos { namespace chain { auto itr = by_account_witness_idx.find(boost::make_tuple(voter.id, witness.id)); if (itr == by_account_witness_idx.end()) { - FC_ASSERT(o.approve, "Vote doesn't exist, user must indicate a desire to approve witness."); + GOLOS_CHECK_LOGIC(o.approve, + logic_exception::witness_vote_does_not_exist, + "Vote doesn't exist, user must indicate a desire to approve witness."); if (_db.has_hardfork(STEEMIT_HARDFORK_0_2)) { - FC_ASSERT(voter.witnesses_voted_for < - STEEMIT_MAX_ACCOUNT_WITNESS_VOTES, "Account has voted for too many witnesses."); // TODO: Remove after hardfork 2 + GOLOS_CHECK_LOGIC(voter.witnesses_voted_for < STEEMIT_MAX_ACCOUNT_WITNESS_VOTES, + logic_exception::account_has_too_many_witness_votes, + "Account has voted for too many witnesses.", + ("max_votes", STEEMIT_MAX_ACCOUNT_WITNESS_VOTES)); // TODO: Remove after hardfork 2 _db.create([&](witness_vote_object &v) { v.witness = witness.id; @@ -1124,7 +1142,9 @@ namespace golos { namespace chain { }); } else { - FC_ASSERT(!o.approve, "Vote currently exists, user must indicate a desire to reject witness."); + GOLOS_CHECK_LOGIC(!o.approve, + logic_exception::witness_vote_already_exist, + "Vote currently exists, user must indicate a desire to reject witness."); if (_db.has_hardfork(STEEMIT_HARDFORK_0_2)) { if (_db.has_hardfork(STEEMIT_HARDFORK_0_3)) { @@ -1144,85 +1164,79 @@ namespace golos { namespace chain { } } - void vote_evaluator::do_apply(const vote_operation &o) { + void vote_evaluator::do_apply(const vote_operation& o) { try { - database &_db = db(); + const auto& comment = _db.get_comment(o.author, o.permlink); + const auto& voter = _db.get_account(o.voter); - const auto &comment = _db.get_comment(o.author, o.permlink); - const auto &voter = _db.get_account(o.voter); + GOLOS_CHECK_LOGIC(!(voter.owner_challenged || voter.active_challenged), + logic_exception::account_is_currently_challenged, + "Account \"${account}\" is currently challenged", ("account", voter.name)); - if (_db.has_hardfork(STEEMIT_HARDFORK_0_10)) - FC_ASSERT(!(voter.owner_challenged || - voter.active_challenged), "Operation cannot be processed because the account is currently challenged."); - - FC_ASSERT(voter.can_vote, "Voter has declined their voting rights."); - - if (o.weight > 0) - FC_ASSERT(comment.allow_votes, "Votes are not allowed on the comment."); - - if (_db.has_hardfork(STEEMIT_HARDFORK_0_12__177) && - _db.calculate_discussion_payout_time(comment) == - fc::time_point_sec::maximum() - ) { - if(!_db.clear_votes()) { - const auto& comment_vote_idx = _db.get_index< comment_vote_index >().indices().get< by_comment_voter >(); - auto itr = comment_vote_idx.find( std::make_tuple( comment.id, voter.id ) ); - - if( itr == comment_vote_idx.end() ) - _db.create< comment_vote_object >( [&]( comment_vote_object& cvo ) { - cvo.voter = voter.id; - cvo.comment = comment.id; - cvo.vote_percent = o.weight; - cvo.last_update = _db.head_block_time(); - }); - else - _db.modify( *itr, [&]( comment_vote_object& cvo ) { - cvo.vote_percent = o.weight; - cvo.last_update = _db.head_block_time(); + GOLOS_CHECK_LOGIC(voter.can_vote, logic_exception::voter_declined_voting_rights, + "Voter has declined their voting rights"); + + if (o.weight > 0) { + GOLOS_CHECK_LOGIC(comment.allow_votes, logic_exception::votes_are_not_allowed, + "Votes are not allowed on the comment."); + } + + if (_db.calculate_discussion_payout_time(comment) == fc::time_point_sec::maximum()) { + // non-consensus vote (after cashout) + const auto& comment_vote_idx = _db.get_index().indices().get(); + auto itr = comment_vote_idx.find(std::make_tuple(comment.id, voter.id)); + if (itr == comment_vote_idx.end()) { + _db.create([&](comment_vote_object& cvo) { + cvo.voter = voter.id; + cvo.comment = comment.id; + cvo.vote_percent = o.weight; + cvo.last_update = _db.head_block_time(); + cvo.num_changes = -1; // mark vote that it's ready to be removed (archived comment) + }); + } else { + _db.modify(*itr, [&](comment_vote_object& cvo) { + cvo.vote_percent = o.weight; + cvo.last_update = _db.head_block_time(); }); } return; } - const auto &comment_vote_idx = _db.get_index().indices().get(); + const auto& comment_vote_idx = _db.get_index().indices().get(); auto itr = comment_vote_idx.find(std::make_tuple(comment.id, voter.id)); - int64_t elapsed_seconds = (_db.head_block_time() - - voter.last_vote_time).to_seconds(); + int64_t elapsed_seconds = (_db.head_block_time() - voter.last_vote_time).to_seconds(); - if (_db.has_hardfork(STEEMIT_HARDFORK_0_11)) - FC_ASSERT(elapsed_seconds >= - STEEMIT_MIN_VOTE_INTERVAL_SEC, "Can only vote once every 3 seconds."); + GOLOS_CHECK_BANDWIDTH(_db.head_block_time(), voter.last_vote_time + STEEMIT_MIN_VOTE_INTERVAL_SEC-1, + bandwidth_exception::vote_bandwidth, "Can only vote once every 3 seconds."); int64_t regenerated_power = (STEEMIT_100_PERCENT * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS; - int64_t current_power = std::min(int64_t(voter.voting_power + - regenerated_power), int64_t(STEEMIT_100_PERCENT)); - FC_ASSERT(current_power > - 0, "Account currently does not have voting power."); + int64_t current_power = std::min( + int64_t(voter.voting_power + regenerated_power), + int64_t(STEEMIT_100_PERCENT)); + GOLOS_CHECK_LOGIC(current_power > 0, logic_exception::does_not_have_voting_power, + "Account currently does not have voting power."); int64_t abs_weight = abs(o.weight); - int64_t used_power = - (current_power * abs_weight) / STEEMIT_100_PERCENT; + int64_t used_power = (current_power * abs_weight) / STEEMIT_100_PERCENT; const dynamic_global_property_object &dgpo = _db.get_dynamic_global_properties(); // used_power = (current_power * abs_weight / STEEMIT_100_PERCENT) * (reserve / max_vote_denom) // The second multiplication is rounded up as of HF 259 int64_t max_vote_denom = dgpo.vote_regeneration_per_day * - STEEMIT_VOTE_REGENERATION_SECONDS / - (60 * 60 * 24); - FC_ASSERT(max_vote_denom > 0); + STEEMIT_VOTE_REGENERATION_SECONDS / (60 * 60 * 24); + GOLOS_ASSERT(max_vote_denom > 0, golos::internal_error, "max_vote_denom is too small"); if (!_db.has_hardfork(STEEMIT_HARDFORK_0_14__259)) { used_power = (used_power / max_vote_denom) + 1; } else { - used_power = - (used_power + max_vote_denom - 1) / max_vote_denom; + used_power = (used_power + max_vote_denom - 1) / max_vote_denom; } - FC_ASSERT(used_power <= - current_power, "Account does not have enough power to vote."); + GOLOS_CHECK_LOGIC(used_power <= current_power, logic_exception::does_not_have_voting_power, + "Account does not have enough power to vote."); int64_t abs_rshares = ( (uint128_t(voter.effective_vesting_shares().amount.value) * used_power) / @@ -1232,36 +1246,25 @@ namespace golos { namespace chain { } if (_db.has_hardfork(STEEMIT_HARDFORK_0_14__259)) { - FC_ASSERT(abs_rshares > 30000000 || o.weight == - 0, "Voting weight is too small, please accumulate more voting power or steem power."); + GOLOS_CHECK_LOGIC(abs_rshares > 30000000 || o.weight == 0, + logic_exception::voting_weight_is_too_small, + "Voting weight is too small, please accumulate more voting power or steem power."); } else if (_db.has_hardfork(STEEMIT_HARDFORK_0_13__248)) { - FC_ASSERT(abs_rshares > 30000000 || abs_rshares == - 1, "Voting weight is too small, please accumulate more voting power or steem power."); + GOLOS_CHECK_LOGIC(abs_rshares > 30000000 || abs_rshares == 1, + logic_exception::voting_weight_is_too_small, + "Voting weight is too small, please accumulate more voting power or steem power."); } - - // Lazily delete vote - if (itr != comment_vote_idx.end() && itr->num_changes == -1) { - if (_db.is_producing() || - _db.has_hardfork(STEEMIT_HARDFORK_0_12__177)) - FC_ASSERT(false, "Cannot vote again on a comment after payout."); - - _db.remove(*itr); - itr = comment_vote_idx.end(); - } - if (itr == comment_vote_idx.end()) { - FC_ASSERT(o.weight != 0, "Vote weight cannot be 0."); + GOLOS_CHECK_OP_PARAM(o, weight, GOLOS_CHECK_VALUE(o.weight != 0, "Vote weight cannot be 0")); /// this is the rshares voting for or against the post int64_t rshares = o.weight < 0 ? -abs_rshares : abs_rshares; - if (rshares > 0) { - if (_db.has_hardfork(STEEMIT_HARDFORK_0_7)) { - FC_ASSERT(_db.head_block_time() < - _db.calculate_discussion_payout_time(comment) - STEEMIT_UPVOTE_LOCKOUT, - "Cannot increase reward of post within the last minute before payout."); - } + GOLOS_CHECK_LOGIC(_db.head_block_time() < + _db.calculate_discussion_payout_time(comment) - STEEMIT_UPVOTE_LOCKOUT, + logic_exception::cannot_vote_within_last_minute_before_payout, + "Cannot increase reward of post within the last minute before payout."); } //used_power /= (50*7); /// a 100% vote means use .28% of voting power which should force users to spread their votes around over 50+ posts day for a week @@ -1296,7 +1299,8 @@ namespace golos { namespace chain { } - FC_ASSERT(abs_rshares > 0, "Cannot vote with 0 rshares."); + GOLOS_CHECK_LOGIC(abs_rshares > 0, logic_exception::cannot_vote_with_zero_rshares, + "Cannot vote with 0 rshares."); auto old_vote_rshares = comment.vote_rshares; @@ -1311,10 +1315,8 @@ namespace golos { namespace chain { } else { c.net_votes--; } - if (!_db.has_hardfork(STEEMIT_HARDFORK_0_6__114) && - c.net_rshares == -c.abs_rshares) - FC_ASSERT(c.net_votes < - 0, "Comment has negative network votes?"); + if (!_db.has_hardfork(STEEMIT_HARDFORK_0_6__114) && c.net_rshares == -c.abs_rshares) + GOLOS_ASSERT(c.net_votes < 0, golos::internal_error, "Comment has negative network votes?"); }); _db.modify(root, [&](comment_object &c) { @@ -1387,8 +1389,7 @@ namespace golos { namespace chain { rshares3 = rshares3 * rshares3 * rshares3; total2 *= total2; - cv.weight = static_cast( rshares3 / - total2 ); + cv.weight = static_cast(rshares3 / total2); } else {// cv.weight = W(R_1) - W(R_0) if (_db.has_hardfork(STEEMIT_HARDFORK_0_1)) { uint64_t old_weight = ( @@ -1441,35 +1442,33 @@ namespace golos { namespace chain { } }); - if (max_vote_weight) // Optimization - { - _db.modify(comment, [&](comment_object &c) { + if (max_vote_weight) { + // Optimization + _db.modify(comment, [&](comment_object& c) { c.total_vote_weight += max_vote_weight; }); } _db.adjust_rshares2(comment, old_rshares, new_rshares); } else { - FC_ASSERT(itr->num_changes < - STEEMIT_MAX_VOTE_CHANGES, "Voter has used the maximum number of vote changes on this comment."); - - if (_db.is_producing() || - _db.has_hardfork(STEEMIT_HARDFORK_0_6__112)) - FC_ASSERT(itr->vote_percent != - o.weight, "You have already voted in a similar way."); + GOLOS_CHECK_LOGIC(itr->num_changes < STEEMIT_MAX_VOTE_CHANGES, + logic_exception::voter_has_used_maximum_vote_changes, + "Voter has used the maximum number of vote changes on this comment."); + GOLOS_CHECK_LOGIC(itr->vote_percent != o.weight, + logic_exception::already_voted_in_similar_way, + "You have already voted in a similar way."); /// this is the rshares voting for or against the post int64_t rshares = o.weight < 0 ? -abs_rshares : abs_rshares; if (itr->rshares < rshares) { - if (_db.has_hardfork(STEEMIT_HARDFORK_0_7)) { - FC_ASSERT(_db.head_block_time() < - _db.calculate_discussion_payout_time(comment) - STEEMIT_UPVOTE_LOCKOUT, - "Cannot increase reward of post within the last minute before payout."); - } + GOLOS_CHECK_LOGIC(_db.head_block_time() < + _db.calculate_discussion_payout_time(comment) - STEEMIT_UPVOTE_LOCKOUT, + logic_exception::cannot_vote_within_last_minute_before_payout, + "Cannot increase reward of post within the last minute before payout."); } - _db.modify(voter, [&](account_object &a) { + _db.modify(voter, [&](account_object& a) { a.voting_power = current_power - used_power; a.last_vote_time = _db.head_block_time(); }); @@ -1481,7 +1480,7 @@ namespace golos { namespace chain { fc::uint128_t avg_cashout_sec = 0; - if (!_db.has_hardfork( STEEMIT_HARDFORK_0_17__431)) { + if (!_db.has_hardfork(STEEMIT_HARDFORK_0_17__431)) { fc::uint128_t cur_cashout_time_sec = _db.calculate_discussion_payout_time(comment).sec_since_epoch(); fc::uint128_t new_cashout_time_sec = _db.head_block_time().sec_since_epoch(); @@ -1525,20 +1524,19 @@ namespace golos { namespace chain { _db.modify(root, [&](comment_object &c) { c.children_abs_rshares += abs_rshares; - if (!_db.has_hardfork( STEEMIT_HARDFORK_0_17__431)) { + if (!_db.has_hardfork(STEEMIT_HARDFORK_0_17__431)) { if (_db.has_hardfork(STEEMIT_HARDFORK_0_12__177) && c.last_payout > fc::time_point_sec::min() ) { c.cashout_time = c.last_payout + STEEMIT_SECOND_CASHOUT_WINDOW; } else { - c.cashout_time = fc::time_point_sec( - std::min(uint32_t(avg_cashout_sec.to_uint64()), - c.max_cashout_time.sec_since_epoch())); + c.cashout_time = fc::time_point_sec(std::min( + uint32_t(avg_cashout_sec.to_uint64()), c.max_cashout_time.sec_since_epoch())); } if (c.max_cashout_time == fc::time_point_sec::maximum()) { c.max_cashout_time = - _db.head_block_time() + fc::seconds(STEEMIT_MAX_CASHOUT_WINDOW_SECONDS); + _db.head_block_time() + fc::seconds(STEEMIT_MAX_CASHOUT_WINDOW_SECONDS); } } }); @@ -1582,7 +1580,7 @@ namespace golos { namespace chain { } catch (const fc::exception &e) { if (d.is_producing()) { - throw e; + throw; } } catch (...) { @@ -1593,7 +1591,6 @@ namespace golos { namespace chain { void custom_binary_evaluator::do_apply(const custom_binary_operation &o) { database &d = db(); - FC_ASSERT(d.has_hardfork(STEEMIT_HARDFORK_0_14__317)); std::shared_ptr eval = d.get_custom_json_evaluator(o.id); if (!eval) { @@ -1605,7 +1602,7 @@ namespace golos { namespace chain { } catch (const fc::exception &e) { if (d.is_producing()) { - throw e; + throw; } } catch (...) { @@ -1622,9 +1619,9 @@ namespace golos { namespace chain { db.has_hardfork(STEEMIT_HARDFORK_0_5__59)) { const auto &witness_by_work = db.get_index().indices().get(); auto work_itr = witness_by_work.find(o.work.work); - if (work_itr != witness_by_work.end()) { - FC_ASSERT(!"DUPLICATE WORK DISCOVERED", "${w} ${witness}", ("w", o)("witness", *work_itr)); - } + GOLOS_CHECK_LOGIC(work_itr == witness_by_work.end(), + logic_exception::duplicate_work_discovered, + "Duplicate work discovered (${work} ${witness})", ("work", o)("witness", *work_itr)); } const auto& name = o.get_worker_account(); @@ -1654,23 +1651,30 @@ namespace golos { namespace chain { } const auto &worker_account = db.get_account(name); // verify it exists - const auto &worker_auth = db.get(name); - FC_ASSERT(worker_auth.active.num_auths() == - 1, "Miners can only have one key authority. ${a}", ("a", worker_auth.active)); - FC_ASSERT(worker_auth.active.key_auths.size() == - 1, "Miners may only have one key authority."); - FC_ASSERT(worker_auth.active.key_auths.begin()->first == - o.work.worker, "Work must be performed by key that signed the work."); - FC_ASSERT( - o.block_id == db.head_block_id(), "pow not for last block"); + const auto &worker_auth = db.get_authority(name); + GOLOS_CHECK_LOGIC(worker_auth.active.num_auths() == 1, + logic_exception::miners_can_only_have_one_key_authority, + "Miners can only have one key authority. ${a}", ("a", worker_auth.active)); + GOLOS_CHECK_LOGIC(worker_auth.active.key_auths.size() == 1, + logic_exception::miners_can_only_have_one_key_authority, + "Miners may only have one key authority."); + GOLOS_CHECK_LOGIC(worker_auth.active.key_auths.begin()->first == o.work.worker, + logic_exception::work_must_be_performed_by_signed_key, + "Work must be performed by key that signed the work."); + GOLOS_CHECK_LOGIC(o.block_id == db.head_block_id(), + logic_exception::work_not_for_last_block, + "pow not for last block"); + if (db.has_hardfork(STEEMIT_HARDFORK_0_13__256)) - FC_ASSERT(worker_account.last_account_update < - db.head_block_time(), "Worker account must not have updated their account this block."); + GOLOS_CHECK_LOGIC(worker_account.last_account_update < db.head_block_time(), + logic_exception::account_must_not_be_updated_in_this_block, + "Worker account must not have updated their account this block."); fc::sha256 target = db.get_pow_target(); - FC_ASSERT( - o.work.work < target, "Work lacks sufficient difficulty."); + GOLOS_CHECK_LOGIC(o.work.work < target, + logic_exception::insufficient_work_difficalty, + "Work lacks sufficient difficulty."); db.modify(dgp, [&](dynamic_global_property_object &p) { p.total_pow++; // make sure this doesn't break anything... @@ -1680,8 +1684,9 @@ namespace golos { namespace chain { const witness_object *cur_witness = db.find_witness(worker_account.name); if (cur_witness) { - FC_ASSERT(cur_witness->pow_worker == - 0, "This account is already scheduled for pow block production."); + GOLOS_CHECK_LOGIC(cur_witness->pow_worker == 0, + logic_exception::account_already_scheduled_for_work, + "This account is already scheduled for pow block production."); db.modify(*cur_witness, [&](witness_object &w) { w.props = o.props; w.pow_worker = dgp.total_pow; @@ -1713,7 +1718,9 @@ namespace golos { namespace chain { } void pow_evaluator::do_apply(const pow_operation &o) { - FC_ASSERT(!db().has_hardfork(STEEMIT_HARDFORK_0_13__256), "pow is deprecated. Use pow2 instead"); + if (db().has_hardfork(STEEMIT_HARDFORK_0_13__256)) { + FC_THROW_EXCEPTION(golos::unsupported_operation, "pow is deprecated. Use pow2 instead"); + } pow_apply(db(), o); } @@ -1726,26 +1733,32 @@ namespace golos { namespace chain { if (db.has_hardfork(STEEMIT_HARDFORK_0_16__551)) { const auto &work = o.work.get(); - FC_ASSERT(work.prev_block == - db.head_block_id(), "Equihash pow op not for last block"); + GOLOS_CHECK_LOGIC(work.prev_block == db.head_block_id(), + logic_exception::work_not_for_last_block, + "Equihash pow op not for last block"); auto recent_block_num = protocol::block_header::num_from_id(work.input.prev_block); - FC_ASSERT(recent_block_num > dgp.last_irreversible_block_num, + GOLOS_CHECK_LOGIC(recent_block_num > dgp.last_irreversible_block_num, + logic_exception::work_for_block_older_last_irreversible_block, "Equihash pow done for block older than last irreversible block num"); - FC_ASSERT(work.pow_summary < - target_pow, "Insufficient work difficulty. Work: ${w}, Target: ${t}", ("w", work.pow_summary)("t", target_pow)); + GOLOS_CHECK_LOGIC(work.pow_summary < target_pow, + logic_exception::insufficient_work_difficalty, + "Insufficient work difficulty. Work: ${w}, Target: ${t}", ("w", work.pow_summary)("t", target_pow)); worker_account = work.input.worker_account; } else { const auto &work = o.work.get(); - FC_ASSERT(work.input.prev_block == - db.head_block_id(), "Work not for last block"); - FC_ASSERT(work.pow_summary < - target_pow, "Insufficient work difficulty. Work: ${w}, Target: ${t}", ("w", work.pow_summary)("t", target_pow)); + GOLOS_CHECK_LOGIC(work.input.prev_block == db.head_block_id(), + logic_exception::work_not_for_last_block, + "Work not for last block"); + GOLOS_CHECK_LOGIC(work.pow_summary < target_pow, + logic_exception::insufficient_work_difficalty, + "Insufficient work difficulty. Work: ${w}, Target: ${t}", ("w", work.pow_summary)("t", target_pow)); worker_account = work.input.worker_account; } - FC_ASSERT(o.props.maximum_block_size >= - STEEMIT_MIN_BLOCK_SIZE_LIMIT * - 2, "Voted maximum block size is too small."); + GOLOS_CHECK_OP_PARAM(o, props.maximum_block_size, + GOLOS_CHECK_VALUE(o.props.maximum_block_size >= STEEMIT_MIN_BLOCK_SIZE_LIMIT * 2, + "Voted maximum block size is too small. Must be more then ${max} bytes.", + ("max", STEEMIT_MIN_BLOCK_SIZE_LIMIT * 2))); db.modify(dgp, [&](dynamic_global_property_object &p) { p.total_pow++; @@ -1755,7 +1768,8 @@ namespace golos { namespace chain { const auto &accounts_by_name = db.get_index().indices().get(); auto itr = accounts_by_name.find(worker_account); if (itr == accounts_by_name.end()) { - FC_ASSERT(o.new_owner_key.valid(), "New owner key is not valid."); + GOLOS_CHECK_OP_PARAM(o, new_owner_key, + GOLOS_CHECK_VALUE(o.new_owner_key.valid(), "Key is not valid.")); db.create([&](account_object &acc) { acc.name = worker_account; acc.memo_key = *o.new_owner_key; @@ -1779,11 +1793,16 @@ namespace golos { namespace chain { w.pow_worker = dgp.total_pow; }); } else { - FC_ASSERT(!o.new_owner_key.valid(), "Cannot specify an owner key unless creating account."); + GOLOS_CHECK_LOGIC(!o.new_owner_key.valid(), + logic_exception::cannot_specify_owner_key_unless_creating_account, + "Cannot specify an owner key unless creating account."); const witness_object *cur_witness = db.find_witness(worker_account); - FC_ASSERT(cur_witness, "Witness must be created for existing account before mining."); - FC_ASSERT(cur_witness->pow_worker == - 0, "This account is already scheduled for pow block production."); + GOLOS_CHECK_LOGIC(cur_witness, + logic_exception::witness_must_be_created_before_minning, + "Witness must be created for existing account before mining."); + GOLOS_CHECK_LOGIC(cur_witness->pow_worker == 0, + logic_exception::account_already_scheduled_for_work, + "This account is already scheduled for pow block production."); db.modify(*cur_witness, [&](witness_object &w) { w.props = o.props; w.pow_worker = dgp.total_pow; @@ -1801,7 +1820,6 @@ namespace golos { namespace chain { } void feed_publish_evaluator::do_apply(const feed_publish_operation &o) { - database &_db = db(); const auto &witness = _db.get_witness(o.publisher); _db.modify(witness, [&](witness_object &w) { w.sbd_exchange_rate = o.exchange_rate; @@ -1810,21 +1828,23 @@ namespace golos { namespace chain { } void convert_evaluator::do_apply(const convert_operation &o) { - database &_db = db(); const auto &owner = _db.get_account(o.owner); - FC_ASSERT(_db.get_balance(owner, o.amount.symbol) >= - o.amount, "Account does not have sufficient balance for conversion."); + GOLOS_CHECK_BALANCE(owner, MAIN_BALANCE, o.amount); _db.adjust_balance(owner, -o.amount); const auto &fhistory = _db.get_feed_history(); - FC_ASSERT(!fhistory.current_median_history.is_null(), "Cannot convert SBD because there is no price feed."); + GOLOS_CHECK_LOGIC(!fhistory.current_median_history.is_null(), + logic_exception::no_price_feed_yet, + "Cannot convert SBD because there is no price feed."); auto steem_conversion_delay = STEEMIT_CONVERSION_DELAY_PRE_HF_16; if (_db.has_hardfork(STEEMIT_HARDFORK_0_16__551)) { steem_conversion_delay = STEEMIT_CONVERSION_DELAY; } + GOLOS_CHECK_OBJECT_MISSING(_db, convert_request, o.owner, o.requestid); + _db.create([&](convert_request_object &obj) { obj.owner = o.owner; obj.requestid = o.requestid; @@ -1835,18 +1855,19 @@ namespace golos { namespace chain { } - void limit_order_create_evaluator::do_apply(const limit_order_create_operation &o) { - database &_db = db(); - FC_ASSERT(o.expiration > - _db.head_block_time(), "Limit order has to expire after head block time."); + void limit_order_create_evaluator::do_apply(const limit_order_create_operation& o) { + GOLOS_CHECK_OP_PARAM(o, expiration, { + GOLOS_CHECK_VALUE(o.expiration > _db.head_block_time(), + "Limit order has to expire after head block time."); + }); const auto &owner = _db.get_account(o.owner); - FC_ASSERT(_db.get_balance(owner, o.amount_to_sell.symbol) >= - o.amount_to_sell, "Account does not have sufficient funds for limit order."); - + GOLOS_CHECK_BALANCE(owner, MAIN_BALANCE, o.amount_to_sell); _db.adjust_balance(owner, -o.amount_to_sell); + GOLOS_CHECK_OBJECT_MISSING(_db, limit_order, o.owner, o.orderid); + const auto &order = _db.create([&](limit_order_object &obj) { obj.created = _db.head_block_time(); obj.seller = o.owner; @@ -1859,21 +1880,23 @@ namespace golos { namespace chain { bool filled = _db.apply_order(order); if (o.fill_or_kill) - FC_ASSERT(filled, "Cancelling order because it was not filled."); + GOLOS_CHECK_LOGIC(filled, logic_exception::cancelling_not_filled_order, + "Cancelling order because it was not filled."); } - void limit_order_create2_evaluator::do_apply(const limit_order_create2_operation &o) { - database &_db = db(); - FC_ASSERT(o.expiration > - _db.head_block_time(), "Limit order has to expire after head block time."); + void limit_order_create2_evaluator::do_apply(const limit_order_create2_operation& o) { + GOLOS_CHECK_OP_PARAM(o, expiration, { + GOLOS_CHECK_VALUE(o.expiration > _db.head_block_time(), + "Limit order has to expire after head block time."); + }); const auto &owner = _db.get_account(o.owner); - FC_ASSERT(_db.get_balance(owner, o.amount_to_sell.symbol) >= - o.amount_to_sell, "Account does not have sufficient funds for limit order."); - + GOLOS_CHECK_BALANCE(owner, MAIN_BALANCE, o.amount_to_sell); _db.adjust_balance(owner, -o.amount_to_sell); + GOLOS_CHECK_OBJECT_MISSING(_db, limit_order, o.owner, o.orderid); + const auto &order = _db.create([&](limit_order_object &obj) { obj.created = _db.head_block_time(); obj.seller = o.owner; @@ -1886,33 +1909,34 @@ namespace golos { namespace chain { bool filled = _db.apply_order(order); if (o.fill_or_kill) - FC_ASSERT(filled, "Cancelling order because it was not filled."); + GOLOS_CHECK_LOGIC(filled, logic_exception::cancelling_not_filled_order, + "Cancelling order because it was not filled."); } void limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation &o) { - database &_db = db(); _db.cancel_order(_db.get_limit_order(o.owner, o.orderid)); } void report_over_production_evaluator::do_apply(const report_over_production_operation &o) { - database &_db = db(); - FC_ASSERT(!_db.has_hardfork(STEEMIT_HARDFORK_0_4), "report_over_production_operation is disabled."); + if (_db.has_hardfork(STEEMIT_HARDFORK_0_4)) { + FC_THROW_EXCEPTION(golos::unsupported_operation, "report_over_production_operation is disabled"); + } } - void challenge_authority_evaluator::do_apply(const challenge_authority_operation &o) { - database &_db = db(); + void challenge_authority_evaluator::do_apply(const challenge_authority_operation& o) { if (_db.has_hardfork(STEEMIT_HARDFORK_0_14__307)) - FC_ASSERT(false, "Challenge authority operation is currently disabled."); - const auto &challenged = _db.get_account(o.challenged); - const auto &challenger = _db.get_account(o.challenger); + GOLOS_ASSERT(false, golos::unsupported_operation, "Challenge authority operation is currently disabled."); + // TODO: update error handling if enable this operation + + const auto& challenged = _db.get_account(o.challenged); + const auto& challenger = _db.get_account(o.challenger); if (o.require_owner) { - FC_ASSERT(challenged.reset_account == - o.challenger, "Owner authority can only be challenged by its reset account."); + FC_ASSERT(challenged.reset_account == o.challenger, + "Owner authority can only be challenged by its reset account."); FC_ASSERT(challenger.balance >= STEEMIT_OWNER_CHALLENGE_FEE); FC_ASSERT(!challenged.owner_challenged); - FC_ASSERT(_db.head_block_time() - challenged.last_owner_proved > - STEEMIT_OWNER_CHALLENGE_COOLDOWN); + FC_ASSERT(_db.head_block_time() - challenged.last_owner_proved > STEEMIT_OWNER_CHALLENGE_COOLDOWN); _db.adjust_balance(challenger, -STEEMIT_OWNER_CHALLENGE_FEE); _db.create_vesting(_db.get_account(o.challenged), STEEMIT_OWNER_CHALLENGE_FEE); @@ -1921,32 +1945,31 @@ namespace golos { namespace chain { a.owner_challenged = true; }); } else { - FC_ASSERT(challenger.balance >= - STEEMIT_ACTIVE_CHALLENGE_FEE, "Account does not have sufficient funds to pay challenge fee."); - FC_ASSERT(!(challenged.owner_challenged || - challenged.active_challenged), "Account is already challenged."); - FC_ASSERT( - _db.head_block_time() - challenged.last_active_proved > - STEEMIT_ACTIVE_CHALLENGE_COOLDOWN, "Account cannot be challenged because it was recently challenged."); + FC_ASSERT(challenger.balance >= STEEMIT_ACTIVE_CHALLENGE_FEE, + "Account does not have sufficient funds to pay challenge fee."); + FC_ASSERT(!(challenged.owner_challenged || challenged.active_challenged), + "Account is already challenged."); + FC_ASSERT(_db.head_block_time() - challenged.last_active_proved > STEEMIT_ACTIVE_CHALLENGE_COOLDOWN, + "Account cannot be challenged because it was recently challenged."); _db.adjust_balance(challenger, -STEEMIT_ACTIVE_CHALLENGE_FEE); _db.create_vesting(_db.get_account(o.challenged), STEEMIT_ACTIVE_CHALLENGE_FEE); - _db.modify(challenged, [&](account_object &a) { + _db.modify(challenged, [&](account_object& a) { a.active_challenged = true; }); } } - void prove_authority_evaluator::do_apply(const prove_authority_operation &o) { - database &_db = db(); - const auto &challenged = _db.get_account(o.challenged); - FC_ASSERT(challenged.owner_challenged || - challenged.active_challenged, "Account is not challeneged. No need to prove authority."); + void prove_authority_evaluator::do_apply(const prove_authority_operation& o) { + const auto& challenged = _db.get_account(o.challenged); + GOLOS_CHECK_LOGIC(challenged.owner_challenged || challenged.active_challenged, + logic_exception::account_is_not_challeneged, + "Account is not challeneged. No need to prove authority."); - _db.modify(challenged, [&](account_object &a) { + _db.modify(challenged, [&](account_object& a) { a.active_challenged = false; - a.last_active_proved = _db.head_block_time(); + a.last_active_proved = _db.head_block_time(); // TODO: if enable `challenge_authority` then check, is it ok to set active always if (o.require_owner) { a.owner_challenged = false; a.last_owner_proved = _db.head_block_time(); @@ -1954,214 +1977,208 @@ namespace golos { namespace chain { }); } - void request_account_recovery_evaluator::do_apply(const request_account_recovery_operation &o) { - database &_db = db(); - const auto &account_to_recover = _db.get_account(o.account_to_recover); - - if (account_to_recover.recovery_account.length()) // Make sure recovery matches expected recovery account - FC_ASSERT(account_to_recover.recovery_account == - o.recovery_account, "Cannot recover an account that does not have you as there recovery partner."); - else // Empty string recovery account defaults to top witness - FC_ASSERT( - _db.get_index().indices().get().begin()->owner == - o.recovery_account, "Top witness must recover an account with no recovery partner."); + void request_account_recovery_evaluator::do_apply(const request_account_recovery_operation& o) { + const auto& account_to_recover = _db.get_account(o.account_to_recover); + if (account_to_recover.recovery_account.length()) { + // Make sure recovery matches expected recovery account + GOLOS_CHECK_LOGIC(account_to_recover.recovery_account == o.recovery_account, + logic_exception::cannot_recover_if_not_partner, + "Cannot recover an account that does not have you as there recovery partner."); + } else { + // Empty string recovery account defaults to top witness + GOLOS_CHECK_LOGIC( + _db.get_index().indices().get().begin()->owner == o.recovery_account, + logic_exception::must_be_recovered_by_top_witness, + "Top witness must recover an account with no recovery partner."); + } - const auto &recovery_request_idx = _db.get_index().indices().get(); + const auto& recovery_request_idx = + _db.get_index().indices().get(); auto request = recovery_request_idx.find(o.account_to_recover); - if (request == recovery_request_idx.end()) // New Request - { - FC_ASSERT(!o.new_owner_authority.is_impossible(), "Cannot recover using an impossible authority."); - FC_ASSERT(o.new_owner_authority.weight_threshold, "Cannot recover using an open authority."); - + if (request == recovery_request_idx.end()) { + // New Request + GOLOS_CHECK_OP_PARAM(o, new_owner_authority, { + GOLOS_CHECK_VALUE(!o.new_owner_authority.is_impossible(), + "Cannot recover using an impossible authority."); + GOLOS_CHECK_VALUE(o.new_owner_authority.weight_threshold, + "Cannot recover using an open authority."); + }); // Check accounts in the new authority exist - if ((_db.has_hardfork(STEEMIT_HARDFORK_0_15__465) || - _db.is_producing())) { - for (auto &a : o.new_owner_authority.account_auths) { + if (_db.has_hardfork(STEEMIT_HARDFORK_0_15__465)) { + for (auto& a : o.new_owner_authority.account_auths) { _db.get_account(a.first); } } - - _db.create([&](account_recovery_request_object &req) { + _db.create([&](account_recovery_request_object& req) { req.account_to_recover = o.account_to_recover; req.new_owner_authority = o.new_owner_authority; - req.expires = _db.head_block_time() + - STEEMIT_ACCOUNT_RECOVERY_REQUEST_EXPIRATION_PERIOD; + req.expires = _db.head_block_time() + STEEMIT_ACCOUNT_RECOVERY_REQUEST_EXPIRATION_PERIOD; }); - } else if (o.new_owner_authority.weight_threshold == - 0) // Cancel Request if authority is open - { + } else if (o.new_owner_authority.weight_threshold == 0) { + // Cancel Request if authority is open _db.remove(*request); - } else // Change Request - { - FC_ASSERT(!o.new_owner_authority.is_impossible(), "Cannot recover using an impossible authority."); - + } else { + // Change Request + GOLOS_CHECK_OP_PARAM(o, new_owner_authority, { + GOLOS_CHECK_VALUE(!o.new_owner_authority.is_impossible(), + "Cannot recover using an impossible authority."); + }); // Check accounts in the new authority exist - if ((_db.has_hardfork(STEEMIT_HARDFORK_0_15__465) || - _db.is_producing())) { - for (auto &a : o.new_owner_authority.account_auths) { + if (_db.has_hardfork(STEEMIT_HARDFORK_0_15__465)) { + for (auto& a : o.new_owner_authority.account_auths) { _db.get_account(a.first); } } - - _db.modify(*request, [&](account_recovery_request_object &req) { + _db.modify(*request, [&](account_recovery_request_object& req) { req.new_owner_authority = o.new_owner_authority; - req.expires = _db.head_block_time() + - STEEMIT_ACCOUNT_RECOVERY_REQUEST_EXPIRATION_PERIOD; + req.expires = _db.head_block_time() + STEEMIT_ACCOUNT_RECOVERY_REQUEST_EXPIRATION_PERIOD; }); } } - void recover_account_evaluator::do_apply(const recover_account_operation &o) { - database &_db = db(); - const auto &account = _db.get_account(o.account_to_recover); + void recover_account_evaluator::do_apply(const recover_account_operation& o) { + const auto& account = _db.get_account(o.account_to_recover); + const auto now = _db.head_block_time(); - if (_db.has_hardfork(STEEMIT_HARDFORK_0_12)) - FC_ASSERT( - _db.head_block_time() - account.last_account_recovery > - STEEMIT_OWNER_UPDATE_LIMIT, "Owner authority can only be updated once an hour."); + if (_db.has_hardfork(STEEMIT_HARDFORK_0_12)) { + GOLOS_CHECK_BANDWIDTH(now, account.last_account_recovery + STEEMIT_OWNER_UPDATE_LIMIT, + bandwidth_exception::change_owner_authority_bandwidth, + "Owner authority can only be updated once an hour."); + } - const auto &recovery_request_idx = _db.get_index().indices().get(); + const auto& recovery_request_idx = _db.get_index().indices().get(); auto request = recovery_request_idx.find(o.account_to_recover); - FC_ASSERT(request != - recovery_request_idx.end(), "There are no active recovery requests for this account."); - FC_ASSERT(request->new_owner_authority == - o.new_owner_authority, "New owner authority does not match recovery request."); + GOLOS_CHECK_LOGIC(request != recovery_request_idx.end(), + logic_exception::no_active_recovery_request, + "There are no active recovery requests for this account."); + GOLOS_CHECK_LOGIC(request->new_owner_authority == o.new_owner_authority, + logic_exception::authority_does_not_match_request, + "New owner authority does not match recovery request."); - const auto &recent_auth_idx = _db.get_index().indices().get(); + const auto& recent_auth_idx = _db.get_index().indices().get(); auto hist = recent_auth_idx.lower_bound(o.account_to_recover); bool found = false; - while (hist != recent_auth_idx.end() && - hist->account == o.account_to_recover && !found) { - found = hist->previous_owner_authority == - o.recent_owner_authority; + while (hist != recent_auth_idx.end() && hist->account == o.account_to_recover && !found) { + found = hist->previous_owner_authority == o.recent_owner_authority; if (found) { break; } ++hist; } - - FC_ASSERT(found, "Recent authority not found in authority history."); + GOLOS_CHECK_LOGIC(found, logic_exception::no_recent_authority_in_history, + "Recent authority not found in authority history."); _db.remove(*request); // Remove first, update_owner_authority may invalidate iterator _db.update_owner_authority(account, o.new_owner_authority); - _db.modify(account, [&](account_object &a) { - a.last_account_recovery = _db.head_block_time(); + _db.modify(account, [&](account_object& a) { + a.last_account_recovery = now; }); } - void change_recovery_account_evaluator::do_apply(const change_recovery_account_operation &o) { - database &_db = db(); + void change_recovery_account_evaluator::do_apply(const change_recovery_account_operation& o) { _db.get_account(o.new_recovery_account); // Simply validate account exists - const auto &account_to_recover = _db.get_account(o.account_to_recover); - - const auto &change_recovery_idx = _db.get_index().indices().get(); + const auto& account_to_recover = _db.get_account(o.account_to_recover); + const auto& change_recovery_idx = + _db.get_index().indices().get(); auto request = change_recovery_idx.find(o.account_to_recover); - if (request == change_recovery_idx.end()) // New request - { - _db.create([&](change_recovery_account_request_object &req) { + if (request == change_recovery_idx.end()) { + // New request + _db.create([&](change_recovery_account_request_object& req) { req.account_to_recover = o.account_to_recover; req.recovery_account = o.new_recovery_account; - req.effective_on = _db.head_block_time() + - STEEMIT_OWNER_AUTH_RECOVERY_PERIOD; + req.effective_on = _db.head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD; }); - } else if (account_to_recover.recovery_account != - o.new_recovery_account) // Change existing request - { - _db.modify(*request, [&](change_recovery_account_request_object &req) { + } else if (account_to_recover.recovery_account != o.new_recovery_account) { + // Change existing request + _db.modify(*request, [&](change_recovery_account_request_object& req) { req.recovery_account = o.new_recovery_account; - req.effective_on = _db.head_block_time() + - STEEMIT_OWNER_AUTH_RECOVERY_PERIOD; + req.effective_on = _db.head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD; }); - } else // Request exists and changing back to current recovery account - { + } else { + // Request exists and changing back to current recovery account _db.remove(*request); } } - void transfer_to_savings_evaluator::do_apply(const transfer_to_savings_operation &op) { - database &_db = db(); - const auto &from = _db.get_account(op.from); - const auto &to = _db.get_account(op.to); - FC_ASSERT(_db.get_balance(from, op.amount.symbol) >= - op.amount, "Account does not have sufficient funds to transfer to savings."); + void transfer_to_savings_evaluator::do_apply(const transfer_to_savings_operation& op) { + const auto& from = _db.get_account(op.from); + const auto& to = _db.get_account(op.to); + + GOLOS_CHECK_BALANCE(from, MAIN_BALANCE, op.amount); _db.adjust_balance(from, -op.amount); _db.adjust_savings_balance(to, op.amount); } - void transfer_from_savings_evaluator::do_apply(const transfer_from_savings_operation &op) { - database &_db = db(); - const auto &from = _db.get_account(op.from); - _db.get_account(op.to); // Verify to account exists + void transfer_from_savings_evaluator::do_apply(const transfer_from_savings_operation& op) { + const auto& from = _db.get_account(op.from); + _db.get_account(op.to); // Verify `to` account exists - FC_ASSERT(from.savings_withdraw_requests < - STEEMIT_SAVINGS_WITHDRAW_REQUEST_LIMIT, "Account has reached limit for pending withdraw requests."); + GOLOS_CHECK_LOGIC(from.savings_withdraw_requests < STEEMIT_SAVINGS_WITHDRAW_REQUEST_LIMIT, + golos::logic_exception::reached_limit_for_pending_withdraw_requests, + "Account has reached limit for pending withdraw requests.", + ("limit",STEEMIT_SAVINGS_WITHDRAW_REQUEST_LIMIT)); - FC_ASSERT(_db.get_savings_balance(from, op.amount.symbol) >= - op.amount); + GOLOS_CHECK_BALANCE(from, SAVINGS, op.amount); _db.adjust_savings_balance(from, -op.amount); - _db.create([&](savings_withdraw_object &s) { + + GOLOS_CHECK_OBJECT_MISSING(_db, savings_withdraw, op.from, op.request_id); + + _db.create([&](savings_withdraw_object& s) { s.from = op.from; s.to = op.to; s.amount = op.amount; -#ifndef IS_LOW_MEM - from_string(s.memo, op.memo); -#endif + if (_db.store_memo_in_savings_withdraws()) { + from_string(s.memo, op.memo); + } s.request_id = op.request_id; - s.complete = - _db.head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME; + s.complete = _db.head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME; }); - - _db.modify(from, [&](account_object &a) { + _db.modify(from, [&](account_object& a) { a.savings_withdraw_requests++; }); } - void cancel_transfer_from_savings_evaluator::do_apply(const cancel_transfer_from_savings_operation &op) { - database &_db = db(); - const auto &swo = _db.get_savings_withdraw(op.from, op.request_id); - _db.adjust_savings_balance(_db.get_account(swo.from), swo.amount); + void cancel_transfer_from_savings_evaluator::do_apply(const cancel_transfer_from_savings_operation& op) { + const auto& name = op.from; + const auto& from = _db.get_account(name); + const auto& swo = _db.get_savings_withdraw(name, op.request_id); + _db.adjust_savings_balance(from, swo.amount); _db.remove(swo); - - const auto &from = _db.get_account(op.from); - _db.modify(from, [&](account_object &a) { + _db.modify(from, [&](account_object& a) { a.savings_withdraw_requests--; }); } - void decline_voting_rights_evaluator::do_apply(const decline_voting_rights_operation &o) { - database &_db = db(); - FC_ASSERT(_db.has_hardfork(STEEMIT_HARDFORK_0_14__324)); - - const auto &account = _db.get_account(o.account); - const auto &request_idx = _db.get_index().indices().get(); + void decline_voting_rights_evaluator::do_apply(const decline_voting_rights_operation& o) { + const auto& account = _db.get_account(o.account); + const auto& request_idx = _db.get_index().indices().get(); auto itr = request_idx.find(account.id); + auto exist = itr != request_idx.end(); if (o.decline) { - FC_ASSERT(itr == - request_idx.end(), "Cannot create new request because one already exists."); - - _db.create([&](decline_voting_rights_request_object &req) { + if (exist) { + GOLOS_THROW_OBJECT_ALREADY_EXIST("decline_voting_rights_request", o.account); + } + _db.create([&](decline_voting_rights_request_object& req) { req.account = account.id; - req.effective_date = _db.head_block_time() + - STEEMIT_OWNER_AUTH_RECOVERY_PERIOD; + req.effective_date = _db.head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD; }); } else { - FC_ASSERT(itr != - request_idx.end(), "Cannot cancel the request because it does not exist."); + if (!exist) { + GOLOS_THROW_MISSING_OBJECT("decline_voting_rights_request", o.account); + } _db.remove(*itr); } } - void reset_account_evaluator::do_apply(const reset_account_operation &op) { - FC_ASSERT(false, "Reset Account Operation is currently disabled."); -/* - database& _db = db(); - const auto& acnt = _db.get_account(op.account_to_reset); + void reset_account_evaluator::do_apply(const reset_account_operation& op) { + GOLOS_ASSERT(false, golos::unsupported_operation, "Reset Account Operation is currently disabled."); +/* const auto& acnt = _db.get_account(op.account_to_reset); auto band = _db.find(std::make_tuple(op.account_to_reset, bandwidth_type::old_forum)); if (band != nullptr) FC_ASSERT((_db.head_block_time() - band->last_bandwidth_update) > fc::days(60), @@ -2171,11 +2188,9 @@ namespace golos { namespace chain { */ } - void set_reset_account_evaluator::do_apply(const set_reset_account_operation &op) { - FC_ASSERT(false, "Set Reset Account Operation is currently disabled."); -/* - database& _db = db(); - const auto& acnt = _db.get_account(op.account); + void set_reset_account_evaluator::do_apply(const set_reset_account_operation& op) { + GOLOS_ASSERT(false, golos::unsupported_operation, "Set Reset Account Operation is currently disabled."); +/* const auto& acnt = _db.get_account(op.account); _db.get_account(op.reset_account); FC_ASSERT(acnt.reset_account == op.current_reset_account, @@ -2189,8 +2204,6 @@ namespace golos { namespace chain { } void delegate_vesting_shares_evaluator::do_apply(const delegate_vesting_shares_operation& op) { - ASSERT_REQ_HF(STEEMIT_HARDFORK_0_18__535, "delegate_vesting_shares_operation"); //TODO: Delete after hardfork - const auto& delegator = _db.get_account(op.delegator); const auto& delegatee = _db.get_account(op.delegatee); auto delegation = _db.find(std::make_tuple(op.delegator, op.delegatee)); @@ -2206,31 +2219,37 @@ namespace golos { namespace chain { op.vesting_shares; auto increasing = delta.amount > 0; - FC_ASSERT((increasing ? delta : -delta) >= min_update, - "Delegation difference is not enough. min_update: ${min}", ("min", min_update)); + GOLOS_CHECK_OP_PARAM(op, vesting_shares, { + GOLOS_CHECK_LOGIC((increasing ? delta : -delta) >= min_update, + logic_exception::delegation_difference_too_low, + "Delegation difference is not enough. min_update: ${min}", ("min", min_update)); #ifdef STEEMIT_BUILD_TESTNET - // min_update depends on account_creation_fee, which can be 0 on testnet - FC_ASSERT(delta.amount != 0, "Delegation difference can't be 0"); + // min_update depends on account_creation_fee, which can be 0 on testnet + GOLOS_CHECK_LOGIC(delta.amount != 0, + logic_exception::delegation_difference_too_low, + "Delegation difference can't be 0"); #endif + }); if (increasing) { auto delegated = delegator.delegated_vesting_shares; - FC_ASSERT(delegator.available_vesting_shares(true) >= delta, - "Account does not have enough vesting shares to delegate.", - ("available", delegator.available_vesting_shares(true)) - ("delta", delta)("vesting_shares", delegator.vesting_shares)("delegated", delegated) - ("to_withdraw", delegator.to_withdraw)("withdrawn", delegator.withdrawn)); + GOLOS_CHECK_BALANCE(delegator, AVAILABLE_VESTING, delta); auto elapsed_seconds = (now - delegator.last_vote_time).to_seconds(); auto regenerated_power = (STEEMIT_100_PERCENT * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS; auto current_power = std::min(delegator.voting_power + regenerated_power, STEEMIT_100_PERCENT); auto max_allowed = delegator.vesting_shares * current_power / STEEMIT_100_PERCENT; - FC_ASSERT(delegated + delta <= max_allowed, + GOLOS_CHECK_LOGIC(delegated + delta <= max_allowed, + logic_exception::delegation_limited_by_voting_power, "Account allowed to delegate a maximum of ${v} with current voting power = ${p}", ("v",max_allowed)("p",current_power)("delegated",delegated)("delta",delta)); if (!delegation) { - FC_ASSERT(op.vesting_shares >= min_delegation, - "Account must delegate a minimum of ${v}", ("v",min_delegation)("vesting_shares",op.vesting_shares)); + GOLOS_CHECK_OP_PARAM(op, vesting_shares, { + GOLOS_CHECK_LOGIC(op.vesting_shares >= min_delegation, + logic_exception::cannot_delegate_below_minimum, + "Account must delegate a minimum of ${v}", + ("v",min_delegation)("vesting_shares",op.vesting_shares)); + }); _db.create([&](vesting_delegation_object& o) { o.delegator = op.delegator; o.delegatee = op.delegatee; @@ -2242,9 +2261,12 @@ namespace golos { namespace chain { a.delegated_vesting_shares += delta; }); } else { - FC_ASSERT(op.vesting_shares.amount == 0 || op.vesting_shares >= min_delegation, - "Delegation must be removed or leave minimum delegation amount of ${v}", - ("v",min_delegation)("vesting_shares",op.vesting_shares)); + GOLOS_CHECK_OP_PARAM(op, vesting_shares, { + GOLOS_CHECK_LOGIC(op.vesting_shares.amount == 0 || op.vesting_shares >= min_delegation, + logic_exception::cannot_delegate_below_minimum, + "Delegation must be removed or leave minimum delegation amount of ${v}", + ("v",min_delegation)("vesting_shares",op.vesting_shares)); + }); _db.create([&](vesting_delegation_expiration_object& o) { o.delegator = op.delegator; o.vesting_shares = -delta; diff --git a/libraries/network/include/golos/network/exceptions.hpp b/libraries/network/include/golos/network/exceptions.hpp index b31eed0515..ff185c2d7f 100644 --- a/libraries/network/include/golos/network/exceptions.hpp +++ b/libraries/network/include/golos/network/exceptions.hpp @@ -32,10 +32,6 @@ namespace golos { FC_DECLARE_EXCEPTION(net_exception, 90000, "P2P Networking Exception"); - FC_DECLARE_DERIVED_EXCEPTION(send_queue_overflow, golos::network::net_exception, 90001, "send queue for this peer exceeded maximum size"); - - FC_DECLARE_DERIVED_EXCEPTION(insufficient_relay_fee, golos::network::net_exception, 90002, "insufficient relay fee"); - FC_DECLARE_DERIVED_EXCEPTION(already_connected_to_requested_peer, golos::network::net_exception, 90003, "already connected to requested peer"); FC_DECLARE_DERIVED_EXCEPTION(block_older_than_undo_history, golos::network::net_exception, 90004, "block is older than our undo history allows us to process"); diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index 607c947c96..fb812ab878 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -1,4 +1,5 @@ #include +#include /* @@ -65,18 +66,19 @@ namespace golos { auto space_pos = s.find(" "); auto dot_pos = s.find("."); - FC_ASSERT(space_pos != std::string::npos); + GOLOS_CHECK_VALUE(space_pos != std::string::npos, "Invalid asset notation"); asset result; result.symbol = uint64_t(0); auto sy = (char *)&result.symbol; if (dot_pos != std::string::npos) { - FC_ASSERT(space_pos > dot_pos); + GOLOS_CHECK_VALUE(space_pos > dot_pos, "Invalid asset notation"); auto intpart = s.substr(0, dot_pos); auto fractpart = "1" + s.substr( dot_pos + 1, space_pos - dot_pos - 1); + GOLOS_CHECK_VALUE(fractpart.size() - 1 < 15, "Asset fraction part is too long"); result.set_decimals(fractpart.size() - 1); result.amount = fc::to_int64(intpart); @@ -92,7 +94,7 @@ namespace golos { size_t symbol_size = symbol.size(); if (symbol_size > 0) { - FC_ASSERT(symbol_size <= 6); + GOLOS_CHECK_VALUE(symbol_size <= 6, "Asset symbol is too long"); memcpy(sy + 1, symbol.c_str(), symbol_size); } @@ -194,9 +196,9 @@ namespace golos { void price::validate() const { try { - FC_ASSERT(base.amount > share_type(0)); - FC_ASSERT(quote.amount > share_type(0)); - FC_ASSERT(base.symbol_name() != quote.symbol_name()); + GOLOS_CHECK_VALUE(base.amount > share_type(0), "Amount of base must be positive"); + GOLOS_CHECK_VALUE(quote.amount > share_type(0), "Amount of quote must be positive"); + GOLOS_CHECK_VALUE(base.symbol_name() != quote.symbol_name(), "Symbols of base and quote must be different"); } FC_CAPTURE_AND_RETHROW((base)(quote)) } diff --git a/libraries/protocol/authority.cpp b/libraries/protocol/authority.cpp index d6d4e415f0..2bb95d9e69 100644 --- a/libraries/protocol/authority.cpp +++ b/libraries/protocol/authority.cpp @@ -1,21 +1,22 @@ #include +#include -namespace golos { - namespace protocol { + +namespace golos { namespace protocol { // authority methods - void authority::add_authority(const public_key_type &k, weight_type w) { + void authority::add_authority(const public_key_type& k, weight_type w) { key_auths[k] = w; } - void authority::add_authority(const account_name_type &k, weight_type w) { + void authority::add_authority(const account_name_type& k, weight_type w) { account_auths[k] = w; } vector authority::get_keys() const { vector result; result.reserve(key_auths.size()); - for (const auto &k : key_auths) { + for (const auto& k : key_auths) { result.push_back(k.first); } return result; @@ -23,10 +24,10 @@ namespace golos { bool authority::is_impossible() const { uint64_t auth_weights = 0; - for (const auto &item : account_auths) { + for (const auto& item : account_auths) { auth_weights += item.second; } - for (const auto &item : key_auths) { + for (const auto& item : key_auths) { auth_weights += item.second; } return auth_weights < weight_threshold; @@ -43,12 +44,16 @@ namespace golos { void authority::validate() const { for (const auto &item : account_auths) { - FC_ASSERT(is_valid_account_name(item.first)); + GOLOS_CHECK_VALUE(is_valid_account_name(item.first), "Account name \"${account}\" is invalid", ("account",item.first)); } } - bool is_valid_account_name(const string &name) { +// local inlines to simplify validation checks +inline bool is_letter(char x) { return 'a' <= x && x <= 'z'; } // lowercase only +inline bool is_digit (char x) { return '0' <= x && x <= '9'; } + + bool is_valid_account_name(const string& name) { #if STEEMIT_MIN_ACCOUNT_NAME_LENGTH < 3 #error This is_valid_account_name implementation implicitly enforces minimum name length of 3. #endif @@ -71,121 +76,18 @@ namespace golos { if (end - begin < 3) { return false; } - switch (name[begin]) { - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - case 'g': - case 'h': - case 'i': - case 'j': - case 'k': - case 'l': - case 'm': - case 'n': - case 'o': - case 'p': - case 'q': - case 'r': - case 's': - case 't': - case 'u': - case 'v': - case 'w': - case 'x': - case 'y': - case 'z': - break; - default: - return false; + if (!is_letter(name[begin])) { + return false; } - switch (name[end - 1]) { - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - case 'g': - case 'h': - case 'i': - case 'j': - case 'k': - case 'l': - case 'm': - case 'n': - case 'o': - case 'p': - case 'q': - case 'r': - case 's': - case 't': - case 'u': - case 'v': - case 'w': - case 'x': - case 'y': - case 'z': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - break; - default: - return false; + auto t = name[end - 1]; + if (!is_letter(t) && !is_digit(t)) { + return false; } for (size_t i = begin + 1; i < end - 1; i++) { - switch (name[i]) { - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - case 'g': - case 'h': - case 'i': - case 'j': - case 'k': - case 'l': - case 'm': - case 'n': - case 'o': - case 'p': - case 'q': - case 'r': - case 's': - case 't': - case 'u': - case 'v': - case 'w': - case 'x': - case 'y': - case 'z': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - break; - default: - return false; - } + t = name[i]; + if (is_letter(t) || is_digit(t) || t == '-') + continue; + return false; } if (end == len) { break; @@ -195,11 +97,10 @@ namespace golos { return true; } - bool operator==(const authority &a, const authority &b) { + bool operator==(const authority& a, const authority& b) { return (a.weight_threshold == b.weight_threshold) && (a.account_auths == b.account_auths) && (a.key_auths == b.key_auths); } - } -} // golos::protocol +} } // golos::protocol diff --git a/libraries/protocol/include/golos/protocol/block.hpp b/libraries/protocol/include/golos/protocol/block.hpp index 19c19d0f17..9ea860d8c9 100644 --- a/libraries/protocol/include/golos/protocol/block.hpp +++ b/libraries/protocol/include/golos/protocol/block.hpp @@ -3,16 +3,14 @@ #include #include -namespace golos { - namespace protocol { +namespace golos { namespace protocol { - struct signed_block : public signed_block_header { - checksum_type calculate_merkle_root() const; +struct signed_block : public signed_block_header { + checksum_type calculate_merkle_root() const; - vector transactions; - }; + vector transactions; +}; - } -} // golos::protocol +} } // golos::protocol FC_REFLECT_DERIVED((golos::protocol::signed_block), ((golos::protocol::signed_block_header)), (transactions)) diff --git a/libraries/protocol/include/golos/protocol/config.hpp b/libraries/protocol/include/golos/protocol/config.hpp index 54a08bd17a..607ad26178 100644 --- a/libraries/protocol/include/golos/protocol/config.hpp +++ b/libraries/protocol/include/golos/protocol/config.hpp @@ -3,7 +3,7 @@ */ #pragma once -#define STEEMIT_BLOCKCHAIN_VERSION (version(0, 18, 3)) +#define STEEMIT_BLOCKCHAIN_VERSION (version(0, 18, 4)) #define STEEMIT_BLOCKCHAIN_HARDFORK_VERSION (hardfork_version(STEEMIT_BLOCKCHAIN_VERSION)) #ifdef STEEMIT_BUILD_TESTNET diff --git a/libraries/protocol/include/golos/protocol/exceptions.hpp b/libraries/protocol/include/golos/protocol/exceptions.hpp index fa1c208dc4..15514068ff 100644 --- a/libraries/protocol/include/golos/protocol/exceptions.hpp +++ b/libraries/protocol/include/golos/protocol/exceptions.hpp @@ -14,9 +14,11 @@ } #define GOLOS_ASSERT(expr, exception_type, FORMAT, ...) \ - if (!(expr)) { \ - throw exception_type(GOLOS_ASSERT_MESSAGE(FORMAT, __VA_ARGS__)); \ - } + FC_MULTILINE_MACRO_BEGIN \ + if (!(expr)) { \ + throw exception_type(GOLOS_ASSERT_MESSAGE(FORMAT, __VA_ARGS__)); \ + } \ + FC_MULTILINE_MACRO_END #define GOLOS_DECLARE_DERIVED_EXCEPTION_BODY(TYPE, BASE, CODE, WHAT) \ public: \ @@ -50,42 +52,369 @@ GOLOS_DECLARE_DERIVED_EXCEPTION_BODY(TYPE, BASE, CODE, WHAT) \ }; +#define GOLOS_CHECK_LOGIC(expr, TYPE, MSG, ...) \ + GOLOS_ASSERT(expr, golos::logic_exception, MSG, ("errid", TYPE)("namespace",golos::get_logic_error_namespace())__VA_ARGS__) + +#define GOLOS_CHECK_DATABASE(expr, TYPE, MSG, ...) \ + GOLOS_ASSERT(expr, golos::database_corrupted, MSG, ("errid", TYPE)__VA_ARGS__) + + +// TODO Remove after done refactor errors in plugins #791 +// This macros is obsolete and replaced with PLUGIN_API_VALIDATE_ARGS +#define GOLOS_DECLARE_PARAM(PARAM, GETTER) auto (PARAM) = [&]() {\ + try {return (GETTER);} \ + catch (const fc::exception &e) { \ + FC_THROW_EXCEPTION(golos::invalid_parameter, \ + "Invalid parameter \"${param}\": ${errmsg}", \ + ("param", BOOST_PP_STRINGIZE(PARAM)) \ + ("errmsg", e.to_string()) \ + ("error", e) \ + ); \ + } \ +}() + +#define GOLOS_CONVERT_PARAM(PARAM, VALUE, TYPE) \ + [&](){ \ + try { \ + return (VALUE).as(); \ + } catch (const fc::exception& e) { \ + FC_THROW_EXCEPTION(invalid_parameter, "Invalid value \"${value}\" for parameter \"${param}\": ${errmsg}", \ + ("param", FC_STRINGIZE(PARAM)) \ + ("value", VALUE) \ + ("errmsg", e.to_string()) \ + ("error", e)); \ + } \ + return TYPE(); /* make compiler happy */\ + }() + +#define GOLOS_CHECK_PARAM(PARAM, VALIDATOR) GOLOS_CHECK_PARAM_I(PARAM, PARAM, VALIDATOR, "") +#define GOLOS_CHECK_OP_PARAM(OP, PARAM, VALIDATOR) GOLOS_CHECK_PARAM_I(PARAM, OP.PARAM, VALIDATOR, "operation ") + +#define GOLOS_CHECK_PARAM_I(PARAM, VALUE, VALIDATOR, TYPE) \ + FC_MULTILINE_MACRO_BEGIN \ + try { \ + VALIDATOR; \ + } catch (const golos::invalid_value& e) { \ + FC_THROW_EXCEPTION(invalid_parameter, "Invalid value \"${value}\" for " TYPE "parameter \"${param}\": ${errmsg}", \ + ("param", FC_STRINGIZE(PARAM)) \ + ("value", VALUE) \ + ("errmsg", e.to_string()) \ + ("error", static_cast(e))); \ + } \ + FC_MULTILINE_MACRO_END + +#define GOLOS_CHECK_OPTION(COND, MSG, ...) \ + GOLOS_ASSERT((COND), golos::invalid_option, MSG, __VA_ARGS__) + +#define GOLOS_CHECK_VALUE(COND, MSG, ...) \ + GOLOS_ASSERT((COND), golos::invalid_value, MSG, __VA_ARGS__) + +#define GOLOS_CHECK_LIMIT(limit, max_value) \ + GOLOS_ASSERT( limit <= max_value, golos::limit_too_large, "Exceeded limit value. Maximum allowed ${max}", \ + ("limit",limit)("max",max_value)) + +#define GOLOS_CHECK_LIMIT_PARAM(limit, max_value) \ + GOLOS_CHECK_PARAM(limit, GOLOS_CHECK_LIMIT(limit, max_value)) + + +#define GOLOS_THROW_MISSING_OBJECT(type, id, ...) \ + FC_THROW_EXCEPTION(golos::missing_object, "Missing ${type} with id \"${id}\"", \ + ("type",type)("id",id) __VA_ARGS__) + +#define GOLOS_THROW_OBJECT_ALREADY_EXIST(type, id, ...) \ + FC_THROW_EXCEPTION(golos::object_already_exist, "Object ${type} with id \"${id}\" already exists", \ + ("type",type)("id",id) __VA_ARGS__) + +#define GOLOS_CHECK_OBJECT_MISSING(DB, OBJ, ...) \ + DB.throw_if_exists_##OBJ(__VA_ARGS__) + +#define GOLOS_THROW_INTERNAL_ERROR(MSG, ...) \ + FC_THROW_EXCEPTION(golos::internal_error, MSG, __VA_ARGS__) + +namespace golos { + + + // Function to get logic_error codes namespace + template + std::string get_logic_error_namespace(); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + golos_exception, fc::exception, + 0, "golos base exception") + + GOLOS_DECLARE_DERIVED_EXCEPTION( + operation_exception, golos_exception, + 1000000, "Opertaion exception"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + unsupported_operation, operation_exception, + 1010000, "Unsupported operation"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + unsupported_api_method, unsupported_operation, + 1010100, "Unsupported api method"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + parameter_exception, operation_exception, + 1020000, "Parameter exception"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + invalid_arguments_count, parameter_exception, + 1020100, "Invalid argument count"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + missing_object, parameter_exception, + 1020200, "Missing object"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + object_already_exist, parameter_exception, + 1020300, "Object already exist"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + invalid_parameter, parameter_exception, + 1020400, "Invalid parameter value"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + business_exception, golos_exception, + 2000000, "Business logic error"); + + class bandwidth_exception : public business_exception { + GOLOS_DECLARE_DERIVED_EXCEPTION_BODY( + bandwidth_exception, business_exception, + 2010000, "bandwidth exceeded error"); + public: + enum bandwidth_types { + post_bandwidth, + comment_bandwidth, + vote_bandwidth, + change_owner_authority_bandwidth, + }; + }; + + GOLOS_DECLARE_DERIVED_EXCEPTION( + insufficient_funds, business_exception, + 2020000, "Account does not have sufficient funds") + + class logic_exception : public business_exception { + GOLOS_DECLARE_DERIVED_EXCEPTION_BODY( + logic_exception, business_exception, + 2030000, "business logic error"); + public: + enum error_types { + reached_limit_for_pending_withdraw_requests = 1, + parent_of_comment_cannot_change, + parent_perlink_of_comment_cannot_change, + + // Vote operation + voter_declined_voting_rights, + account_is_currently_challenged, + votes_are_not_allowed, + does_not_have_voting_power, + voting_weight_is_too_small, + cannot_vote_within_last_minute_before_payout, + cannot_vote_with_zero_rshares, + voter_has_used_maximum_vote_changes, + already_voted_in_similar_way, + + // Comment operation + cannot_update_comment_because_nothing_changed, + reached_comment_max_depth, + replies_are_not_allowed, + cannot_delete_comment_with_replies, + cannot_delete_comment_with_positive_votes, + comment_options_requires_no_rshares, + curation_rewards_cannot_be_reenabled, + voting_cannot_be_reenabled, + comment_cannot_accept_greater_payout, + comment_cannot_accept_greater_percent_GBG, + cannot_specify_more_beneficiaries, + comment_already_has_beneficiaries, + comment_must_not_have_been_voted, + + // withdraw_vesting + insufficient_fee_for_powerdown_registered_account, + operation_would_not_change_vesting_withdraw_rate, + cannot_create_zero_percent_destination, + reached_maxumum_number_of_routes, + more_100percent_allocated_to_destinations, + + //account_create_with_delegation + not_enough_delegation, + + // challenge_authority_operation + cannot_challenge_yourself, + // prove_authority_evaluator + account_is_not_challeneged, + + // escrow + escrow_no_amount_set, + escrow_wrong_time_limits, + escrow_time_in_past, + escrow_bad_to, + escrow_bad_agent, + escrow_bad_receiver, + ratification_deadline_passed, + account_already_approved_escrow, + cannot_dispute_expired_escrow, + escrow_must_be_approved_first, + escrow_already_disputed, + release_amount_exceeds_escrow_balance, + only_agent_can_release_disputed, + only_from_to_can_release_non_disputed, + from_can_release_only_to_to, + to_can_release_only_to_from, + + // request_account_recovery + cannot_recover_if_not_partner, + must_be_recovered_by_top_witness, + // recover_account_operation + cannot_set_recent_recovery, + no_active_recovery_request, + authority_does_not_match_request, + no_recent_authority_in_history, + + //set_reset_account_operation + cannot_set_same_reset_account, + + //delegate_vesting_shares + cannot_delegate_to_yourself, + delegation_difference_too_low, + delegation_limited_by_voting_power, + cannot_delegate_below_minimum, + + //proposals and transactions + proposal_depth_too_high, + tx_with_both_posting_active_ops, + + empty_approvals, + add_and_remove_same_approval, + cannot_add_approval_in_review_period, + non_existing_approval, + already_existing_approval, + + proposal_delete_not_allowed, + + // limit order + limit_order_must_be_for_golos_gbg_market, + cancelling_not_filled_order, + + // feed_publish_operation + price_feed_must_be_for_golos_gbg_market, + + // account_witness_vote_operation + cannot_vote_when_route_are_set, + witness_vote_does_not_exist, + witness_vote_already_exist, + account_has_too_many_witness_votes, + + // account_witness_proxy_operation + proxy_must_change, + proxy_would_create_loop, + proxy_chain_is_too_long, + + // convert operation + no_price_feed_yet, + + // pow operation + duplicate_work_discovered, + miners_can_only_have_one_key_authority, + work_must_be_performed_by_signed_key, + work_not_for_last_block, + work_for_block_older_last_irreversible_block, + account_must_not_be_updated_in_this_block, + insufficient_work_difficalty, + account_already_scheduled_for_work, + cannot_specify_owner_key_unless_creating_account, + witness_must_be_created_before_minning, + + // custom operations + inner_authorities_does_not_match_outer, + + // database logic + account_exceeded_bandwidth_per_vestring_share, + }; + }; + + GOLOS_DECLARE_DERIVED_EXCEPTION( + internal_error, golos_exception, + 4000000, "internal error"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + assert_exception, internal_error, + 4010000, "assert exception"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + invalid_value, internal_error, + 4020000, "invalid value exception"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + limit_too_large, invalid_value, + 4020100, "Exceeded limit value"); + + class database_corrupted : public internal_error { + GOLOS_DECLARE_DERIVED_EXCEPTION_BODY( + database_corrupted, internal_error, + 4030000, "Database corrupted"); + public: + enum error_types { + wrong_block_num_was_read, + wrong_position_marker_was_read, + append_index_file_at_wrong_position, + reading_data_beyond_end_of_file, + }; + }; + + GOLOS_DECLARE_DERIVED_EXCEPTION( + invalid_option, golos_exception, + 5000000, "invalid option"); + +} // golos + + namespace golos { namespace protocol { GOLOS_DECLARE_DERIVED_EXCEPTION( - transaction_exception, fc::exception, + transaction_exception, golos_exception, 3000000, "transaction exception") - class tx_missing_active_auth: public transaction_exception { + GOLOS_DECLARE_DERIVED_EXCEPTION( + tx_invalid_operation, transaction_exception, + 3010000, "invalid operation in transaction"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + tx_missing_authority, transaction_exception, + 3020000, "missing authority"); + + class tx_missing_active_auth: public tx_missing_authority { GOLOS_DECLARE_DERIVED_EXCEPTION_BODY( - tx_missing_active_auth, transaction_exception, - 3010000, "missing required active authority"); + tx_missing_active_auth, tx_missing_authority, + 3020100, "missing required active authority"); public: std::vector missing_accounts; std::vector used_signatures; }; - class tx_missing_owner_auth: public transaction_exception { + class tx_missing_owner_auth: public tx_missing_authority { GOLOS_DECLARE_DERIVED_EXCEPTION_BODY( - tx_missing_owner_auth, transaction_exception, - 3020000, "missing required owner authority"); + tx_missing_owner_auth, tx_missing_authority, + 3020200, "missing required owner authority"); public: std::vector missing_accounts; std::vector used_signatures; }; - class tx_missing_posting_auth: public transaction_exception { + class tx_missing_posting_auth: public tx_missing_authority { GOLOS_DECLARE_DERIVED_EXCEPTION_BODY( - tx_missing_posting_auth, transaction_exception, - 3030000, "missing required posting authority"); + tx_missing_posting_auth, tx_missing_authority, + 3020300, "missing required posting authority"); public: std::vector missing_accounts; std::vector used_signatures; }; - class tx_missing_other_auth: public transaction_exception { + class tx_missing_other_auth: public tx_missing_authority { GOLOS_DECLARE_DERIVED_EXCEPTION_BODY( - tx_missing_other_auth, transaction_exception, - 3040000, "missing required other authority"); + tx_missing_other_auth, tx_missing_authority, + 3020400, "missing required other authority"); public: std::vector missing_auths; }; @@ -93,7 +422,7 @@ namespace golos { namespace protocol { class tx_irrelevant_sig: public transaction_exception { GOLOS_DECLARE_DERIVED_EXCEPTION_BODY( tx_irrelevant_sig, transaction_exception, - 3050000, "irrelevant signature included"); + 3030000, "irrelevant signature included"); public: std::vector unused_signatures; }; @@ -112,4 +441,169 @@ namespace golos { namespace protocol { std::vector unused_approvals; }; + GOLOS_DECLARE_DERIVED_EXCEPTION( + tx_duplicate_transaction, transaction_exception, + 3080000, "duplicate transaction"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + tx_too_long, transaction_exception, + 3090000, "transaction too long"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + tx_expired, transaction_exception, + 3100000, "expired transaction"); + + GOLOS_DECLARE_DERIVED_EXCEPTION( + tx_invalid_field, transaction_exception, + 3110000, "invalid transaction field"); + + } } // golos::protocol + +FC_REFLECT_ENUM(golos::logic_exception::error_types, + (reached_limit_for_pending_withdraw_requests) + (parent_of_comment_cannot_change) + (parent_perlink_of_comment_cannot_change) + + // Vote operation + (voter_declined_voting_rights) + (account_is_currently_challenged) + (votes_are_not_allowed) + (does_not_have_voting_power) + (voting_weight_is_too_small) + (cannot_vote_within_last_minute_before_payout) + (cannot_vote_with_zero_rshares) + (voter_has_used_maximum_vote_changes) + (already_voted_in_similar_way) + + // Comment operation + (cannot_update_comment_because_nothing_changed) + (reached_comment_max_depth) + (replies_are_not_allowed) + (cannot_delete_comment_with_replies) + (cannot_delete_comment_with_positive_votes) + (comment_options_requires_no_rshares) + (curation_rewards_cannot_be_reenabled) + (voting_cannot_be_reenabled) + (comment_cannot_accept_greater_payout) + (comment_cannot_accept_greater_percent_GBG) + (cannot_specify_more_beneficiaries) + (comment_already_has_beneficiaries) + (comment_must_not_have_been_voted) + + // withdraw_vesting + (insufficient_fee_for_powerdown_registered_account) + (operation_would_not_change_vesting_withdraw_rate) + (cannot_create_zero_percent_destination) + (reached_maxumum_number_of_routes) + (more_100percent_allocated_to_destinations) + + //challenge_authority_operation + (cannot_challenge_yourself) + //prove_authority_evaluator + (account_is_not_challeneged) + + //escrow + (escrow_no_amount_set) + (escrow_wrong_time_limits) + (escrow_time_in_past) + (escrow_bad_to) + (escrow_bad_agent) + (escrow_bad_receiver) + (ratification_deadline_passed) + (account_already_approved_escrow) + (cannot_dispute_expired_escrow) + (escrow_must_be_approved_first) + (escrow_already_disputed) + (release_amount_exceeds_escrow_balance) + (only_agent_can_release_disputed) + (only_from_to_can_release_non_disputed) + (from_can_release_only_to_to) + (to_can_release_only_to_from) + + // request_account_recovery + (cannot_recover_if_not_partner) + (must_be_recovered_by_top_witness) + // recover_account_operation + (cannot_set_recent_recovery) + (no_active_recovery_request) + (authority_does_not_match_request) + (no_recent_authority_in_history) + + //set_reset_account_operation + (cannot_set_same_reset_account) + + //delegate_vesting_shares + (cannot_delegate_to_yourself) + (delegation_difference_too_low) + (delegation_limited_by_voting_power) + (cannot_delegate_below_minimum) + + //account_create_with_delegation + (not_enough_delegation) + + //proposals + (proposal_depth_too_high) + (tx_with_both_posting_active_ops) + + (empty_approvals) + (add_and_remove_same_approval) + (cannot_add_approval_in_review_period) + (non_existing_approval) + (already_existing_approval) + + (proposal_delete_not_allowed) + + // limit order + (limit_order_must_be_for_golos_gbg_market) + (cancelling_not_filled_order) + + // feed_publis_operation + (price_feed_must_be_for_golos_gbg_market) + + // account_witness_vote_operation + (cannot_vote_when_route_are_set) + (witness_vote_does_not_exist) + (witness_vote_already_exist) + (account_has_too_many_witness_votes) + + // account_witness_proxy_operation + (proxy_must_change) + (proxy_would_create_loop) + (proxy_chain_is_too_long) + + // convert operation + (no_price_feed_yet) + + // pow operation + (duplicate_work_discovered) + (miners_can_only_have_one_key_authority) + (work_must_be_performed_by_signed_key) + (work_not_for_last_block) + (work_for_block_older_last_irreversible_block) + (account_must_not_be_updated_in_this_block) + (insufficient_work_difficalty) + (account_already_scheduled_for_work) + (cannot_specify_owner_key_unless_creating_account) + (witness_must_be_created_before_minning) + + // custom operations + (inner_authorities_does_not_match_outer) + + // database logic + (account_exceeded_bandwidth_per_vestring_share) +); + +FC_REFLECT_ENUM(golos::bandwidth_exception::bandwidth_types, + (post_bandwidth) + (comment_bandwidth) + (vote_bandwidth) + (change_owner_authority_bandwidth) +); + +FC_REFLECT_ENUM(golos::database_corrupted::error_types, + (wrong_block_num_was_read) + (wrong_position_marker_was_read) + (append_index_file_at_wrong_position) + (reading_data_beyond_end_of_file) +); diff --git a/libraries/protocol/include/golos/protocol/operations.hpp b/libraries/protocol/include/golos/protocol/operations.hpp index 59e1511068..0bed9b25df 100644 --- a/libraries/protocol/include/golos/protocol/operations.hpp +++ b/libraries/protocol/include/golos/protocol/operations.hpp @@ -81,7 +81,8 @@ namespace golos { namespace protocol { hardfork_operation, comment_payout_update_operation, comment_benefactor_reward_operation, - return_vesting_delegation_operation + return_vesting_delegation_operation, + producer_reward_operation > operation; /*void operation_get_required_authorities( const operation& op, diff --git a/libraries/protocol/include/golos/protocol/steem_operations.hpp b/libraries/protocol/include/golos/protocol/steem_operations.hpp index 483eb09698..0a131cc642 100644 --- a/libraries/protocol/include/golos/protocol/steem_operations.hpp +++ b/libraries/protocol/include/golos/protocol/steem_operations.hpp @@ -160,7 +160,7 @@ namespace golos { namespace protocol { void validate() const; - void get_required_active_authorities(flat_set &a) const { + void get_required_active_authorities(flat_set& a) const { a.insert(challenger); } }; @@ -171,13 +171,13 @@ namespace golos { namespace protocol { void validate() const; - void get_required_active_authorities(flat_set &a) const { + void get_required_active_authorities(flat_set& a) const { if (!require_owner) { a.insert(challenged); } } - void get_required_owner_authorities(flat_set &a) const { + void get_required_owner_authorities(flat_set& a) const { if (require_owner) { a.insert(challenged); } @@ -442,13 +442,7 @@ namespace golos { namespace protocol { uint32_t maximum_block_size = STEEMIT_MIN_BLOCK_SIZE_LIMIT * 2; uint16_t sbd_interest_rate = STEEMIT_DEFAULT_SBD_INTEREST_RATE; - void validate() const { - FC_ASSERT(account_creation_fee.symbol == STEEM_SYMBOL); - FC_ASSERT(account_creation_fee.amount >= STEEMIT_MIN_ACCOUNT_CREATION_FEE); - FC_ASSERT(maximum_block_size >= STEEMIT_MIN_BLOCK_SIZE_LIMIT); - FC_ASSERT(sbd_interest_rate >= 0); - FC_ASSERT(sbd_interest_rate <= STEEMIT_100_PERCENT); - } + void validate() const; chain_properties_17& operator=(const chain_properties_17&) = default; @@ -490,16 +484,7 @@ namespace golos { namespace protocol { asset(STEEMIT_MIN_ACCOUNT_CREATION_FEE * GOLOS_MIN_DELEGATION_MULTIPLIER, STEEM_SYMBOL); - void validate() const { - chain_properties_17::validate(); - FC_ASSERT(create_account_min_golos_fee.amount > 0); - FC_ASSERT(create_account_min_golos_fee.symbol == STEEM_SYMBOL); - FC_ASSERT(create_account_min_delegation.amount > 0); - FC_ASSERT(create_account_min_delegation.symbol == STEEM_SYMBOL); - FC_ASSERT(min_delegation.amount > 0); - FC_ASSERT(min_delegation.symbol == STEEM_SYMBOL); - FC_ASSERT(create_account_delegation_time > (GOLOS_CREATE_ACCOUNT_DELEGATION_TIME).to_seconds() / 2); - } + void validate() const; chain_properties_18& operator=(const chain_properties_17& src) { account_creation_fee = src.account_creation_fee; diff --git a/libraries/protocol/include/golos/protocol/steem_virtual_operations.hpp b/libraries/protocol/include/golos/protocol/steem_virtual_operations.hpp index a990f38985..a6a45e1171 100644 --- a/libraries/protocol/include/golos/protocol/steem_virtual_operations.hpp +++ b/libraries/protocol/include/golos/protocol/steem_virtual_operations.hpp @@ -187,6 +187,14 @@ namespace golos { namespace protocol { asset reward; }; + struct producer_reward_operation : public virtual_operation { + producer_reward_operation() {} + producer_reward_operation(const string& p, const asset& v) : producer(p), vesting_shares(v) {} + + account_name_type producer; + asset vesting_shares; + }; + struct return_vesting_delegation_operation: public virtual_operation { return_vesting_delegation_operation() { } @@ -213,3 +221,4 @@ FC_REFLECT((golos::protocol::hardfork_operation), (hardfork_id)) FC_REFLECT((golos::protocol::comment_payout_update_operation), (author)(permlink)) FC_REFLECT((golos::protocol::comment_benefactor_reward_operation), (benefactor)(author)(permlink)(reward)) FC_REFLECT((golos::protocol::return_vesting_delegation_operation), (account)(vesting_shares)) +FC_REFLECT((golos::protocol::producer_reward_operation), (producer)(vesting_shares)) diff --git a/libraries/protocol/include/golos/protocol/transaction.hpp b/libraries/protocol/include/golos/protocol/transaction.hpp index 90841ac765..a6b7238b4f 100644 --- a/libraries/protocol/include/golos/protocol/transaction.hpp +++ b/libraries/protocol/include/golos/protocol/transaction.hpp @@ -6,8 +6,7 @@ #include -namespace golos { - namespace protocol { +namespace golos { namespace protocol { struct transaction { uint16_t ref_block_num = 0; @@ -127,8 +126,7 @@ namespace golos { /// @} transactions group - } -} // golos::protocol +} } // golos::protocol FC_REFLECT((golos::protocol::transaction), (ref_block_num)(ref_block_prefix)(expiration)(operations)(extensions)) FC_REFLECT_DERIVED((golos::protocol::signed_transaction), ((golos::protocol::transaction)), (signatures)) diff --git a/libraries/protocol/include/golos/protocol/validate_helper.hpp b/libraries/protocol/include/golos/protocol/validate_helper.hpp new file mode 100644 index 0000000000..0a0898c07b --- /dev/null +++ b/libraries/protocol/include/golos/protocol/validate_helper.hpp @@ -0,0 +1,78 @@ +#pragma once + +// macro helpers on GOLOS_CHECK_VALUE and GOLOS_CHECK_PARAM +// to check common cases and autogenerate message text + +// Common abbreviations: +// EQ, NE, LT, GT, LE, GE — like in asm conditions +// (Equal, Not Equal, Less Than, Greather Than, Less or Equal, Greater or Equal) + + +// GOLOS_CHECK_PARAM helpers +//------------------------------------------------------------- + +// check if account parameter valid +#define GOLOS_CHECK_PARAM_ACCOUNT(A) GOLOS_CHECK_PARAM(A, validate_account_name(A)) +// call param internal validate +#define GOLOS_CHECK_PARAM_VALIDATE(P) GOLOS_CHECK_PARAM(P, P.validate()) + +// GOLOS_CHECK_VALUE helpers +//------------------------------------------------------------- + +// size +#define GOLOS_CHECK_VALUE_MAX_SIZE(F, M) \ + GOLOS_CHECK_VALUE(F.size() <= (M), MUST_BE2(F, "is too long", "<= " FC_STRINGIZE(M))); +#define GOLOS_CHECK_VALUE_NOT_EMPTY(F) \ + GOLOS_CHECK_VALUE(!F.empty(), CANNOT_BE(F, "empty")); +// utf-8 +#define GOLOS_CHECK_VALUE_UTF8(F) \ + GOLOS_CHECK_VALUE(fc::is_utf8(F), MUST_BE(F, "valid UTF8 string")); +// json +#define GOLOS_CHECK_VALUE_JSON(F) \ + GOLOS_CHECK_VALUE(fc::json::is_valid(F), MUST_BE(F, "valid JSON")); + + +// compare field with value +#define GOLOS_CHECK_VALUE_EQ(F, X) GOLOS_CHECK_VALUE_I(F, ==, X) +#define GOLOS_CHECK_VALUE_GT(F, X) GOLOS_CHECK_VALUE_I(F, > , X) +#define GOLOS_CHECK_VALUE_LT(F, X) GOLOS_CHECK_VALUE_I(F, < , X) +#define GOLOS_CHECK_VALUE_GE(F, X) GOLOS_CHECK_VALUE_I(F, >=, X) +#define GOLOS_CHECK_VALUE_LE(F, X) GOLOS_CHECK_VALUE_I(F, <=, X) +#define GOLOS_CHECK_VALUE_LEGE(F, L, H) \ + GOLOS_CHECK_VALUE((L) <= F && F <= (H) , MUST_BE(F, "between " FC_STRINGIZE(L) " and" FC_STRINGIZE(H))) + +// check asset type +#define GOLOS_CHECK_ASSET_TYPE(X, NAME) GOLOS_CHECK_ASSET_##NAME(X); +#define GOLOS_CHECK_ASSET_GESTS(X) GOLOS_CHECK_ASSET_TYPE_I(X, VESTS_SYMBOL, "GESTS") +#define GOLOS_CHECK_ASSET_GOLOS(X) GOLOS_CHECK_ASSET_TYPE_I(X, STEEM_SYMBOL, "GOLOS") +#define GOLOS_CHECK_ASSET_GBG(X) GOLOS_CHECK_ASSET_TYPE_I(X, SBD_SYMBOL, "GBG") + +#define GOLOS_CHECK_ASSET_GOLOS_OR_GBG(X) \ + GOLOS_CHECK_VALUE(X.symbol == STEEM_SYMBOL || X.symbol == SBD_SYMBOL, MUST_BE(X, "GOLOS or GBG")) + +// check asset type and value +#define GOLOS_CHECK_ASSET_GT0(X, NAME) GOLOS_CHECK_ASSET_VAL(X, >, 0, NAME) +#define GOLOS_CHECK_ASSET_GE0(X, NAME) GOLOS_CHECK_ASSET_VAL(X, >=, 0, NAME) +#define GOLOS_CHECK_ASSET_GE(X, NAME, V) GOLOS_CHECK_ASSET_VAL(X, >=, V, NAME) + + +// internals +//------------------------------------------------------------- + +// fields +#define GOLOS_CHECK_VALUE_I(F, OP, X) GOLOS_CHECK_VALUE_II(F, OP, X, F) +#define GOLOS_CHECK_VALUE_II(F, OP, X, N) GOLOS_CHECK_VALUE(F OP (X), MUST_BE(N, "" #OP FC_STRINGIZE(X))) + +// asset type +#define GOLOS_CHECK_ASSET_TYPE_I(X, SYMBOL, SNAME) GOLOS_CHECK_VALUE(X.symbol == SYMBOL, MUST_BE(X, SNAME)) + +// asset value +#define GOLOS_CHECK_ASSET_VAL(X, OP, V, N) { \ + GOLOS_CHECK_ASSET_TYPE(X, N); \ + GOLOS_CHECK_VALUE_II(X.amount, OP, V, X); \ +} + +// utils +#define MUST_BE(NAME, REQ) #NAME " must be " REQ +#define MUST_BE2(NAME, DESC, REQ) #NAME DESC ", it must be " REQ +#define CANNOT_BE(NAME, REQ) #NAME " cannot be " REQ diff --git a/libraries/protocol/proposal_operations.cpp b/libraries/protocol/proposal_operations.cpp index 35c0378dc9..0dcd1ec79b 100644 --- a/libraries/protocol/proposal_operations.cpp +++ b/libraries/protocol/proposal_operations.cpp @@ -1,57 +1,77 @@ #include #include #include +#include +#include #include -#include #include +// TODO: move somewhere globally accessible +#define GOLOS_PROPOSAL_MAX_TITLE_SIZE 256 +#define GOLOS_PROPOSAL_MAX_MEMO_SIZE 4096 + + namespace golos { namespace protocol { - inline void validate_account_name(const string& name) { - FC_ASSERT(is_valid_account_name(name), "Account name ${n} is invalid", ("n", name)); + // TODO: reuse code from steem_operations.cpp + static inline void validate_account_name(const string &name) { + GOLOS_CHECK_VALUE(is_valid_account_name(name), "Account name ${name} is invalid", ("name", name)); } - void proposal_create_operation::validate() const { - validate_account_name(author); - FC_ASSERT(!title.empty(), "Title is empty"); - FC_ASSERT(title.size() < 256, "Title larger than size limit"); - FC_ASSERT(fc::is_utf8(title), "Title not formatted in UTF8"); + void proposal_create_operation::validate() const { + GOLOS_CHECK_PARAM_ACCOUNT(author); + GOLOS_CHECK_PARAM(title, { + GOLOS_CHECK_VALUE_NOT_EMPTY(title); + GOLOS_CHECK_VALUE_MAX_SIZE(title, GOLOS_PROPOSAL_MAX_TITLE_SIZE); + GOLOS_CHECK_VALUE_UTF8(title); + }); + + GOLOS_CHECK_PARAM(proposed_operations, { + GOLOS_CHECK_VALUE(!proposed_operations.empty(), "proposed_operations can't be empty"); + for (const auto& op : proposed_operations) { + operation_validate(op.op); + } + }); - FC_ASSERT(!proposed_operations.empty()); - for (const auto& op : proposed_operations) { - operation_validate(op.op); - } + GOLOS_CHECK_PARAM(memo, { + if (memo.size() > 0) { + GOLOS_CHECK_VALUE_MAX_SIZE(memo, GOLOS_PROPOSAL_MAX_MEMO_SIZE); // "Memo larger than size limit" + GOLOS_CHECK_VALUE_UTF8(memo); + } + }); - if (memo.size() > 0) { - FC_ASSERT(memo.size() < 4096, "Memo larger than size limit"); - FC_ASSERT(fc::is_utf8(memo), "Memo not formatted in UTF8"); - } + // TODO: time in past can be validated (expiration_time and review_period_time) + // TODO: combination of posting+active ops van be validated } void proposal_update_operation::validate() const { - validate_account_name(author); - - FC_ASSERT(!title.empty(), "Title is empty"); - FC_ASSERT(fc::is_utf8(title), "Title not formatted in UTF8"); - - FC_ASSERT( - !(active_approvals_to_add.empty() && active_approvals_to_remove.empty() && - owner_approvals_to_add.empty() && owner_approvals_to_remove.empty() && - posting_approvals_to_add.empty() && posting_approvals_to_remove.empty() && - key_approvals_to_add.empty() && key_approvals_to_remove.empty())); + GOLOS_CHECK_PARAM_ACCOUNT(author); + GOLOS_CHECK_PARAM(title, { + GOLOS_CHECK_VALUE_NOT_EMPTY(title); + GOLOS_CHECK_VALUE_MAX_SIZE(title, GOLOS_PROPOSAL_MAX_TITLE_SIZE); + GOLOS_CHECK_VALUE_UTF8(title); + }); + + GOLOS_CHECK_LOGIC(0 < ( + owner_approvals_to_add.size() + owner_approvals_to_remove.size() + + active_approvals_to_add.size() + active_approvals_to_remove.size() + + posting_approvals_to_add.size() + posting_approvals_to_remove.size() + + key_approvals_to_add.size() + key_approvals_to_remove.size()), + logic_exception::empty_approvals, + "At least one approval add or approval remove must exist"); auto validate = [&](const auto& to_add, const auto& to_remove) { for (const auto& a: to_add) { - FC_ASSERT( - to_remove.find(a) == to_remove.end(), - "Cannot add and remove approval at the same time."); + GOLOS_CHECK_LOGIC(to_remove.find(a) == to_remove.end(), + logic_exception::add_and_remove_same_approval, + "Cannot add and remove approval at the same time"); } }; - validate(active_approvals_to_add, active_approvals_to_remove); validate(owner_approvals_to_add, owner_approvals_to_remove); + validate(active_approvals_to_add, active_approvals_to_remove); validate(posting_approvals_to_add, posting_approvals_to_remove); validate(key_approvals_to_add, key_approvals_to_remove); } @@ -87,9 +107,13 @@ namespace golos { namespace protocol { } void proposal_delete_operation::validate() const { - validate_account_name(author); - FC_ASSERT(!title.empty(), "Title is empty"); - FC_ASSERT(fc::is_utf8(title), "Title not formatted in UTF8"); + GOLOS_CHECK_PARAM_ACCOUNT(requester); + GOLOS_CHECK_PARAM_ACCOUNT(author); + GOLOS_CHECK_PARAM(title, { + GOLOS_CHECK_VALUE_NOT_EMPTY(title); + GOLOS_CHECK_VALUE_MAX_SIZE(title, GOLOS_PROPOSAL_MAX_TITLE_SIZE); + GOLOS_CHECK_VALUE_UTF8(title); + }); } -} } // golos::chain \ No newline at end of file +} } // golos::chain diff --git a/libraries/protocol/steem_operations.cpp b/libraries/protocol/steem_operations.cpp index 786c723a04..f82f18fcdc 100644 --- a/libraries/protocol/steem_operations.cpp +++ b/libraries/protocol/steem_operations.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include namespace golos { namespace protocol { @@ -7,19 +9,18 @@ namespace golos { namespace protocol { /// Issue #56 contains the justificiation for allowing any UTF-8 string to serve as a permlink, content will be grouped by tags /// going forward. inline void validate_permlink(const string &permlink) { - FC_ASSERT(permlink.size() < - STEEMIT_MAX_PERMLINK_LENGTH, "permlink is too long"); - FC_ASSERT(fc::is_utf8(permlink), "permlink not formatted in UTF8"); + GOLOS_CHECK_VALUE(permlink.size() < STEEMIT_MAX_PERMLINK_LENGTH, "permlink is too long"); + GOLOS_CHECK_VALUE(fc::is_utf8(permlink), "permlink not formatted in UTF8"); } - inline void validate_account_name(const string &name) { - FC_ASSERT(is_valid_account_name(name), "Account name ${n} is invalid", ("n", name)); + static inline void validate_account_name(const string &name) { + GOLOS_CHECK_VALUE(is_valid_account_name(name), "Account name ${name} is invalid", ("name", name)); } inline void validate_account_json_metadata(const string& json_metadata) { if (json_metadata.size() > 0) { - FC_ASSERT(fc::is_utf8(json_metadata), "JSON Metadata not formatted in UTF8"); - FC_ASSERT(fc::json::is_valid(json_metadata), "JSON Metadata not valid JSON"); + GOLOS_CHECK_VALUE_UTF8(json_metadata); + GOLOS_CHECK_VALUE(fc::json::is_valid(json_metadata), "JSON Metadata not valid JSON"); } } @@ -28,60 +29,69 @@ namespace golos { namespace protocol { } void account_create_operation::validate() const { - validate_account_name(new_account_name); - FC_ASSERT(is_asset_type(fee, STEEM_SYMBOL), "Account creation fee must be GOLOS"); - owner.validate(); - active.validate(); - validate_account_json_metadata(json_metadata); - FC_ASSERT(fee >= asset(0, STEEM_SYMBOL), "Account creation fee cannot be negative"); + GOLOS_CHECK_PARAM(new_account_name, validate_account_name(new_account_name)); + GOLOS_CHECK_PARAM(owner, owner.validate()); + GOLOS_CHECK_PARAM(active, active.validate()); + GOLOS_CHECK_PARAM(fee, { + GOLOS_CHECK_VALUE(is_asset_type(fee, STEEM_SYMBOL), "Account creation fee must be GOLOS"); + GOLOS_CHECK_VALUE(fee >= asset(0, STEEM_SYMBOL), "Account creation fee cannot be negative"); + }); + GOLOS_CHECK_PARAM(json_metadata, validate_account_json_metadata(json_metadata)); } void account_create_with_delegation_operation::validate() const { - validate_account_name(new_account_name); - validate_account_name(creator); - FC_ASSERT(is_asset_type(fee, STEEM_SYMBOL), "Account creation fee must be GOLOS"); - FC_ASSERT(is_asset_type(delegation, VESTS_SYMBOL), "Delegation must be GESTS"); - FC_ASSERT(fee.amount >= 0, "Account creation fee cannot be negative"); - FC_ASSERT(delegation.amount >= 0, "Delegation cannot be negative"); - owner.validate(); - active.validate(); - posting.validate(); - validate_account_json_metadata(json_metadata); + GOLOS_CHECK_PARAM_ACCOUNT(new_account_name); + GOLOS_CHECK_PARAM_ACCOUNT(creator); + GOLOS_CHECK_PARAM(fee, {GOLOS_CHECK_ASSET_GE0(fee, GOLOS)}); + GOLOS_CHECK_PARAM(delegation, {GOLOS_CHECK_ASSET_GE0(delegation, GESTS)}); + GOLOS_CHECK_PARAM_VALIDATE(owner); + GOLOS_CHECK_PARAM_VALIDATE(active); + GOLOS_CHECK_PARAM_VALIDATE(posting); + GOLOS_CHECK_PARAM(json_metadata, validate_account_json_metadata(json_metadata)); } void account_update_operation::validate() const { - validate_account_name(account); + GOLOS_CHECK_PARAM(account, validate_account_name(account)); /*if( owner ) owner->validate(); if( active ) active->validate(); if( posting ) posting->validate();*/ - validate_account_json_metadata(json_metadata); + GOLOS_CHECK_PARAM(json_metadata, validate_account_json_metadata(json_metadata)); } void account_metadata_operation::validate() const { - validate_account_name(account); - FC_ASSERT(json_metadata.size() > 0, "json_metadata can't be empty"); - validate_account_json_metadata(json_metadata); + GOLOS_CHECK_PARAM_ACCOUNT(account); + GOLOS_CHECK_PARAM(json_metadata, { + GOLOS_CHECK_VALUE_NOT_EMPTY(json_metadata); + validate_account_json_metadata(json_metadata); + }); } void comment_operation::validate() const { - FC_ASSERT(title.size() < 256, "Title larger than size limit"); - FC_ASSERT(fc::is_utf8(title), "Title not formatted in UTF8"); - FC_ASSERT(body.size() > 0, "Body is empty"); - FC_ASSERT(fc::is_utf8(body), "Body not formatted in UTF8"); + GOLOS_CHECK_PARAM(title, { + GOLOS_CHECK_VALUE(title.size() < 256, "Title larger than size limit"); + GOLOS_CHECK_VALUE(fc::is_utf8(title), "Title not formatted in UTF8"); + }); + GOLOS_CHECK_PARAM(body, { + GOLOS_CHECK_VALUE(body.size() > 0, "Body is empty"); + GOLOS_CHECK_VALUE(fc::is_utf8(body), "Body not formatted in UTF8"); + }); if (parent_author.size()) { - validate_account_name(parent_author); + GOLOS_CHECK_PARAM(parent_author, validate_account_name(parent_author)); } - validate_account_name(author); - validate_permlink(parent_permlink); - validate_permlink(permlink); + + GOLOS_CHECK_PARAM(author, validate_account_name(author)); + GOLOS_CHECK_PARAM(parent_permlink, validate_permlink(parent_permlink)); + GOLOS_CHECK_PARAM(permlink, validate_permlink(permlink)); if (json_metadata.size() > 0) { - FC_ASSERT(fc::json::is_valid(json_metadata), "JSON Metadata not valid JSON"); + GOLOS_CHECK_PARAM(json_metadata, { + GOLOS_CHECK_VALUE(fc::json::is_valid(json_metadata), "JSON Metadata not valid JSON"); + }); } } @@ -97,111 +107,136 @@ namespace golos { namespace protocol { }; void comment_payout_beneficiaries::validate() const { - uint32_t sum = 0; - - FC_ASSERT(beneficiaries.size(), "Must specify at least one beneficiary"); - FC_ASSERT(beneficiaries.size() < 128, - "Cannot specify more than 127 beneficiaries."); // Require size serializtion fits in one byte. - - validate_account_name(beneficiaries[0].account); - FC_ASSERT(beneficiaries[0].weight <= STEEMIT_100_PERCENT, - "Cannot allocate more than 100% of rewards to one account"); - sum += beneficiaries[0].weight; - FC_ASSERT(sum <= STEEMIT_100_PERCENT, - "Cannot allocate more than 100% of rewards to a comment"); // Have to check incrementally to avoid overflow - - for (size_t i = 1; i < beneficiaries.size(); i++) { - validate_account_name( beneficiaries[i].account); - FC_ASSERT(beneficiaries[i].weight <= STEEMIT_100_PERCENT, - "Cannot allocate more than 100% of rewards to one account"); - sum += beneficiaries[i].weight; - FC_ASSERT(sum <= STEEMIT_100_PERCENT, - "Cannot allocate more than 100% of rewards to a comment"); // Have to check incrementally to avoid overflow - FC_ASSERT(beneficiaries[i - 1] < beneficiaries[i], - "Benficiaries must be specified in sorted order (account ascending)"); - } + uint32_t sum = 0; // avoid overflow + + GOLOS_CHECK_PARAM(beneficiaries, { + GOLOS_CHECK_VALUE(beneficiaries.size(), "Must specify at least one beneficiary"); + GOLOS_CHECK_VALUE(beneficiaries.size() < 128, + "Cannot specify more than 127 beneficiaries."); // Require size serializtion fits in one byte. + + for (auto beneficiar: beneficiaries) { + validate_account_name(beneficiaries[0].account); + GOLOS_CHECK_VALUE(beneficiar.weight <= STEEMIT_100_PERCENT, + "Cannot allocate more than 100% of rewards to one account"); + sum += beneficiar.weight; + GOLOS_CHECK_VALUE(sum <= STEEMIT_100_PERCENT, + "Cannot allocate more than 100% of rewards to a comment"); + } + + for (size_t i = 1; i < beneficiaries.size(); i++) { + GOLOS_CHECK_VALUE(beneficiaries[i - 1] < beneficiaries[i], + "Benficiaries ${first} and ${second} not in sorted order (account ascending)", + ("first", beneficiaries[i-1].account)("second", beneficiaries[i].account)); + } + }); } void comment_options_operation::validate() const { - validate_account_name(author); - FC_ASSERT(percent_steem_dollars <= STEEMIT_100_PERCENT, "Percent cannot exceed 100%"); - FC_ASSERT(max_accepted_payout.symbol == SBD_SYMBOL, "Max accepted payout must be in GBG"); - FC_ASSERT(max_accepted_payout.amount.value >= 0, "Cannot accept less than 0 payout"); - validate_permlink(permlink); + GOLOS_CHECK_PARAM_ACCOUNT(author); + GOLOS_CHECK_PARAM(percent_steem_dollars, GOLOS_CHECK_VALUE_LE(percent_steem_dollars, STEEMIT_100_PERCENT)); + GOLOS_CHECK_PARAM(max_accepted_payout, GOLOS_CHECK_ASSET_GE0(max_accepted_payout, GBG)); + GOLOS_CHECK_PARAM(permlink, validate_permlink(permlink)); for (auto &e : extensions) { e.visit(comment_options_extension_validate_visitor()); } } void delete_comment_operation::validate() const { - validate_permlink(permlink); - validate_account_name(author); + GOLOS_CHECK_PARAM_ACCOUNT(author); + GOLOS_CHECK_PARAM(permlink, validate_permlink(permlink)); } void challenge_authority_operation::validate() const { - validate_account_name(challenger); - validate_account_name(challenged); - FC_ASSERT(challenged != challenger, "cannot challenge yourself"); + GOLOS_CHECK_PARAM_ACCOUNT(challenger); + GOLOS_CHECK_PARAM_ACCOUNT(challenged); + GOLOS_CHECK_LOGIC(challenged != challenger, + logic_exception::cannot_challenge_yourself, + "cannot challenge yourself"); } void prove_authority_operation::validate() const { - validate_account_name(challenged); + GOLOS_CHECK_PARAM_ACCOUNT(challenged); } void vote_operation::validate() const { - validate_account_name(voter); - validate_account_name(author);\ - FC_ASSERT(abs(weight) <= - STEEMIT_100_PERCENT, "Weight is not a STEEMIT percentage"); - validate_permlink(permlink); + GOLOS_CHECK_PARAM(voter, validate_account_name(voter)); + GOLOS_CHECK_PARAM(author, validate_account_name(author)); + GOLOS_CHECK_PARAM(weight, { + GOLOS_CHECK_VALUE(abs(weight) <= STEEMIT_100_PERCENT, + "Weight is not a STEEMIT percentage"); + }); + GOLOS_CHECK_PARAM(permlink, validate_permlink(permlink)); } void transfer_operation::validate() const { try { - validate_account_name(from); - validate_account_name(to); - FC_ASSERT(amount.symbol != - VESTS_SYMBOL, "transferring of Golos Power (STMP) is not allowed."); - FC_ASSERT(amount.amount > - 0, "Cannot transfer a negative amount (aka: stealing)"); - FC_ASSERT(memo.size() < - STEEMIT_MAX_MEMO_SIZE, "Memo is too large"); - FC_ASSERT(fc::is_utf8(memo), "Memo is not UTF8"); + GOLOS_CHECK_PARAM(from, validate_account_name(from)); + GOLOS_CHECK_PARAM(to, validate_account_name(to)); + GOLOS_CHECK_PARAM(amount, { + GOLOS_CHECK_VALUE(amount.symbol != VESTS_SYMBOL, "transferring of Golos Power (GESTS) is not allowed."); + GOLOS_CHECK_VALUE(amount.amount > 0, "Cannot transfer a negative amount (aka: stealing)"); + }); + GOLOS_CHECK_PARAM(memo, { + GOLOS_CHECK_VALUE(memo.size() < STEEMIT_MAX_MEMO_SIZE, "Memo is too large"); + GOLOS_CHECK_VALUE(fc::is_utf8(memo), "Memo is not UTF8"); + }); } FC_CAPTURE_AND_RETHROW((*this)) } void transfer_to_vesting_operation::validate() const { - validate_account_name(from); - FC_ASSERT(is_asset_type(amount, STEEM_SYMBOL), "Amount must be GOLOS"); - if (to != account_name_type()) { - validate_account_name(to); - } - FC_ASSERT(amount > - asset(0, STEEM_SYMBOL), "Must transfer a nonzero amount"); + GOLOS_CHECK_PARAM(from, validate_account_name(from)); + GOLOS_CHECK_PARAM(amount, { + GOLOS_CHECK_VALUE(is_asset_type(amount, STEEM_SYMBOL), "Amount must be GOLOS"); + GOLOS_CHECK_VALUE(amount > asset(0, STEEM_SYMBOL), "Must transfer a nonzero amount"); + }); + GOLOS_CHECK_PARAM(to, { + if (to != account_name_type()) { + validate_account_name(to); + } + }); } void withdraw_vesting_operation::validate() const { - validate_account_name(account); - FC_ASSERT(is_asset_type(vesting_shares, VESTS_SYMBOL), "Amount must be GESTS"); - FC_ASSERT( - vesting_shares.amount >= 0, - "Cannot withdraw negative GESTS. account: ${account}, vests:${vests}", - ("account", account)("vests", vesting_shares)); + GOLOS_CHECK_PARAM(account, validate_account_name(account)); + GOLOS_CHECK_PARAM(vesting_shares, { + GOLOS_CHECK_VALUE(is_asset_type(vesting_shares, VESTS_SYMBOL), "Amount must be GESTS"); + GOLOS_CHECK_VALUE(vesting_shares.amount >= 0, "Cannot withdraw negative GESTS"); + }); } void set_withdraw_vesting_route_operation::validate() const { - validate_account_name(from_account); - validate_account_name(to_account); - FC_ASSERT(0 <= percent && percent <= - STEEMIT_100_PERCENT, "Percent must be valid golos percent"); + GOLOS_CHECK_PARAM_ACCOUNT(from_account); + GOLOS_CHECK_PARAM_ACCOUNT(to_account); + GOLOS_CHECK_PARAM(percent, GOLOS_CHECK_VALUE_LEGE(percent, 0, STEEMIT_100_PERCENT)); + } + + + void chain_properties_17::validate() const { + GOLOS_CHECK_ASSET_GE(account_creation_fee, GOLOS, STEEMIT_MIN_ACCOUNT_CREATION_FEE); + GOLOS_CHECK_VALUE_GE(maximum_block_size, STEEMIT_MIN_BLOCK_SIZE_LIMIT); + GOLOS_CHECK_VALUE_LEGE(sbd_interest_rate, 0, STEEMIT_100_PERCENT); + } + + void chain_properties_18::validate() const { + chain_properties_17::validate(); + GOLOS_CHECK_ASSET_GT0(create_account_min_golos_fee, GOLOS); + GOLOS_CHECK_ASSET_GT0(create_account_min_delegation, GOLOS); + GOLOS_CHECK_ASSET_GT0(min_delegation, GOLOS); + GOLOS_CHECK_VALUE_GT(create_account_delegation_time, + (GOLOS_CREATE_ACCOUNT_DELEGATION_TIME).to_seconds() / 2); } void witness_update_operation::validate() const { - validate_account_name(owner); - FC_ASSERT(url.size() > 0, "URL size must be greater than 0"); - FC_ASSERT(fc::is_utf8(url), "URL is not valid UTF8"); - FC_ASSERT(fee >= asset(0, STEEM_SYMBOL), "Fee cannot be negative"); - props.validate(); + GOLOS_CHECK_PARAM_ACCOUNT(owner); + GOLOS_CHECK_PARAM(url, { + GOLOS_CHECK_VALUE_NOT_EMPTY(url); + // GOLOS_CHECK_VALUE_MAX_SIZE(url, STEEMIT_MAX_WITNESS_URL_LENGTH); // can't add without HF + GOLOS_CHECK_VALUE_UTF8(url); + }); + GOLOS_CHECK_PARAM(fee, { + GOLOS_CHECK_ASSET_GE0(fee, GOLOS); //"cannot be negative" + }); + GOLOS_CHECK_PARAM_VALIDATE(props); } struct chain_properties_validator { @@ -214,48 +249,53 @@ namespace golos { namespace protocol { }; void chain_properties_update_operation::validate() const { - validate_account_name(owner); - props.visit(chain_properties_validator()); + GOLOS_CHECK_PARAM_ACCOUNT(owner); + GOLOS_CHECK_PARAM(props, props.visit(chain_properties_validator())); } void account_witness_vote_operation::validate() const { - validate_account_name(account); - validate_account_name(witness); + GOLOS_CHECK_PARAM_ACCOUNT(account); + GOLOS_CHECK_PARAM_ACCOUNT(witness); } void account_witness_proxy_operation::validate() const { - validate_account_name(account); + GOLOS_CHECK_PARAM_ACCOUNT(account); if (proxy.size()) { - validate_account_name(proxy); + GOLOS_CHECK_PARAM(proxy, { + validate_account_name(proxy); + GOLOS_CHECK_VALUE(proxy != account, "Cannot proxy to self"); + }); } - FC_ASSERT(proxy != account, "Cannot proxy to self"); } void custom_operation::validate() const { /// required auth accounts are the ones whose bandwidth is consumed - FC_ASSERT(required_auths.size() > - 0, "at least on account must be specified"); + GOLOS_CHECK_PARAM(required_auths, + GOLOS_CHECK_VALUE(required_auths.size() > 0, "at least one account must be specified")); } void custom_json_operation::validate() const { /// required auth accounts are the ones whose bandwidth is consumed - FC_ASSERT((required_auths.size() + required_posting_auths.size()) > - 0, "at least on account must be specified"); - FC_ASSERT(id.size() <= 32, "id is too long"); - FC_ASSERT(fc::is_utf8(json), "JSON Metadata not formatted in UTF8"); - FC_ASSERT(fc::json::is_valid(json), "JSON Metadata not valid JSON"); + GOLOS_CHECK_PARAM(required_auths, + GOLOS_CHECK_VALUE((required_auths.size() + required_posting_auths.size()) > 0, "at least on account must be specified")); + GOLOS_CHECK_PARAM(id, GOLOS_CHECK_VALUE_MAX_SIZE(id, 32)); + GOLOS_CHECK_PARAM(json, { + GOLOS_CHECK_VALUE_UTF8(json); + GOLOS_CHECK_VALUE_JSON(json); + }); } void custom_binary_operation::validate() const { /// required auth accounts are the ones whose bandwidth is consumed - FC_ASSERT((required_owner_auths.size() + - required_active_auths.size() + - required_posting_auths.size()) > - 0, "at least on account must be specified"); - FC_ASSERT(id.size() <= 32, "id is too long"); - for (const auto &a : required_auths) { - a.validate(); - } + GOLOS_CHECK_PARAM(required_owner_auths, + GOLOS_CHECK_VALUE((required_owner_auths.size() + required_active_auths.size() + required_posting_auths.size()) > 0, + "at least one account must be specified")); + GOLOS_CHECK_PARAM(id, GOLOS_CHECK_VALUE_MAX_SIZE(id, 32)); + GOLOS_CHECK_PARAM(required_auths, { + for (const auto &a : required_auths) { + a.validate(); + } + }); } @@ -267,10 +307,11 @@ namespace golos { namespace protocol { void pow_operation::validate() const { props.validate(); - validate_account_name(worker_account); - FC_ASSERT(work_input() == - work.input, "Determninistic input does not match recorded input"); - work.validate(); + GOLOS_CHECK_PARAM_ACCOUNT(worker_account); + GOLOS_CHECK_PARAM(work, { + GOLOS_CHECK_VALUE(work_input() == work.input, "Determninistic input does not match recorded input"); + work.validate(); + }); } struct pow2_operation_validate_visitor { @@ -346,232 +387,239 @@ namespace golos { namespace protocol { } void pow::validate() const { - FC_ASSERT(work != fc::sha256()); - FC_ASSERT( - public_key_type(fc::ecc::public_key(signature, input, false)) == - worker); + GOLOS_CHECK_VALUE(work != fc::sha256(), "Work must not equal empty hash value"); + GOLOS_CHECK_VALUE(worker == public_key_type(fc::ecc::public_key(signature, input, false)), + "Work doesn't match to worker"); auto sig_hash = fc::sha256::hash(signature); public_key_type recover = fc::ecc::public_key(signature, sig_hash, false); - FC_ASSERT(work == fc::sha256::hash(recover)); + GOLOS_CHECK_VALUE(work == fc::sha256::hash(recover), "Invalid work result"); } void pow2::validate() const { - validate_account_name(input.worker_account); + GOLOS_CHECK_PARAM_ACCOUNT(input.worker_account); pow2 tmp; tmp.create(input.prev_block, input.worker_account, input.nonce); - FC_ASSERT(pow_summary == - tmp.pow_summary, "reported work does not match calculated work"); + GOLOS_CHECK_PARAM(pow_summary, + GOLOS_CHECK_VALUE(pow_summary == tmp.pow_summary, "reported work does not match calculated work")); } void equihash_pow::validate() const { - validate_account_name(input.worker_account); + GOLOS_CHECK_PARAM_ACCOUNT(input.worker_account); auto seed = fc::sha256::hash(input); - FC_ASSERT(proof.n == - STEEMIT_EQUIHASH_N, "proof of work 'n' value is incorrect"); - FC_ASSERT(proof.k == - STEEMIT_EQUIHASH_K, "proof of work 'k' value is incorrect"); - FC_ASSERT(proof.seed == - seed, "proof of work seed does not match expected seed"); - FC_ASSERT(proof.is_valid(), "proof of work is not a solution", ("block_id", input.prev_block)("worker_account", input.worker_account)("nonce", input.nonce)); - FC_ASSERT(pow_summary == - fc::sha256::hash(proof.inputs).approx_log_32()); + GOLOS_CHECK_PARAM(proof, { + GOLOS_CHECK_VALUE(proof.n == STEEMIT_EQUIHASH_N, "proof of work 'n' value is incorrect"); + GOLOS_CHECK_VALUE(proof.k == STEEMIT_EQUIHASH_K, "proof of work 'k' value is incorrect"); + GOLOS_CHECK_VALUE(proof.seed == seed, "proof of work seed does not match expected seed"); + GOLOS_CHECK_VALUE(proof.is_valid(), "proof of work is not a solution", ("block_id", input.prev_block)("worker_account", input.worker_account)("nonce", input.nonce)); + }); + GOLOS_CHECK_PARAM(pow_summary, + GOLOS_CHECK_VALUE_EQ(pow_summary, fc::sha256::hash(proof.inputs).approx_log_32())); } void feed_publish_operation::validate() const { - validate_account_name(publisher); - FC_ASSERT((is_asset_type(exchange_rate.base, STEEM_SYMBOL) && + GOLOS_CHECK_PARAM(publisher, validate_account_name(publisher)); + GOLOS_CHECK_LOGIC((is_asset_type(exchange_rate.base, STEEM_SYMBOL) && is_asset_type(exchange_rate.quote, SBD_SYMBOL)) || (is_asset_type(exchange_rate.base, SBD_SYMBOL) && is_asset_type(exchange_rate.quote, STEEM_SYMBOL)), + logic_exception::price_feed_must_be_for_golos_gbg_market, "Price feed must be a GOLOS/GBG price"); - exchange_rate.validate(); + GOLOS_CHECK_PARAM(exchange_rate, exchange_rate.validate()); } void limit_order_create_operation::validate() const { - validate_account_name(owner); - FC_ASSERT((is_asset_type(amount_to_sell, STEEM_SYMBOL) && + GOLOS_CHECK_PARAM(owner, validate_account_name(owner)); + GOLOS_CHECK_LOGIC((is_asset_type(amount_to_sell, STEEM_SYMBOL) && is_asset_type(min_to_receive, SBD_SYMBOL)) || (is_asset_type(amount_to_sell, SBD_SYMBOL) && is_asset_type(min_to_receive, STEEM_SYMBOL)), + logic_exception::limit_order_must_be_for_golos_gbg_market, "Limit order must be for the GOLOS:GBG market"); - (amount_to_sell / min_to_receive).validate(); + + auto price = (amount_to_sell / min_to_receive); + GOLOS_CHECK_PARAM(price, price.validate()); } void limit_order_create2_operation::validate() const { - validate_account_name(owner); - FC_ASSERT(amount_to_sell.symbol == - exchange_rate.base.symbol, "Sell asset must be the base of the price"); - exchange_rate.validate(); + GOLOS_CHECK_PARAM(owner, validate_account_name(owner)); + GOLOS_CHECK_PARAM(exchange_rate, exchange_rate.validate()); - FC_ASSERT((is_asset_type(amount_to_sell, STEEM_SYMBOL) && + GOLOS_CHECK_LOGIC((is_asset_type(amount_to_sell, STEEM_SYMBOL) && is_asset_type(exchange_rate.quote, SBD_SYMBOL)) || (is_asset_type(amount_to_sell, SBD_SYMBOL) && is_asset_type(exchange_rate.quote, STEEM_SYMBOL)), + logic_exception::limit_order_must_be_for_golos_gbg_market, "Limit order must be for the GOLOS:GBG market"); - FC_ASSERT((amount_to_sell * exchange_rate).amount > - 0, "Amount to sell cannot round to 0 when traded"); + GOLOS_CHECK_PARAM(amount_to_sell, { + GOLOS_CHECK_VALUE(amount_to_sell.symbol == exchange_rate.base.symbol, + "Sell asset must be the base of the price"); + GOLOS_CHECK_VALUE((amount_to_sell * exchange_rate).amount > 0, + "Amount to sell cannot round to 0 when traded"); + }); } void limit_order_cancel_operation::validate() const { - validate_account_name(owner); + GOLOS_CHECK_PARAM(owner, validate_account_name(owner)); } void convert_operation::validate() const { - validate_account_name(owner); + GOLOS_CHECK_PARAM_ACCOUNT(owner); /// only allow conversion from GBG to GOLOS, allowing the opposite can enable traders to abuse /// market fluxuations through converting large quantities without moving the price. - FC_ASSERT(is_asset_type(amount, SBD_SYMBOL), "Can only convert GBG to GOLOS"); - FC_ASSERT(amount.amount > 0, "Must convert some GBG"); + GOLOS_CHECK_PARAM(amount, GOLOS_CHECK_ASSET_GT0(amount, GBG)); } void report_over_production_operation::validate() const { - validate_account_name(reporter); - validate_account_name(first_block.witness); - FC_ASSERT(first_block.witness == second_block.witness); - FC_ASSERT(first_block.timestamp == second_block.timestamp); - FC_ASSERT(first_block.signee() == second_block.signee()); - FC_ASSERT(first_block.id() != second_block.id()); + GOLOS_CHECK_PARAM_ACCOUNT(reporter); + GOLOS_CHECK_PARAM_ACCOUNT(first_block.witness); + GOLOS_CHECK_PARAM(first_block, { + GOLOS_CHECK_VALUE(first_block.witness == second_block.witness, "Witness for first and second blocks must be equal"); + GOLOS_CHECK_VALUE(first_block.timestamp == second_block.timestamp, "Timestamp for first and second blocks must be equal"); + GOLOS_CHECK_VALUE(first_block.signee() == second_block.signee(), "Signee for first and second blocks must be equal"); + GOLOS_CHECK_VALUE(first_block.id() != second_block.id(), "ID for first and second blocks must be different"); + }); } void escrow_transfer_operation::validate() const { - validate_account_name(from); - validate_account_name(to); - validate_account_name(agent); - FC_ASSERT(fee.amount >= 0, "fee cannot be negative"); - FC_ASSERT(sbd_amount.amount >= 0, "gbg amount cannot be negative"); - FC_ASSERT(steem_amount.amount >= - 0, "steem amount cannot be negative"); - FC_ASSERT(sbd_amount.amount > 0 || steem_amount.amount > - 0, "escrow must transfer a non-zero amount"); - FC_ASSERT(from != agent && - to != agent, "agent must be a third party"); - FC_ASSERT((fee.symbol == STEEM_SYMBOL) || - (fee.symbol == SBD_SYMBOL), "fee must be GOLOS or GBG"); - FC_ASSERT(sbd_amount.symbol == - SBD_SYMBOL, "sbd amount must contain GBG"); - FC_ASSERT(steem_amount.symbol == - STEEM_SYMBOL, "golos amount must contain GOLOS"); - FC_ASSERT(ratification_deadline < - escrow_expiration, "ratification deadline must be before escrow expiration"); + GOLOS_CHECK_PARAM_ACCOUNT(from); + GOLOS_CHECK_PARAM_ACCOUNT(to); + GOLOS_CHECK_PARAM_ACCOUNT(agent); + GOLOS_CHECK_PARAM(fee, GOLOS_CHECK_ASSET_GE0(fee, GOLOS_OR_GBG)); + GOLOS_CHECK_PARAM(sbd_amount, GOLOS_CHECK_ASSET_GE0(sbd_amount, GBG)); + GOLOS_CHECK_PARAM(steem_amount, GOLOS_CHECK_ASSET_GE0(steem_amount, GOLOS)); + GOLOS_CHECK_LOGIC(sbd_amount.amount > 0 || steem_amount.amount > 0, + logic_exception::escrow_no_amount_set, + "escrow must release a non-zero amount"); + GOLOS_CHECK_PARAM(agent, GOLOS_CHECK_VALUE(from != agent && to != agent, "agent must be a third party")); + GOLOS_CHECK_LOGIC(ratification_deadline < escrow_expiration, + logic_exception::escrow_wrong_time_limits, + "ratification deadline must be before escrow expiration"); if (json_meta.size() > 0) { - FC_ASSERT(fc::is_utf8(json_meta), "JSON Metadata not formatted in UTF8"); - FC_ASSERT(fc::json::is_valid(json_meta), "JSON Metadata not valid JSON"); + GOLOS_CHECK_PARAM(json_meta, { + GOLOS_CHECK_VALUE_UTF8(json_meta); + GOLOS_CHECK_VALUE_JSON(json_meta); + }); } } void escrow_approve_operation::validate() const { - validate_account_name(from); - validate_account_name(to); - validate_account_name(agent); - validate_account_name(who); - FC_ASSERT(who == to || - who == agent, "to or agent must approve escrow"); + GOLOS_CHECK_PARAM_ACCOUNT(from); + GOLOS_CHECK_PARAM_ACCOUNT(to); + GOLOS_CHECK_PARAM_ACCOUNT(agent); + GOLOS_CHECK_PARAM_ACCOUNT(who); + GOLOS_CHECK_PARAM(who, GOLOS_CHECK_VALUE(who == to || who == agent, "to or agent must approve escrow")); } void escrow_dispute_operation::validate() const { - validate_account_name(from); - validate_account_name(to); - validate_account_name(agent); - validate_account_name(who); - FC_ASSERT(who == from || who == to, "who must be from or to"); + GOLOS_CHECK_PARAM_ACCOUNT(from); + GOLOS_CHECK_PARAM_ACCOUNT(to); + GOLOS_CHECK_PARAM_ACCOUNT(agent); + GOLOS_CHECK_PARAM_ACCOUNT(who); + GOLOS_CHECK_PARAM(who, GOLOS_CHECK_VALUE(who == from || who == to, "who must be from or to")); } void escrow_release_operation::validate() const { - validate_account_name(from); - validate_account_name(to); - validate_account_name(agent); - validate_account_name(who); - validate_account_name(receiver); - FC_ASSERT(who == from || who == to || - who == agent, "who must be from or to or agent"); - FC_ASSERT(receiver == from || - receiver == to, "receiver must be from or to"); - FC_ASSERT(sbd_amount.amount >= 0, "gbg amount cannot be negative"); - FC_ASSERT(steem_amount.amount >= - 0, "golos amount cannot be negative"); - FC_ASSERT(sbd_amount.amount > 0 || steem_amount.amount > - 0, "escrow must release a non-zero amount"); - FC_ASSERT(sbd_amount.symbol == - SBD_SYMBOL, "gbg amount must contain GBG"); - FC_ASSERT(steem_amount.symbol == - STEEM_SYMBOL, "golos amount must contain GOLOS"); + GOLOS_CHECK_PARAM_ACCOUNT(from); + GOLOS_CHECK_PARAM_ACCOUNT(to); + GOLOS_CHECK_PARAM_ACCOUNT(agent); + GOLOS_CHECK_PARAM_ACCOUNT(who); + GOLOS_CHECK_PARAM_ACCOUNT(receiver); + GOLOS_CHECK_PARAM(who, + GOLOS_CHECK_VALUE(who == from || who == to || who == agent, "who must be from or to or agent")); + GOLOS_CHECK_PARAM(receiver, + GOLOS_CHECK_VALUE(receiver == from || receiver == to, "receiver must be from or to")); + GOLOS_CHECK_PARAM(sbd_amount, GOLOS_CHECK_ASSET_GE0(sbd_amount, GBG)); + GOLOS_CHECK_PARAM(steem_amount, GOLOS_CHECK_ASSET_GE0(steem_amount, GOLOS)); + GOLOS_CHECK_LOGIC(sbd_amount.amount > 0 || steem_amount.amount > 0, + logic_exception::escrow_no_amount_set, + "escrow must release a non-zero amount"); } void request_account_recovery_operation::validate() const { - validate_account_name(recovery_account); - validate_account_name(account_to_recover); - new_owner_authority.validate(); + GOLOS_CHECK_PARAM_ACCOUNT(recovery_account); + GOLOS_CHECK_PARAM_ACCOUNT(account_to_recover); + GOLOS_CHECK_PARAM_VALIDATE(new_owner_authority); } void recover_account_operation::validate() const { - validate_account_name(account_to_recover); - FC_ASSERT(!(new_owner_authority == - recent_owner_authority), "Cannot set new owner authority to the recent owner authority"); - FC_ASSERT(!new_owner_authority.is_impossible(), "new owner authority cannot be impossible"); - FC_ASSERT(!recent_owner_authority.is_impossible(), "recent owner authority cannot be impossible"); - FC_ASSERT(new_owner_authority.weight_threshold, "new owner authority cannot be trivial"); - new_owner_authority.validate(); - recent_owner_authority.validate(); + GOLOS_CHECK_PARAM_ACCOUNT(account_to_recover); + GOLOS_CHECK_PARAM(new_owner_authority, { + GOLOS_CHECK_LOGIC(!(new_owner_authority == recent_owner_authority), + logic_exception::cannot_set_recent_recovery, + "Cannot set new owner authority to the recent owner authority"); + GOLOS_CHECK_VALUE(!new_owner_authority.is_impossible(), "new owner authority cannot be impossible"); + GOLOS_CHECK_VALUE(new_owner_authority.weight_threshold, "new owner authority cannot be trivial"); + new_owner_authority.validate(); + }); + GOLOS_CHECK_PARAM(recent_owner_authority, { + GOLOS_CHECK_VALUE(!recent_owner_authority.is_impossible(), "recent owner authority cannot be impossible"); + recent_owner_authority.validate(); + }); } void change_recovery_account_operation::validate() const { - validate_account_name(account_to_recover); - validate_account_name(new_recovery_account); + GOLOS_CHECK_PARAM_ACCOUNT(account_to_recover); + GOLOS_CHECK_PARAM_ACCOUNT(new_recovery_account); } void transfer_to_savings_operation::validate() const { - validate_account_name(from); - validate_account_name(to); - FC_ASSERT(amount.amount > 0); - FC_ASSERT(amount.symbol == STEEM_SYMBOL || - amount.symbol == SBD_SYMBOL); - FC_ASSERT(memo.size() < STEEMIT_MAX_MEMO_SIZE, "Memo is too large"); - FC_ASSERT(fc::is_utf8(memo), "Memo is not UTF8"); + GOLOS_CHECK_PARAM_ACCOUNT(from); + GOLOS_CHECK_PARAM_ACCOUNT(to); + GOLOS_CHECK_PARAM(amount, GOLOS_CHECK_ASSET_GT0(amount, GOLOS_OR_GBG)); + GOLOS_CHECK_PARAM(memo, { + GOLOS_CHECK_VALUE_MAX_SIZE(memo, STEEMIT_MAX_MEMO_SIZE - 1); //-1 to satisfy <= check (vs <) + GOLOS_CHECK_VALUE_UTF8(memo); + }); } void transfer_from_savings_operation::validate() const { - validate_account_name(from); - validate_account_name(to); - FC_ASSERT(amount.amount > 0); - FC_ASSERT(amount.symbol == STEEM_SYMBOL || - amount.symbol == SBD_SYMBOL); - FC_ASSERT(memo.size() < STEEMIT_MAX_MEMO_SIZE, "Memo is too large"); - FC_ASSERT(fc::is_utf8(memo), "Memo is not UTF8"); + GOLOS_CHECK_PARAM_ACCOUNT(from); + GOLOS_CHECK_PARAM_ACCOUNT(to); + GOLOS_CHECK_PARAM(amount, GOLOS_CHECK_ASSET_GT0(amount, GOLOS_OR_GBG)); + GOLOS_CHECK_PARAM(memo, { + GOLOS_CHECK_VALUE_MAX_SIZE(memo, STEEMIT_MAX_MEMO_SIZE - 1); //-1 to satisfy <= check. TODO: unify <=/< + GOLOS_CHECK_VALUE_UTF8(memo); + }); } void cancel_transfer_from_savings_operation::validate() const { - validate_account_name(from); + GOLOS_CHECK_PARAM_ACCOUNT(from); } void decline_voting_rights_operation::validate() const { - validate_account_name(account); + GOLOS_CHECK_PARAM_ACCOUNT(account); } void reset_account_operation::validate() const { - validate_account_name(reset_account); - validate_account_name(account_to_reset); - FC_ASSERT(!new_owner_authority.is_impossible(), "new owner authority cannot be impossible"); - FC_ASSERT(new_owner_authority.weight_threshold, "new owner authority cannot be trivial"); - new_owner_authority.validate(); + // This op is disabled. Maybe just remove it completely? + GOLOS_CHECK_PARAM_ACCOUNT(reset_account); + GOLOS_CHECK_PARAM_ACCOUNT(account_to_reset); + GOLOS_CHECK_PARAM(new_owner_authority, { + GOLOS_CHECK_VALUE(!new_owner_authority.is_impossible(), "New owner authority cannot be impossible"); + GOLOS_CHECK_VALUE(new_owner_authority.weight_threshold, "New owner authority cannot be trivial"); + new_owner_authority.validate(); + }); } void set_reset_account_operation::validate() const { - validate_account_name(account); + // This op is disabled. Maybe just remove it completely? + GOLOS_CHECK_PARAM_ACCOUNT(account); if (current_reset_account.size()) { - validate_account_name(current_reset_account); + GOLOS_CHECK_PARAM_ACCOUNT(current_reset_account); } - validate_account_name(reset_account); - FC_ASSERT(current_reset_account != - reset_account, "new reset account cannot be current reset account"); + GOLOS_CHECK_PARAM_ACCOUNT(reset_account); + GOLOS_CHECK_LOGIC(current_reset_account != reset_account, + logic_exception::cannot_set_same_reset_account, + "New reset account cannot be current reset account"); } void delegate_vesting_shares_operation::validate() const { - validate_account_name(delegator); - validate_account_name(delegatee); - FC_ASSERT(delegator != delegatee, "You cannot delegate GESTS to yourself"); - FC_ASSERT(is_asset_type(vesting_shares, VESTS_SYMBOL), "Delegation must be GESTS"); - FC_ASSERT(vesting_shares.amount >= 0, "Delegation cannot be negative"); + GOLOS_CHECK_PARAM_ACCOUNT(delegator); + GOLOS_CHECK_PARAM_ACCOUNT(delegatee); + GOLOS_CHECK_LOGIC(delegator != delegatee, logic_exception::cannot_delegate_to_yourself, + "You cannot delegate GESTS to yourself"); + GOLOS_CHECK_PARAM(vesting_shares, GOLOS_CHECK_ASSET_GE0(vesting_shares, GESTS)); } } } // golos::protocol diff --git a/libraries/protocol/transaction.cpp b/libraries/protocol/transaction.cpp index 5b9a75f1f5..5ff4d4fb6d 100644 --- a/libraries/protocol/transaction.cpp +++ b/libraries/protocol/transaction.cpp @@ -28,10 +28,22 @@ namespace golos { } void transaction::validate() const { - FC_ASSERT(operations.size() > - 0, "A transaction must have at least one operation", ("trx", *this)); + GOLOS_ASSERT(operations.size() > 0, + golos::protocol::transaction_exception, + "A transaction must have at least one operation", ("trx", *this)); + + uint32_t _current_op_in_trx = 0; for (const auto &op : operations) { - operation_validate(op); + try { + operation_validate(op); + ++_current_op_in_trx; + } catch (const fc::exception& e) { + FC_THROW_EXCEPTION(tx_invalid_operation, + "Invalid operation ${index} in transaction: ${errmsg}", + ("index", _current_op_in_trx) + ("errmsg", e.to_string()) + ("error", e)); + } } } @@ -122,9 +134,10 @@ namespace golos { * check for the merged authority of active and posting. */ if (required_posting.size()) { - FC_ASSERT(required_active.size() == 0); - FC_ASSERT(required_owner.size() == 0); - FC_ASSERT(other.size() == 0); + GOLOS_CHECK_LOGIC( + required_active.size() == 0 && required_owner.size() == 0 && other.size() == 0, + logic_exception::tx_with_both_posting_active_ops, + "Can't combine operations required posting authority and active or owner authority"); sign_state s(sigs, get_posting, avail); s.max_recursion = max_recursion_depth; @@ -255,8 +268,10 @@ namespace golos { sign_state s(get_signature_keys(chain_id), get_posting, available_keys); s.max_recursion = max_recursion_depth; - FC_ASSERT(!required_owner.size()); - FC_ASSERT(!required_active.size()); + GOLOS_CHECK_LOGIC( + required_active.size() == 0 && required_owner.size() == 0, + logic_exception::tx_with_both_posting_active_ops, + "Can't combine operations required posting authority and active or owner authority"); for (auto &posting : required_posting) { s.check_authority(posting); } diff --git a/libraries/protocol/types.cpp b/libraries/protocol/types.cpp index 43017f2316..fd0577f4da 100644 --- a/libraries/protocol/types.cpp +++ b/libraries/protocol/types.cpp @@ -1,11 +1,33 @@ #include #include +#include #include namespace golos { + + template<> + std::string get_logic_error_namespace() { + return ""; + } + namespace protocol { + template + binary_key key_data_from_string(const std::string &base58str) { + std::string prefix(STEEMIT_ADDRESS_PREFIX); + + const size_t prefix_len = prefix.size(); + GOLOS_CHECK_VALUE(base58str.size() > prefix_len, "Public key is too short"); + GOLOS_CHECK_VALUE(base58str.substr(0, prefix_len) == prefix, "Invalid public key prefix", ("base58str", base58str)); + auto bin = fc::from_base58(base58str.substr(prefix_len)); + auto bin_key = fc::raw::unpack(bin); // TODO catch exceptions from unpack + auto key_data = bin_key.data; + GOLOS_CHECK_VALUE(fc::ripemd160::hash(key_data.data, key_data.size())._hash[0] == bin_key.check, + "Invalid public key"); + return bin_key; + } + public_key_type::public_key_type() : key_data() { }; @@ -20,18 +42,7 @@ namespace golos { public_key_type::public_key_type(const std::string &base58str) { // TODO: Refactor syntactic checks into static is_valid() // to make public_key_type API more similar to address API - std::string prefix(STEEMIT_ADDRESS_PREFIX); - - const size_t prefix_len = prefix.size(); - FC_ASSERT(base58str.size() > prefix_len); - FC_ASSERT(base58str.substr(0, prefix_len) == - prefix, "", ("base58str", base58str)); - auto bin = fc::from_base58(base58str.substr(prefix_len)); - auto bin_key = fc::raw::unpack(bin); - key_data = bin_key.data; - FC_ASSERT( - fc::ripemd160::hash(key_data.data, key_data.size())._hash[0] == - bin_key.check); + key_data = key_data_from_string(base58str).data; }; @@ -78,18 +89,7 @@ namespace golos { }; extended_public_key_type::extended_public_key_type(const std::string &base58str) { - std::string prefix(STEEMIT_ADDRESS_PREFIX); - - const size_t prefix_len = prefix.size(); - FC_ASSERT(base58str.size() > prefix_len); - FC_ASSERT(base58str.substr(0, prefix_len) == - prefix, "", ("base58str", base58str)); - auto bin = fc::from_base58(base58str.substr(prefix_len)); - auto bin_key = fc::raw::unpack(bin); - FC_ASSERT( - fc::ripemd160::hash(bin_key.data.data, bin_key.data.size())._hash[0] == - bin_key.check); - key_data = bin_key.data; + key_data = key_data_from_string(base58str).data; } extended_public_key_type::operator fc::ecc::extended_public_key() const { @@ -131,18 +131,7 @@ namespace golos { }; extended_private_key_type::extended_private_key_type(const std::string &base58str) { - std::string prefix(STEEMIT_ADDRESS_PREFIX); - - const size_t prefix_len = prefix.size(); - FC_ASSERT(base58str.size() > prefix_len); - FC_ASSERT(base58str.substr(0, prefix_len) == - prefix, "", ("base58str", base58str)); - auto bin = fc::from_base58(base58str.substr(prefix_len)); - auto bin_key = fc::raw::unpack(bin); - FC_ASSERT( - fc::ripemd160::hash(bin_key.data.data, bin_key.data.size())._hash[0] == - bin_key.check); - key_data = bin_key.data; + key_data = key_data_from_string(base58str).data; } extended_private_key_type::operator fc::ecc::extended_private_key() const { diff --git a/libraries/protocol/version.cpp b/libraries/protocol/version.cpp index c058628204..eb36fb548f 100644 --- a/libraries/protocol/version.cpp +++ b/libraries/protocol/version.cpp @@ -1,6 +1,5 @@ #include - -#include +#include namespace golos { namespace protocol { @@ -52,12 +51,12 @@ namespace fc { s >> major >> dot_a >> hardfork >> dot_b >> revision; // We'll accept either m.h.v or m_h_v as canonical version strings - FC_ASSERT((dot_a == '.' || dot_a == '_') && dot_a == - dot_b, "Variant does not contain proper dotted decimal format"); - FC_ASSERT(major <= 0xFF, "Major version is out of range"); - FC_ASSERT(hardfork <= 0xFF, "Hardfork version is out of range"); - FC_ASSERT(revision <= 0xFFFF, "Revision version is out of range"); - FC_ASSERT(s.eof(), "Extra information at end of version string"); + GOLOS_CHECK_VALUE((dot_a == '.' || dot_a == '_') && dot_a == dot_b, + "Variant does not contain proper dotted decimal format"); + GOLOS_CHECK_VALUE(major <= 0xFF, "Major version is out of range"); + GOLOS_CHECK_VALUE(hardfork <= 0xFF, "Hardfork version is out of range"); + GOLOS_CHECK_VALUE(revision <= 0xFFFF, "Revision version is out of range"); + GOLOS_CHECK_VALUE(s.eof(), "Extra information at end of version string"); v.v_num = 0 | (major << 24) | (hardfork << 16) | revision; } diff --git a/libraries/time/include/golos/time/time.hpp b/libraries/time/include/golos/time/time.hpp index 86c4a7195d..55969870a4 100644 --- a/libraries/time/include/golos/time/time.hpp +++ b/libraries/time/include/golos/time/time.hpp @@ -10,17 +10,8 @@ namespace golos { typedef fc::signal time_discontinuity_signal_type; extern time_discontinuity_signal_type time_discontinuity_signal; - fc::optional ntp_time(); - fc::time_point now(); - fc::time_point nonblocking_now(); // identical to now() but guaranteed not to block - void update_ntp_time(); - - fc::microseconds ntp_error(); - - void shutdown_ntp_time(); - void start_simulated_time(const fc::time_point sim_time); void advance_simulated_time_to(const fc::time_point sim_time); diff --git a/libraries/time/time.cpp b/libraries/time/time.cpp index cd12132df5..7bf5e44eff 100644 --- a/libraries/time/time.cpp +++ b/libraries/time/time.cpp @@ -15,70 +15,13 @@ namespace golos { time_discontinuity_signal_type time_discontinuity_signal; - namespace detail { - std::atomic ntp_service(nullptr); - fc::mutex ntp_service_initialization_mutex; - } - - fc::optional ntp_time() { - fc::ntp *actual_ntp_service = detail::ntp_service.load(); - if (!actual_ntp_service) { - fc::scoped_lock lock(detail::ntp_service_initialization_mutex); - actual_ntp_service = detail::ntp_service.load(); - if (!actual_ntp_service) { - actual_ntp_service = new fc::ntp; - detail::ntp_service.store(actual_ntp_service); - } - } - return actual_ntp_service->get_time(); - } - - void shutdown_ntp_time() { - fc::ntp *actual_ntp_service = detail::ntp_service.exchange(nullptr); - delete actual_ntp_service; - } - fc::time_point now() { if (simulated_time) { return fc::time_point() + fc::seconds(simulated_time + adjusted_time_sec); } - fc::optional current_ntp_time = ntp_time(); - if (current_ntp_time.valid()) { - return *current_ntp_time + fc::seconds(adjusted_time_sec); - } else { - return fc::time_point::now() + fc::seconds(adjusted_time_sec); - } - } - - fc::time_point nonblocking_now() { - if (simulated_time) { - return fc::time_point() + - fc::seconds(simulated_time + adjusted_time_sec); - } - - fc::ntp *actual_ntp_service = detail::ntp_service.load(); - fc::optional current_ntp_time; - if (actual_ntp_service) { - current_ntp_time = actual_ntp_service->get_time(); - } - - if (current_ntp_time) { - return *current_ntp_time + fc::seconds(adjusted_time_sec); - } else { - return fc::time_point::now() + fc::seconds(adjusted_time_sec); - } - } - - void update_ntp_time() { - detail::ntp_service.load()->request_now(); - } - - fc::microseconds ntp_error() { - fc::optional current_ntp_time = ntp_time(); - FC_ASSERT(current_ntp_time, "We don't have NTP time!"); - return *current_ntp_time - fc::time_point::now(); + return fc::time_point::now() + fc::seconds(adjusted_time_sec); } void start_simulated_time(const fc::time_point sim_time) { diff --git a/libraries/wallet/CMakeLists.txt b/libraries/wallet/CMakeLists.txt index bab0aa623b..dd18237500 100644 --- a/libraries/wallet/CMakeLists.txt +++ b/libraries/wallet/CMakeLists.txt @@ -8,7 +8,7 @@ if(PERL_FOUND AND DOXYGEN_FOUND AND NOT "${CMAKE_GENERATOR}" STREQUAL "Ninja") add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/doxygen/perlmod/DoxyDocs.pm WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${DOXYGEN_EXECUTABLE} - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile ${CMAKE_CURRENT_SOURCE_DIR}/include/steemit/wallet/wallet.hpp) + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile ${CMAKE_CURRENT_SOURCE_DIR}/include/golos/wallet/wallet.hpp) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate_api_documentation.pl ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp.new @@ -30,7 +30,9 @@ list(APPEND ${CURRENT_TARGET}_HEADERS include/golos/wallet/wallet.hpp ) +# don't remove "api_documentation.cpp", it's required to auto-generate help list(APPEND ${CURRENT_TARGET}_SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp api_documentation_standin.cpp wallet.cpp ) diff --git a/libraries/wallet/include/golos/wallet/remote_node_api.hpp b/libraries/wallet/include/golos/wallet/remote_node_api.hpp index d9d767ec77..8ab1dfd3d5 100644 --- a/libraries/wallet/include/golos/wallet/remote_node_api.hpp +++ b/libraries/wallet/include/golos/wallet/remote_node_api.hpp @@ -1,24 +1,26 @@ #pragma once -#include +#include #include +#include #include -#include -#include +#include +#include +#include #include +#include +#include +#include #include -#include #include +#include + +#include #include +#include #include -#include -#include -#include -#include -#include -#include -#include +#include namespace golos { namespace wallet { @@ -28,7 +30,6 @@ using fc::optional; using namespace chain; using namespace plugins; -//using namespace plugins::condenser_api; using namespace plugins::database_api; using namespace plugins::follow; using namespace plugins::social_network; @@ -36,8 +37,9 @@ using namespace plugins::tags; using namespace plugins::market_history; using namespace plugins::network_broadcast_api; using namespace plugins::private_message; -using namespace golos::api; using namespace plugins::witness_api; +using namespace plugins::account_history; +using namespace golos::api; /** * This is a dummy class exists only to provide method signature information to fc::api, not to execute calls. @@ -87,7 +89,7 @@ struct remote_operation_history { * Class is used by wallet to send formatted API calls to operation_history plugin on remote node. */ struct remote_account_history { - map get_account_history( account_name_type, uint64_t, uint32_t ); + history_operations get_account_history(account_name_type, uint64_t, uint32_t, account_history_query); }; /** @@ -175,8 +177,13 @@ struct remote_market_history { * Class is used by wallet to send formatted API calls to market_history plugin on remote node. */ struct remote_private_message { - vector get_inbox(const std::string& to, time_point newest, uint16_t limit, std::uint64_t offset) const; - vector get_outbox(const std::string& from, time_point newest, uint16_t limit, std::uint64_t offset) const; + vector get_inbox(const std::string& to, const message_box_query&) const; + vector get_outbox(const std::string& from, const message_box_query&) const; + vector get_thread(const std::string& from, const std::string& to, const message_thread_query&) const; + settings_api_object get_settings(const std::string& owner) const; + contacts_size_api_object get_contacts_size(const std::string& owner) const; + contact_api_object get_contact_info(const std::string& owner, const std::string& contact) const; + std::vector get_contacts(const std::string& owner, private_contact_type, uint16_t limit, uint32_t offset) const; }; /** @@ -324,6 +331,11 @@ FC_API( golos::wallet::remote_market_history, FC_API( golos::wallet::remote_private_message, (get_inbox) (get_outbox) + (get_thread) + (get_settings) + (get_contacts) + (get_contacts_size) + (get_contact_info) ) /** diff --git a/libraries/wallet/include/golos/wallet/time_converter.hpp b/libraries/wallet/include/golos/wallet/time_converter.hpp index 7ff112ced2..655372e0d6 100644 --- a/libraries/wallet/include/golos/wallet/time_converter.hpp +++ b/libraries/wallet/include/golos/wallet/time_converter.hpp @@ -8,7 +8,7 @@ class time_converter { private: time_point_sec tps; public: - time_converter(const std::string &s, const time_point_sec &start_tps, const time_point_sec &default_tps) { + time_converter(const std::string& s, const time_point_sec& start_tps, const time_point_sec& default_tps) { if (s.empty()) { tps = default_tps; return; @@ -18,6 +18,11 @@ class time_converter { tps += std::stoi(s.substr(1)); return; } + if (s.at(0) == '-') { + tps = start_tps; + tps -= std::stoi(s.substr(1)); + return; + } tps = time_point_sec::from_iso_string(s); if (tps.sec_since_epoch() == 0) { tps = default_tps; diff --git a/libraries/wallet/include/golos/wallet/wallet.hpp b/libraries/wallet/include/golos/wallet/wallet.hpp index 6264214880..0a93512ca3 100644 --- a/libraries/wallet/include/golos/wallet/wallet.hpp +++ b/libraries/wallet/include/golos/wallet/wallet.hpp @@ -3,7 +3,8 @@ #include #include #include -#include +#include +#include #include @@ -19,6 +20,7 @@ namespace golos { namespace wallet { using namespace golos::utilities; using namespace golos::protocol; using namespace golos::plugins::private_message; + using namespace golos::plugins::account_history; typedef uint16_t transaction_handle_type; @@ -44,6 +46,37 @@ namespace golos { namespace wallet { fc::optional min_delegation; }; + struct optional_private_box_query { + fc::flat_set select_accounts; + fc::flat_set filter_accounts; + std::string newest_date; + fc::optional unread_only; + fc::optional limit; + fc::optional offset; + }; + + struct optional_private_thread_query { + std::string newest_date; + fc::optional unread_only; + fc::optional limit; + fc::optional offset; + }; + + struct message_body { + std::string subject; + std::string body; + }; + + struct extended_message_object: public message_api_object { + extended_message_object() = default; + + extended_message_object(const message_api_object& o) + : message_api_object(o) { + } + + message_body message; + }; + struct memo_data { static optional from_string( string str ) { @@ -51,7 +84,7 @@ namespace golos { namespace wallet { if( str.size() > sizeof(memo_data) && str[0] == '#') { auto data = fc::from_base58( str.substr(1) ); auto m = fc::raw::unpack( data ); - FC_ASSERT( string(m) == str ); + GOLOS_CHECK_VALUE( string(m) == str, "Invalid encoded memo data" ); return m; } } catch ( ... ) {} @@ -1063,8 +1096,23 @@ namespace golos { namespace wallet { * @param from - the absolute sequence number, -1 means most recent, limit is the number of operations before from. * @param limit - the maximum number of items that can be queried (0 to 1000], must be less than from */ - map< uint32_t, golos::plugins::operation_history::applied_operation > - get_account_history( string account, uint32_t from, uint32_t limit ); + history_operations get_account_history(string account, uint32_t from, uint32_t limit); + + /** + * Account operations have sequence numbers from 0 to N where N is the most recent operation. + * This method returns operations in the range [from-limit, from] + * + * @param account - name of account, which history requested. + * @param from - the absolute sequence number, -1 means most recent, limit is the number of operations before from. + * @param limit - the maximum number of items that can be queried (0 to 1000], must be less than from + * @param query - filtering query - object with following optional fields: + * { + * select_ops - list of operations to include. special values: ALL, REAL, VIRTUAL. if skipped = ALL + * filter_ops - blacklist. if skipped = empty list (nothing blacklisted) + * dir - direction of operation in relation to account: any, sender, receiver, dual. Experimental + * } + */ + history_operations filter_account_history(string account, uint32_t from, uint32_t limit, account_history_query query); FC_TODO(Supplement API argument description) @@ -1107,13 +1155,185 @@ namespace golos { namespace wallet { annotated_signed_transaction decline_voting_rights( string account, bool decline, bool broadcast ); - // Private message - vector get_inbox( - const std::string& to, time_point newest, uint16_t limit, std::uint64_t offset); - vector get_outbox( - const std::string& from, time_point newest, uint16_t limit, std::uint64_t offset); + /** + * Select inbox private messages for `to` account + */ + vector get_private_inbox( + const std::string& to, const optional_private_box_query& query); + + /** + * Select outbox private messages for `from` account + */ + vector get_private_outbox( + const std::string& from, const optional_private_box_query& query); + + /** + * Select thread private messages between `from ` and `to` accounts + */ + vector get_private_thread( + const std::string& from, const std::string& to, const optional_private_thread_query& query); + + /** + * Change settings for private messages + * + * @param owner + * @param settings + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction set_private_settings( + const std::string& owner, const settings_api_object& settings, bool broadcast); + + /** + * Get settings for private messages + * + * @param owner + * @return the settings for private messages + */ + settings_api_object get_private_settings(const std::string& owner); + + /** + * Add/modify contact list + * + * @param owner + * @param contact + * @param type (undefined - remove it if no messages, pinned, ignore) + * @param json_metadata + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction add_private_contact( + const std::string& owner, const std::string& contact, private_contact_type type, + fc::optional json_metadata, bool broadcast); + + /** + * Get contact list + * + * @param owner + * @param type (undefined, pinned, ignore) + * @param limit + * @param offset + * @return List of contacts + */ + vector get_private_contacts( + const std::string& owner, private_contact_type type, uint16_t limit, uint32_t offset); + + /** + * Get contact info + * + * @param owner + * @param contact + * @return Contact + */ + contact_api_object get_private_contact(const std::string& owner, const std::string& contact); + + /** + * Send an encrypted private message from one account to other + * + * @param from account from which you send message + * @param to account to which you send message + * @param message to send + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction send_private_message( + const std::string& from, const std::string& to, const message_body& message, bool broadcast); + + /** + * Edit an encrypted private message from one account to other + * + * @param from account from which you send message + * @param to account to which you send message + * @param nonce of sended message + * @param message to send + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction edit_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, + const message_body& message, bool broadcast); + + /** + * Delete encrypted private message from inbox + * + * @param from account from which you send message + * @param to account to which you send message + * @param nonce of sended message + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction delete_inbox_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, bool broadcast); + + /** + * Delete encrypted private messages from inbox by date range + * + * @param from account from which you send message + * @param to account to which you send message + * @param start_date begin of date range + * @param stop_date begin of date range + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction delete_inbox_private_messages( + const std::string& from, const std::string& to, + const std::string& start_date, const std::string& stop_date, bool broadcast); + + /** + * Delete encrypted private message from outbox + * + * @param from account from which you send message + * @param to account to which you send message + * @param nonce of sended message + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction delete_outbox_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, bool broadcast); + + /** + * Delete encrypted private messages from outbox by date range + * + * @param from account from which you send message + * @param to account to which you send message + * @param start_date begin of date range + * @param stop_date begin of date range + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction delete_outbox_private_messages( + const std::string& from, const std::string& to, + const std::string& start_date, const std::string& stop_date, bool broadcast); + + /** + * Mark encrypted private message with read time + * + * @param from account from which you send message + * @param to account to which you send message + * @param nonce of sended message + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction mark_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, bool broadcast); + + /** + * Mark encrypted private messages with read time by date range + * + * @param from account from which you send message + * @param to account to which you send message + * @param start_date begin of date range + * @param stop_date begin of date range + * @param broadcast true if you wish to broadcast the transaction + * @return the signed version of the transaction + */ + annotated_signed_transaction mark_private_messages( + const std::string& from, const std::string& to, + const std::string& start_date, const std::string& stop_date, bool broadcast); + + private: + void decrypt_history_memos(history_operations& result); - message_body try_decrypt_message( const message_api_obj& mo ); }; struct plain_keys { @@ -1164,6 +1384,7 @@ FC_API( golos::wallet::wallet_api, (get_feed_history) (get_conversion_requests) (get_account_history) + (filter_account_history) (get_withdraw_routes) /// transaction api @@ -1229,8 +1450,23 @@ FC_API( golos::wallet::wallet_api, (get_active_witnesses) (get_miner_queue) (get_transaction) - (get_inbox) - (get_outbox) + + (get_private_inbox) + (get_private_outbox) + (get_private_thread) + (set_private_settings) + (get_private_settings) + (get_private_contacts) + (get_private_contact) + (add_private_contact) + (send_private_message) + (edit_private_message) + (delete_inbox_private_message) + (delete_inbox_private_messages) + (delete_outbox_private_message) + (delete_outbox_private_messages) + (mark_private_message) + (mark_private_messages) ) FC_REFLECT((golos::wallet::memo_data), (from)(to)(nonce)(check)(encrypted)) @@ -1241,7 +1477,24 @@ FC_REFLECT( (posting_approvals_to_add)(posting_approvals_to_remove) (key_approvals_to_add)(key_approvals_to_remove)) +FC_REFLECT( + (golos::wallet::optional_private_box_query), + (select_accounts)(filter_accounts)(newest_date)(limit)(offset)(unread_only)) + +FC_REFLECT( + (golos::wallet::optional_private_thread_query), + (newest_date)(limit)(offset)(unread_only)) + FC_REFLECT((golos::wallet::optional_chain_props), (account_creation_fee)(maximum_block_size)(sbd_interest_rate) (create_account_min_golos_fee)(create_account_min_delegation) (create_account_delegation_time)(min_delegation)) + +FC_REFLECT( + (golos::wallet::message_body), + (subject)(body)) + +FC_REFLECT_DERIVED( + (golos::wallet::extended_message_object), + ((golos::plugins::private_message::message_api_object)), + (message)) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 8a667d6c7e..631c810f06 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -57,8 +58,38 @@ # include #endif +#define WALLET_CHECK_UNLOCKED() \ + GOLOS_ASSERT(!is_locked(), golos::wallet::wallet_is_locked, "The wallet must be unlocked before operation") + #define BRAIN_KEY_WORD_COUNT 16 +namespace golos { namespace wallet { + + struct logic_errors { + enum types { + detected_private_key_in_memo, + owner_authority_change_would_render_account_irrecoverable, + private_key_not_available, + no_account_in_lut, + malformed_private_key, + }; + }; + + GOLOS_DECLARE_DERIVED_EXCEPTION( + wallet_is_locked, golos::golos_exception, + 10000, "The wallet must be unlocked"); + +} } // golos::wallet + +namespace golos { + +template<> +std::string get_logic_error_namespace() { + return "wallet"; +} + +} // golos + namespace golos { namespace wallet { namespace detail { @@ -370,6 +401,18 @@ namespace golos { namespace wallet { return result; } + fc::variant make_operation_id(transaction_handle_type handle, uint32_t op_index) { + return fc::mutable_variant_object()("transaction",handle)("op_index",op_index); + } + + signed_transaction &get_builder_transaction(transaction_handle_type handle) { + auto trx = _builder_transactions.find(handle); + if (trx == _builder_transactions.end()) { + GOLOS_THROW_MISSING_OBJECT("transaction", handle); + } + return trx->second; + } + transaction_handle_type begin_builder_transaction() { transaction_handle_type handle = 0; if (!_builder_transactions.empty()) { @@ -382,8 +425,8 @@ namespace golos { namespace wallet { void add_operation_to_builder_transaction( transaction_handle_type handle, const operation& op ) { - FC_ASSERT(_builder_transactions.count(handle)); - _builder_transactions[handle].operations.emplace_back(op); + auto& trx = get_builder_transaction(handle); + trx.operations.emplace_back(op); } void add_operation_copy_to_builder_transaction( @@ -391,12 +434,12 @@ namespace golos { namespace wallet { transaction_handle_type dst_handle, uint32_t op_index ) { - FC_ASSERT(_builder_transactions.count(src_handle)); - FC_ASSERT(_builder_transactions.count(dst_handle)); - signed_transaction& trx = _builder_transactions[src_handle]; - FC_ASSERT(op_index < trx.operations.size()); - const auto op = trx.operations[op_index]; - _builder_transactions[dst_handle].operations.emplace_back(op); + auto& src_trx = get_builder_transaction(src_handle); + auto& dst_trx = get_builder_transaction(dst_handle); + if (op_index >= src_trx.operations.size()) + GOLOS_THROW_MISSING_OBJECT("operation", make_operation_id(src_handle,op_index)); + const auto op = src_trx.operations[op_index]; + dst_trx.operations.emplace_back(op); } void replace_operation_in_builder_transaction( @@ -404,20 +447,19 @@ namespace golos { namespace wallet { uint32_t op_index, const operation& new_op ) { - FC_ASSERT(_builder_transactions.count(handle)); - signed_transaction& trx = _builder_transactions[handle]; - FC_ASSERT(op_index < trx.operations.size()); + auto& trx = get_builder_transaction(handle); + if (op_index >= trx.operations.size()) + GOLOS_THROW_MISSING_OBJECT("operation", make_operation_id(handle,op_index)); trx.operations[op_index] = new_op; } transaction preview_builder_transaction(transaction_handle_type handle) { - FC_ASSERT(_builder_transactions.count(handle)); - return _builder_transactions[handle]; + return get_builder_transaction(handle); } signed_transaction sign_builder_transaction(transaction_handle_type handle, bool broadcast) { - FC_ASSERT(_builder_transactions.count(handle)); - return _builder_transactions[handle] = sign_transaction(_builder_transactions[handle], broadcast); + auto& trx = get_builder_transaction(handle); + return _builder_transactions[handle] = sign_transaction(trx, broadcast); } signed_transaction propose_builder_transaction( @@ -429,7 +471,7 @@ namespace golos { namespace wallet { std::string review_period_time, bool broadcast ) { - FC_ASSERT(_builder_transactions.count(handle)); + (void)get_builder_transaction(handle); proposal_create_operation op; op.author = author; op.title = title; @@ -501,9 +543,11 @@ namespace golos { namespace wallet { return _remote_database_api->get_proposed_transactions(account, from, limit); } - golos::api::account_api_object get_account( string account_name ) const { + golos::api::account_api_object get_account(const string& account_name) const { auto accounts = _remote_database_api->get_accounts( { account_name } ); - FC_ASSERT( !accounts.empty(), "Unknown account" ); + if (accounts.size() != 1 || account_name != accounts[0].name) { + GOLOS_THROW_MISSING_OBJECT("account", account_name); + } return accounts.front(); } @@ -518,7 +562,8 @@ namespace golos { namespace wallet { fc::ecc::private_key get_private_key(const public_key_type& id)const { auto has_key = try_get_private_key( id ); - FC_ASSERT( has_key ); + if (!has_key) + GOLOS_THROW_MISSING_OBJECT("private_key", id); return *has_key; } @@ -676,7 +721,7 @@ namespace golos { namespace wallet { } void set_transaction_expiration( uint32_t tx_expiration_seconds ) { - FC_ASSERT( tx_expiration_seconds < STEEMIT_MAX_TIME_UNTIL_EXPIRATION ); + GOLOS_CHECK_VALUE_LT( tx_expiration_seconds, STEEMIT_MAX_TIME_UNTIL_EXPIRATION ); _tx_expiration_seconds = tx_expiration_seconds; } @@ -687,88 +732,96 @@ namespace golos { namespace wallet { flat_set< account_name_type > req_posting_approvals; vector< authority > other_auths; - tx.get_required_authorities( req_active_approvals, req_owner_approvals, req_posting_approvals, other_auths ); + // gets required account names of operations + tx.get_required_authorities( req_active_approvals, req_owner_approvals, + req_posting_approvals, other_auths ); for( const auto& auth : other_auths ) for( const auto& a : auth.account_auths ) req_active_approvals.insert(a.first); - // std::merge lets us de-duplicate account_id's that occur in both - // sets, and dump them into a vector (as required by remote_db api) - // at the same time - vector< account_name_type > v_approving_account_names; - std::merge(req_active_approvals.begin(), req_active_approvals.end(), - req_owner_approvals.begin() , req_owner_approvals.end(), - std::back_inserter( v_approving_account_names ) ); - - for( const auto& a : req_posting_approvals ) - v_approving_account_names.push_back(a); + // collects all keys to common set and accounts to common map - /// TODO: fetch the accounts specified via other_auths as well. + flat_map approving_account_lut; - auto approving_account_objects = _remote_database_api->get_accounts( v_approving_account_names ); - - /// TODO: recursively check one layer deeper in the authority tree for keys + flat_set approving_key_set; - FC_ASSERT( approving_account_objects.size() == v_approving_account_names.size(), "", ("aco.size:", approving_account_objects.size())("acn",v_approving_account_names.size()) ); + std::vector active_account_auths; + std::vector owner_account_auths; + std::vector posting_account_auths; - flat_map< string, golos::api::account_api_object > approving_account_lut; - size_t i = 0; - for( const optional< golos::api::account_api_object >& approving_acct : approving_account_objects ) { - if( !approving_acct.valid() ) { - wlog( "operation_get_required_auths said approval of non-existing account ${name} was needed", - ("name", v_approving_account_names[i]) ); - i++; - continue; + auto fetch_keys = [&](const authority& auth) { + for (const public_key_type& approving_key : auth.get_keys()) { + wdump((approving_key)); + approving_key_set.insert( approving_key ); } - approving_account_lut[ approving_acct->name ] = *approving_acct; - i++; - } - auto get_account_from_lut = [&]( const std::string& name ) -> const golos::api::account_api_object& { - auto it = approving_account_lut.find( name ); - FC_ASSERT( it != approving_account_lut.end() ); - return it->second; }; - flat_set approving_key_set; - for( account_name_type& acct_name : req_active_approvals ) { - const auto it = approving_account_lut.find( acct_name ); - if( it == approving_account_lut.end() ) - continue; - const golos::api::account_api_object& acct = it->second; - vector v_approving_keys = acct.active.get_keys(); - wdump((v_approving_keys)); - for( const public_key_type& approving_key : v_approving_keys ) { - wdump((approving_key)); - approving_key_set.insert( approving_key ); + if (!req_active_approvals.empty()) { + auto req_active_accs =_remote_database_api->get_accounts(std::vector( + req_active_approvals.begin(), req_active_approvals.end())); + for (auto& acc : req_active_accs) { + approving_account_lut[acc.name] = acc; + fetch_keys(acc.active); + for (const auto& auth : acc.active.account_auths) { + active_account_auths.push_back(auth.first); + } } } - - for( account_name_type& acct_name : req_posting_approvals ) { - const auto it = approving_account_lut.find( acct_name ); - if( it == approving_account_lut.end() ) - continue; - const golos::api::account_api_object& acct = it->second; - vector v_approving_keys = acct.posting.get_keys(); - wdump((v_approving_keys)); - for( const public_key_type& approving_key : v_approving_keys ) - { - wdump((approving_key)); - approving_key_set.insert( approving_key ); + if (!req_owner_approvals.empty()) { + auto req_owner_accs =_remote_database_api->get_accounts(std::vector( + req_owner_approvals.begin(), req_owner_approvals.end())); + for (auto& acc : req_owner_accs) { + approving_account_lut[acc.name] = acc; + fetch_keys(acc.owner); + for (const auto& auth : acc.owner.account_auths) { + owner_account_auths.push_back(auth.first); + } + } + } + if (!req_posting_approvals.empty()) { + auto req_posting_accs =_remote_database_api->get_accounts(std::vector( + req_posting_approvals.begin(), req_posting_approvals.end())); + for (auto& acc : req_posting_accs) { + approving_account_lut[acc.name] = acc; + fetch_keys(acc.posting); + for (const auto& auth : acc.posting.account_auths) { + posting_account_auths.push_back(auth.first); + } } } - for( const account_name_type& acct_name : req_owner_approvals ) { - const auto it = approving_account_lut.find( acct_name ); - if( it == approving_account_lut.end() ) - continue; - const golos::api::account_api_object& acct = it->second; - vector v_approving_keys = acct.owner.get_keys(); - for( const public_key_type& approving_key : v_approving_keys ) { - wdump((approving_key)); - approving_key_set.insert( approving_key ); + if (!active_account_auths.empty()) { + auto active_account_auth_objs = _remote_database_api->get_accounts(active_account_auths); + for (auto& acc : active_account_auth_objs) { + approving_account_lut[acc.name] = acc; + fetch_keys(acc.active); + } + } + if (!owner_account_auths.empty()) { + auto owner_account_auth_objs = _remote_database_api->get_accounts(owner_account_auths); + for (auto& acc : owner_account_auth_objs) { + approving_account_lut[acc.name] = acc; + fetch_keys(acc.owner); + } + } + if (!posting_account_auths.empty()) { + auto posting_account_auth_objs = _remote_database_api->get_accounts(posting_account_auths); + for (auto& acc : posting_account_auth_objs) { + approving_account_lut[acc.name] = acc; + fetch_keys(acc.posting); } } + + auto get_account_from_lut = [&]( const std::string& name ) -> const golos::api::account_api_object& { + auto it = approving_account_lut.find( name ); + GOLOS_CHECK_LOGIC( it != approving_account_lut.end(), + logic_errors::no_account_in_lut, + "No account in lut: '${name}'", ("name",name) ); + return it->second; + }; + + // get keys of each other auth into common set for( const authority& a : other_auths ) { for( const auto& k : a.key_auths ) { wdump((k.first)); @@ -781,6 +834,8 @@ namespace golos { namespace wallet { tx.set_expiration( dyn_props.time + fc::seconds(_tx_expiration_seconds) ); tx.signatures.clear(); + // checking each key from common set exists in wallet's keys + // and adding available ones to available set and map //idump((_keys)); flat_set< public_key_type > available_keys; flat_map< public_key_type, fc::ecc::private_key > available_private_keys; @@ -790,12 +845,16 @@ namespace golos { namespace wallet { if( it != _keys.end() ) { fc::optional privkey = wif_to_key( it->second ); - FC_ASSERT( privkey.valid(), "Malformed private key in _keys" ); + GOLOS_CHECK_LOGIC(privkey.valid(), + logic_errors::malformed_private_key, + "Malformed private key in _keys for public key ${key}", + ("key",key)); available_keys.insert(key); available_private_keys[key] = *privkey; } } + // removing excessive keys from available keys set auto minimal_signing_keys = tx.minimize_required_signatures( steem_chain_id, available_keys, @@ -808,9 +867,13 @@ namespace golos { namespace wallet { STEEMIT_MAX_SIG_CHECK_DEPTH ); + // checking if each private key exists and signing tx with it for( const public_key_type& k : minimal_signing_keys ) { auto it = available_private_keys.find(k); - FC_ASSERT( it != available_private_keys.end() ); + GOLOS_CHECK_LOGIC( it != available_private_keys.end(), + logic_errors::private_key_not_available, + "Private key for public key ${key} not available", + ("key",k)); tx.sign( it->second, steem_chain_id ); } @@ -862,16 +925,16 @@ namespace golos { namespace wallet { << std::right << std::setw(16) << fc::variant(total_sbd).as_string() <<"\n"; return out.str(); }; - m["get_account_history"] = []( variant result, const fc::variants& a ) { + auto acc_history_formatter = [](variant result, const fc::variants& a) { std::stringstream ss; - ss << std::left << std::setw( 5 ) << "#" << " "; - ss << std::left << std::setw( 10 ) << "BLOCK #" << " "; - ss << std::left << std::setw( 15 ) << "TRX ID" << " "; - ss << std::left << std::setw( 20 ) << "OPERATION" << " "; - ss << std::left << std::setw( 50 ) << "DETAILS" << "\n"; + ss << std::left << std::setw(5) << "#" << " "; + ss << std::left << std::setw(10) << "BLOCK #" << " "; + ss << std::left << std::setw(15) << "TRX ID" << " "; + ss << std::left << std::setw(20) << "OPERATION" << " "; + ss << std::left << std::setw(50) << "DETAILS" << "\n"; ss << "-------------------------------------------------------------------------------\n"; const auto& results = result.get_array(); - for( const auto& item : results ) { + for (const auto& item : results) { ss << std::left << std::setw(5) << item.get_array()[0].as_string() << " "; const auto& op = item.get_array()[1].get_object(); ss << std::left << std::setw(10) << op["block"].as_string() << " "; @@ -882,6 +945,9 @@ namespace golos { namespace wallet { } return ss.str(); }; + m["get_account_history"] = acc_history_formatter; + m["filter_account_history"] = acc_history_formatter; + /*m["get_open_orders"] = []( variant result, const fc::variants& a ) { auto orders = result.as>(); @@ -991,6 +1057,164 @@ namespace golos { namespace wallet { return it->second; } + message_body try_decrypt_private_message(const message_api_object& mo) const { + message_body result; + + fc::sha512 shared_secret; + + auto it = _keys.find(mo.from_memo_key); + auto pub_key = mo.to_memo_key; + if (it == _keys.end()) { + it = _keys.find(mo.to_memo_key); + pub_key = mo.from_memo_key; + } + if (it ==_keys.end()) { + wlog("unable to find keys"); + return result; + } + auto priv_key = wif_to_key(it->second); + if (!priv_key) { + wlog("empty private key"); + return result; + } + shared_secret = priv_key->get_shared_secret(pub_key); + + fc::sha512::encoder enc; + fc::raw::pack(enc, mo.nonce); + fc::raw::pack(enc, shared_secret); + auto encrypt_key = enc.result(); + + uint32_t check = fc::sha256::hash(encrypt_key)._hash[0]; + + if (mo.checksum != check) { + wlog("wrong checksum"); + return result; + } + + auto decrypt_data = fc::aes_decrypt(encrypt_key, mo.encrypted_message); + auto msg_json = std::string(decrypt_data.begin(), decrypt_data.end()); + try { + result = fc::json::from_string(msg_json).as(); + } catch (...) { + result.body = msg_json; + } + return result; + } + + std::vector decrypt_private_messages( + std::vector remote_result + ) const { + std::vector result; + result.reserve(remote_result.size()); + for (const auto& item : remote_result) { + result.emplace_back(item); + result.back().message = try_decrypt_private_message(item); + } + return result; + } + + annotated_signed_transaction send_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, const bool update, + const message_body& message, bool broadcast + ) { + WALLET_CHECK_UNLOCKED(); + + auto from_account = get_account(from); + auto to_account = get_account(to); + auto shared_secret = get_private_key(from_account.memo_key).get_shared_secret(to_account.memo_key); + + fc::sha512::encoder enc; + fc::raw::pack(enc, nonce); + fc::raw::pack(enc, shared_secret); + auto encrypt_key = enc.result(); + + auto msg_json = fc::json::to_string(message); + auto msg_data = std::vector(msg_json.begin(), msg_json.end()); + + private_message_operation op; + + op.from = from; + op.from_memo_key = from_account.memo_key; + op.to = to; + op.to_memo_key = to_account.memo_key; + op.nonce = nonce; + op.update = update; + op.encrypted_message = fc::aes_encrypt(encrypt_key, msg_data); + op.checksum = fc::sha256::hash(encrypt_key)._hash[0]; + + private_message_plugin_operation pop = op; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths.insert(from); + + signed_transaction trx; + trx.operations.push_back(jop); + trx.validate(); + + return sign_transaction(trx, broadcast); + } + + annotated_signed_transaction delete_private_message( + const std::string& requester, + const std::string& from, const std::string& to, const uint64_t nonce, bool broadcast + ) { + WALLET_CHECK_UNLOCKED(); + GOLOS_CHECK_PARAM(nonce, GOLOS_CHECK_VALUE(nonce != 0, "You should specify nonce of deleted message")); + + private_delete_message_operation op; + op.requester = requester; + op.from = from; + op.to = to; + op.nonce = nonce; + op.start_date = time_point_sec::min(); + op.stop_date = time_point_sec::min(); + + private_message_plugin_operation pop = op; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths.insert(requester); + + signed_transaction trx; + trx.operations.push_back(jop); + trx.validate(); + + return sign_transaction(trx, broadcast); + } + + annotated_signed_transaction delete_private_messages( + const std::string& requester, + const std::string& from, const std::string& to, + const std::string& start_date, const std::string& stop_date, + bool broadcast + ) { + WALLET_CHECK_UNLOCKED(); + + private_delete_message_operation op; + op.requester = requester; + op.from = from; + op.to = to; + op.nonce = 0; + op.start_date = time_converter(start_date, time_point::now(), time_point_sec::min()).time(); + op.stop_date = time_converter(stop_date, time_point::now(), time_point::now()).time(); + + private_message_plugin_operation pop = op; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths.insert(requester); + + signed_transaction trx; + trx.operations.push_back(jop); + trx.validate(); + + return sign_transaction(trx, broadcast); + } + string _wallet_filename; wallet_data _wallet; golos::protocol::chain_id_type steem_chain_id; @@ -1019,6 +1243,8 @@ namespace golos { namespace wallet { const string _wallet_filename_extension = ".wallet"; }; + + } } } // golos::wallet::detail @@ -1045,7 +1271,7 @@ namespace golos { namespace wallet { } vector< golos::api::account_api_object > wallet_api::list_my_accounts() { - FC_ASSERT( !is_locked(), "Wallet must be unlocked to list accounts" ); + WALLET_CHECK_UNLOCKED(); vector result; vector pub_keys; @@ -1123,7 +1349,7 @@ namespace golos { namespace wallet { bool wallet_api::import_key(string wif_key) { - FC_ASSERT(!is_locked()); + WALLET_CHECK_UNLOCKED(); // backup wallet fc::optional optional_private_key = wif_to_key(wif_key); if (!optional_private_key) @@ -1320,7 +1546,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st void wallet_api::lock() { try { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); encrypt_keys(); for( auto& key : my->_keys ) key.second = key_to_wif(fc::ecc::private_key()); @@ -1331,27 +1557,28 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st void wallet_api::unlock(string password) { try { - FC_ASSERT(password.size() > 0); + GOLOS_CHECK_PARAM(password, GOLOS_CHECK_VALUE(password.size() > 0, "Password should be non empty")); auto pw = fc::sha512::hash(password.c_str(), password.size()); vector decrypted = fc::aes_decrypt(pw, my->_wallet.cipher_keys); auto pk = fc::raw::unpack(decrypted); - FC_ASSERT(pk.checksum == pw); my->_keys = std::move(pk.keys); my->_checksum = pk.checksum; my->self.lock_changed(false); + } catch (const fc::aes_exception& e) { + GOLOS_CHECK_PARAM(password, FC_THROW_EXCEPTION(golos::invalid_value, "Invalid password")); } FC_CAPTURE_AND_RETHROW() } void wallet_api::set_password( string password ) { if( !is_new() ) - FC_ASSERT( !is_locked(), "The wallet must be unlocked before the password can be set" ); + WALLET_CHECK_UNLOCKED(); my->_checksum = fc::sha512::hash( password.c_str(), password.size() ); lock(); } vector wallet_api::list_keys(string account) { - FC_ASSERT(!is_locked()); + WALLET_CHECK_UNLOCKED(); vector all_keys; @@ -1361,7 +1588,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } else { accounts.push_back(get_account(account)); } - + for (auto it = accounts.begin(); it != accounts.end(); it++) { key_with_data memo_key_data; memo_key_data.public_key = std::string(it->memo_key); @@ -1371,7 +1598,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st all_keys.push_back(memo_key_data); auto acc_owner_keys = it->owner.get_keys(); - for (auto it2 = acc_owner_keys.begin(); it2 != acc_owner_keys.end(); it2++) { + for (auto it2 = acc_owner_keys.begin(); it2 != acc_owner_keys.end(); it2++) { key_with_data key_data; key_data.public_key = std::string(*it2); key_data.private_key = get_private_key(*it2); @@ -1381,7 +1608,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } auto acc_active_keys = it->active.get_keys(); - for (auto it2 = acc_active_keys.begin(); it2 != acc_active_keys.end(); it2++) { + for (auto it2 = acc_active_keys.begin(); it2 != acc_active_keys.end(); it2++) { key_with_data key_data; key_data.public_key = std::string(*it2); key_data.private_key = get_private_key(*it2); @@ -1391,7 +1618,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } auto acc_posting_keys = it->posting.get_keys(); - for (auto it2 = acc_posting_keys.begin(); it2 != acc_posting_keys.end(); it2++) { + for (auto it2 = acc_posting_keys.begin(); it2 != acc_posting_keys.end(); it2++) { key_with_data key_data; key_data.public_key = std::string(*it2); key_data.private_key = get_private_key(*it2); @@ -1411,7 +1638,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st pair wallet_api::get_private_key_from_password( string account, string role, string password )const { auto seed = account + role + password; - FC_ASSERT( seed.size() ); + GOLOS_CHECK_PARAM(account, GOLOS_CHECK_VALUE(seed.size(), "At least one of 'account', 'role', 'password' should be non empty")); auto secret = fc::sha256::hash( seed.c_str(), seed.size() ); auto priv = fc::ecc::private_key::regenerate( secret ); return std::make_pair( public_key_type( priv.get_public_key() ), key_to_wif( priv ) ); @@ -1447,7 +1674,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st bool broadcast ) const { try { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); account_create_operation op; op.creator = creator; op.new_account_name = new_account_name; @@ -1476,7 +1703,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st string json_meta, bool broadcast ) { try { - FC_ASSERT(!is_locked()); + WALLET_CHECK_UNLOCKED(); auto owner = suggest_brain_key(); auto active = suggest_brain_key(); auto posting = suggest_brain_key(); @@ -1509,7 +1736,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st bool broadcast ) const { try { - FC_ASSERT(!is_locked()); + WALLET_CHECK_UNLOCKED(); account_create_with_delegation_operation op; op.creator = creator; op.new_account_name = new_account_name; @@ -1536,7 +1763,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st */ annotated_signed_transaction wallet_api::request_account_recovery( string recovery_account, string account_to_recover, authority new_authority, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); request_account_recovery_operation op; op.recovery_account = recovery_account; op.account_to_recover = account_to_recover; @@ -1550,7 +1777,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } annotated_signed_transaction wallet_api::recover_account( string account_to_recover, authority recent_authority, authority new_authority, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); recover_account_operation op; op.account_to_recover = account_to_recover; @@ -1565,7 +1792,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } annotated_signed_transaction wallet_api::change_recovery_account( string owner, string new_recovery_account, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); change_recovery_account_operation op; op.account_to_recover = owner; @@ -1593,7 +1820,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st { try { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); account_update_operation op; op.account = account_name; @@ -1614,29 +1841,27 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::update_account_auth_key( string account_name, authority_type type, public_key_type key, weight_type weight, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); - auto accounts = my->_remote_database_api->get_accounts( { account_name } ); - FC_ASSERT( accounts.size() == 1, "Account does not exist" ); - FC_ASSERT( account_name == accounts[0].name, "Account name doesn't match?" ); + auto account = get_account(account_name); account_update_operation op; op.account = account_name; - op.memo_key = accounts[0].memo_key; - op.json_metadata = accounts[0].json_metadata; + op.memo_key = account.memo_key; + op.json_metadata = account.json_metadata; authority new_auth; switch( type ) { case( owner ): - new_auth = accounts[0].owner; + new_auth = account.owner; break; case( active ): - new_auth = accounts[0].active; + new_auth = account.active; break; case( posting ): - new_auth = accounts[0].posting; + new_auth = account.posting; break; } @@ -1650,9 +1875,8 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } if( new_auth.is_impossible() ) { - if ( type == owner ) { - FC_ASSERT( false, "Owner authority change would render account irrecoverable." ); - } + GOLOS_CHECK_LOGIC(type != owner, logic_errors::owner_authority_change_would_render_account_irrecoverable, + "Owner authority change would render account irrecoverable." ); wlog( "Authority is now impossible." ); } @@ -1678,29 +1902,27 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::update_account_auth_account( string account_name, authority_type type, string auth_account, weight_type weight, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); - auto accounts = my->_remote_database_api->get_accounts( { account_name } ); - FC_ASSERT( accounts.size() == 1, "Account does not exist" ); - FC_ASSERT( account_name == accounts[0].name, "Account name doesn't match?" ); + auto account = get_account(account_name); account_update_operation op; op.account = account_name; - op.memo_key = accounts[0].memo_key; - op.json_metadata = accounts[0].json_metadata; + op.memo_key = account.memo_key; + op.json_metadata = account.json_metadata; authority new_auth; switch( type ) { case( owner ): - new_auth = accounts[0].owner; + new_auth = account.owner; break; case( active ): - new_auth = accounts[0].active; + new_auth = account.active; break; case( posting ): - new_auth = accounts[0].posting; + new_auth = account.posting; break; } @@ -1715,10 +1937,8 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st if( new_auth.is_impossible() ) { - if ( type == owner ) - { - FC_ASSERT( false, "Owner authority change would render account irrecoverable." ); - } + GOLOS_CHECK_LOGIC(type != owner, logic_errors::owner_authority_change_would_render_account_irrecoverable, + "Owner authority change would render account irrecoverable." ); wlog( "Authority is now impossible." ); } @@ -1745,30 +1965,28 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::update_account_auth_threshold( string account_name, authority_type type, uint32_t threshold, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); - auto accounts = my->_remote_database_api->get_accounts( { account_name } ); - FC_ASSERT( accounts.size() == 1, "Account does not exist" ); - FC_ASSERT( account_name == accounts[0].name, "Account name doesn't match?" ); - FC_ASSERT( threshold != 0, "Authority is implicitly satisfied" ); + GOLOS_CHECK_PARAM(threshold, GOLOS_CHECK_VALUE(threshold != 0, "Authority is implicitly satisfied")); + auto account = get_account(account_name); account_update_operation op; op.account = account_name; - op.memo_key = accounts[0].memo_key; - op.json_metadata = accounts[0].json_metadata; + op.memo_key = account.memo_key; + op.json_metadata = account.json_metadata; authority new_auth; switch( type ) { case( owner ): - new_auth = accounts[0].owner; + new_auth = account.owner; break; case( active ): - new_auth = accounts[0].active; + new_auth = account.active; break; case( posting ): - new_auth = accounts[0].posting; + new_auth = account.posting; break; } @@ -1776,10 +1994,8 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st if( new_auth.is_impossible() ) { - if ( type == owner ) - { - FC_ASSERT( false, "Owner authority change would render account irrecoverable." ); - } + GOLOS_CHECK_LOGIC(type != owner, logic_errors::owner_authority_change_would_render_account_irrecoverable, + "Owner authority change would render account irrecoverable." ); wlog( "Authority is now impossible." ); } @@ -1805,10 +2021,9 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } annotated_signed_transaction wallet_api::update_account_meta(string account_name, string json_meta, bool broadcast) { - FC_ASSERT(!is_locked()); - auto accounts = my->_remote_database_api->get_accounts({account_name}); - FC_ASSERT(accounts.size() == 1, "Account does not exist"); - FC_ASSERT(account_name == accounts[0].name, "Account name doesn't match?"); + WALLET_CHECK_UNLOCKED(); + + auto account = get_account(account_name); signed_transaction tx; auto hf = my->_remote_database_api->get_hardfork_version(); @@ -1816,7 +2031,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st // TODO: remove this branch after HF 0.18 account_update_operation op; op.account = account_name; - op.memo_key = accounts[0].memo_key; + op.memo_key = account.memo_key; op.json_metadata = json_meta; tx.operations.push_back(op); } else { @@ -1831,16 +2046,14 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::update_account_memo_key( string account_name, public_key_type key, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); - auto accounts = my->_remote_database_api->get_accounts( { account_name } ); - FC_ASSERT( accounts.size() == 1, "Account does not exist" ); - FC_ASSERT( account_name == accounts[0].name, "Account name doesn't match?" ); + auto account = get_account(account_name); account_update_operation op; op.account = account_name; op.memo_key = key; - op.json_metadata = accounts[0].json_metadata; + op.json_metadata = account.json_metadata; signed_transaction tx; tx.operations.push_back(op); @@ -1850,11 +2063,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } annotated_signed_transaction wallet_api::delegate_vesting_shares(string delegator, string delegatee, asset vesting_shares, bool broadcast) { - FC_ASSERT(!is_locked()); - auto accounts = my->_remote_database_api->get_accounts({delegator, delegatee}); - FC_ASSERT(accounts.size() == 2, "One or more of the accounts specified do not exist."); - FC_ASSERT(delegator == accounts[0].name, "Delegator account is not right?"); - FC_ASSERT(delegatee == accounts[1].name, "Delegatee account is not right?"); + WALLET_CHECK_UNLOCKED(); delegate_vesting_shares_operation op; op.delegator = delegator; @@ -1876,7 +2085,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st string creator, string new_account_name, string json_meta, asset fee, bool broadcast ) { try { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); auto owner = suggest_brain_key(); auto active = suggest_brain_key(); auto posting = suggest_brain_key(); @@ -1908,7 +2117,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st optional props, bool broadcast ) { - FC_ASSERT(!is_locked()); + WALLET_CHECK_UNLOCKED(); const auto hf = my->_remote_database_api->get_hardfork_version(); const auto has_hf18 = hf >= hardfork_version(0, STEEMIT_HARDFORK_0_18__673); @@ -1950,7 +2159,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st const optional_chain_props& props, bool broadcast ) { - FC_ASSERT(!is_locked()); + WALLET_CHECK_UNLOCKED(); signed_transaction tx; chain_properties_update_operation op; @@ -1990,7 +2199,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::vote_for_witness(string voting_account, string witness_to_vote_for, bool approve, bool broadcast ) { try { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); account_witness_vote_operation op; op.account = voting_account; op.witness = witness_to_vote_for; @@ -2032,30 +2241,45 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st for( auto& key_weight_pair : account.owner.key_auths ) { for( auto& key : keys ) - FC_ASSERT( key_weight_pair.first != key, "Detected private owner key in memo field. Cancelling transaction." ); + GOLOS_CHECK_LOGIC(key_weight_pair.first != key, + logic_errors::detected_private_key_in_memo, + "Detected ${type} private key in memo field", + ("type","owner")); } for( auto& key_weight_pair : account.active.key_auths ) { for( auto& key : keys ) - FC_ASSERT( key_weight_pair.first != key, "Detected private active key in memo field. Cancelling transaction." ); + GOLOS_CHECK_LOGIC(key_weight_pair.first != key, + logic_errors::detected_private_key_in_memo, + "Detected ${type} private key in memo field", + ("type","active")); } for( auto& key_weight_pair : account.posting.key_auths ) { for( auto& key : keys ) - FC_ASSERT( key_weight_pair.first != key, "Detected private posting key in memo field. Cancelling transaction." ); + GOLOS_CHECK_LOGIC(key_weight_pair.first != key, + logic_errors::detected_private_key_in_memo, + "Detected ${type} private key in memo field", + ("type","posting")); } const auto& memo_key = account.memo_key; for( auto& key : keys ) - FC_ASSERT( memo_key != key, "Detected private memo key in memo field. Cancelling transaction." ); + GOLOS_CHECK_LOGIC(memo_key != key, + logic_errors::detected_private_key_in_memo, + "Detected ${type} private key in memo field", + ("type","memo")); // Check against imported keys for( auto& key_pair : my->_keys ) { for( auto& key : keys ) - FC_ASSERT( key != key_pair.first, "Detected imported private key in memo field. Cancelling trasanction." ); + GOLOS_CHECK_LOGIC(key_pair.first != key, + logic_errors::detected_private_key_in_memo, + "Detected ${type} private key in memo field", + ("type","imported")); } } @@ -2089,7 +2313,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::transfer(string from, string to, asset amount, string memo, bool broadcast) { try { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); check_memo( memo, get_account( from ) ); transfer_operation op; op.from = from; @@ -2119,7 +2343,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); escrow_transfer_operation op; op.from = from; op.to = to; @@ -2149,7 +2373,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); escrow_approve_operation op; op.from = from; op.to = to; @@ -2173,7 +2397,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); escrow_dispute_operation op; op.from = from; op.to = to; @@ -2200,7 +2424,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); escrow_release_operation op; op.from = from; op.to = to; @@ -2222,7 +2446,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st */ annotated_signed_transaction wallet_api::transfer_to_savings( string from, string to, asset amount, string memo, bool broadcast) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); check_memo( memo, get_account( from ) ); transfer_to_savings_operation op; op.from = from; @@ -2242,7 +2466,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st */ annotated_signed_transaction wallet_api::transfer_from_savings( string from, uint32_t request_id, string to, asset amount, string memo, bool broadcast) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); check_memo( memo, get_account( from ) ); transfer_from_savings_operation op; op.from = from; @@ -2264,7 +2488,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st */ annotated_signed_transaction wallet_api::cancel_transfer_from_savings( string from, uint32_t request_id, bool broadcast) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); cancel_transfer_from_savings_operation op; op.from = from; op.request_id = request_id; @@ -2277,7 +2501,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::transfer_to_vesting(string from, string to, asset amount, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); transfer_to_vesting_operation op; op.from = from; op.to = (to == from ? "" : to); @@ -2292,7 +2516,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::withdraw_vesting(string from, asset vesting_shares, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); withdraw_vesting_operation op; op.account = from; op.vesting_shares = vesting_shares; @@ -2306,7 +2530,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::set_withdraw_vesting_route( string from, string to, uint16_t percent, bool auto_vest, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); set_withdraw_vesting_route_operation op; op.from_account = from; op.to_account = to; @@ -2322,7 +2546,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::convert_sbd(string from, asset amount, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); convert_operation op; op.owner = from; op.requestid = fc::time_point::now().sec_since_epoch(); @@ -2337,7 +2561,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::publish_feed(string witness, price exchange_rate, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); feed_publish_operation op; op.publisher = witness; op.exchange_rate = exchange_rate; @@ -2387,7 +2611,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st annotated_signed_transaction wallet_api::decline_voting_rights( string account, bool decline, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); decline_voting_rights_operation op; op.account = account; op.decline = decline; @@ -2399,25 +2623,34 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st return my->sign_transaction( tx, broadcast ); } - map< uint32_t, golos::plugins::operation_history::applied_operation> wallet_api::get_account_history( string account, uint32_t from, uint32_t limit ) { - auto result = my->_remote_account_history->get_account_history( account, from, limit ); - if( !is_locked() ) { - for( auto& item : result ) { - if( item.second.op.which() == operation::tag::value ) { + history_operations wallet_api::get_account_history(string account, uint32_t from, uint32_t limit) { + auto result = my->_remote_account_history->get_account_history(account, from, limit, account_history_query()); + decrypt_history_memos(result); + return result; + } + history_operations wallet_api::filter_account_history(string account, uint32_t from, uint32_t limit, account_history_query q) { + auto result = my->_remote_account_history->get_account_history(account, from, limit, q); + decrypt_history_memos(result); + return result; + } + + void wallet_api::decrypt_history_memos(history_operations& result) { + if (!is_locked()) { + for (auto& item : result) { + if (item.second.op.which() == operation::tag::value) { auto& top = item.second.op.get(); - top.memo = decrypt_memo( top.memo ); + top.memo = decrypt_memo(top.memo); } - else if( item.second.op.which() == operation::tag::value ) { + else if (item.second.op.which() == operation::tag::value) { auto& top = item.second.op.get(); - top.memo = decrypt_memo( top.memo ); + top.memo = decrypt_memo(top.memo); } - else if( item.second.op.which() == operation::tag::value ) { + else if (item.second.op.which() == operation::tag::value) { auto& top = item.second.op.get(); - top.memo = decrypt_memo( top.memo ); + top.memo = decrypt_memo(top.memo); } } } - return result; } vector< database_api::withdraw_vesting_route_api_object > wallet_api::get_withdraw_routes( string account, database_api::withdraw_route_type type )const { @@ -2425,7 +2658,6 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } market_history::order_book wallet_api::get_order_book( uint32_t limit ) { - FC_ASSERT( limit <= 1000 ); return my->_remote_market_history->get_order_book( limit ); } @@ -2434,7 +2666,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } annotated_signed_transaction wallet_api::create_order(string owner, uint32_t order_id, asset amount_to_sell, asset min_to_receive, bool fill_or_kill, uint32_t expiration_sec, bool broadcast) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); limit_order_create_operation op; op.owner = owner; op.orderid = order_id; @@ -2451,7 +2683,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } annotated_signed_transaction wallet_api::cancel_order( string owner, uint32_t orderid, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); limit_order_cancel_operation op; op.owner = owner; op.orderid = orderid; @@ -2464,7 +2696,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } annotated_signed_transaction wallet_api::post_comment( string author, string permlink, string parent_author, string parent_permlink, string title, string body, string json, bool broadcast ) { - FC_ASSERT( !is_locked() ); + WALLET_CHECK_UNLOCKED(); comment_operation op; op.parent_author = parent_author; op.parent_permlink = parent_permlink; @@ -2482,8 +2714,8 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } annotated_signed_transaction wallet_api::vote( string voter, string author, string permlink, int16_t weight, bool broadcast ) { - FC_ASSERT( !is_locked() ); - FC_ASSERT( abs(weight) <= 100, "Weight must be between -100 and 100 and not 0" ); + WALLET_CHECK_UNLOCKED(); + GOLOS_CHECK_PARAM(weight, GOLOS_CHECK_VALUE(abs(weight) <= 100, "Weight must be between -100 and 100")); vote_operation op; op.voter = voter; @@ -2499,77 +2731,243 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } void wallet_api::set_transaction_expiration(uint32_t seconds) { - my->set_transaction_expiration(seconds); + GOLOS_CHECK_PARAM(seconds, + my->set_transaction_expiration(seconds)); } annotated_signed_transaction wallet_api::get_transaction( transaction_id_type id )const { return my->_remote_operation_history->get_transaction( id ); } - vector wallet_api::get_inbox(const std::string& to, time_point newest, uint16_t limit, std::uint64_t offset) { - FC_ASSERT( !is_locked() ); - vector result; - auto remote_result = my->_remote_private_message->get_inbox(to, newest, limit, offset); - for( const auto& item : remote_result ) { - result.emplace_back( item ); - message_body tmp = try_decrypt_message( item ); - result.back().message = std::move(tmp); + message_box_query get_message_box_query(const optional_private_box_query& query_template) { + message_box_query query; + query.newest_date = time_converter(query_template.newest_date, time_point::now(), time_point::now()).time(); + query.select_accounts = query_template.select_accounts; + query.filter_accounts = query_template.filter_accounts; + if (query_template.unread_only) { + query.unread_only = *query_template.unread_only; } - return result; + if (query_template.limit) { + query.limit = *query_template.limit; + } + if (query_template.offset) { + query.offset = *query_template.offset; + } + return query; } - vector wallet_api::get_outbox(const std::string& from, time_point newest, uint16_t limit, std::uint64_t offset) { - FC_ASSERT( !is_locked() ); - vector result; - auto remote_result = my->_remote_private_message->get_outbox(from, newest, limit, offset); - for( const auto& item : remote_result ) { - result.emplace_back( item ); - message_body tmp = try_decrypt_message( item ); + vector wallet_api::get_private_inbox( + const std::string& to, const optional_private_box_query& query + ) { + WALLET_CHECK_UNLOCKED(); + return my->decrypt_private_messages( + my->_remote_private_message->get_inbox( + to, get_message_box_query(query))); + } + + vector wallet_api::get_private_outbox( + const std::string& from, const optional_private_box_query& query + ) { + WALLET_CHECK_UNLOCKED(); + return my->decrypt_private_messages( + my->_remote_private_message->get_outbox( + from, get_message_box_query(query))); + } + + vector wallet_api::get_private_thread( + const std::string& from, const std::string& to, const optional_private_thread_query& query_template + ) { + WALLET_CHECK_UNLOCKED(); + std::vector result; + message_thread_query query; + query.newest_date = time_converter(query_template.newest_date, time_point::now(), time_point::now()).time(); + if (query_template.unread_only) { + query.unread_only = *query_template.unread_only; + } + if (query_template.limit) { + query.limit = *query_template.limit; + } + if (query_template.offset) { + query.offset = *query_template.offset; + } + auto remote_result = my->_remote_private_message->get_thread(from, to, query); + result.reserve(remote_result.size()); + for (const auto& item : remote_result) { + result.emplace_back(item); + message_body tmp = my->try_decrypt_private_message(item); result.back().message = std::move(tmp); } return result; } - message_body wallet_api::try_decrypt_message( const message_api_obj& mo ) { - message_body result; + annotated_signed_transaction wallet_api::set_private_settings( + const std::string& owner, const settings_api_object& s, bool broadcast + ) { + WALLET_CHECK_UNLOCKED(); + private_settings_operation op; - fc::sha512 shared_secret; + op.owner = owner; + op.ignore_messages_from_unknown_contact = s.ignore_messages_from_unknown_contact; - auto it = my->_keys.find(mo.from_memo_key); - if( it == my->_keys.end() ) - { - it = my->_keys.find(mo.to_memo_key); - if( it == my->_keys.end() ) - { - wlog( "unable to find keys" ); - return result; - } - auto priv_key = wif_to_key( it->second ); - if( !priv_key ) return result; - shared_secret = priv_key->get_shared_secret( mo.from_memo_key ); + private_message_plugin_operation pop = op; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths.insert(owner); + + signed_transaction trx; + trx.operations.push_back(jop); + trx.validate(); + + return my->sign_transaction(trx, broadcast); + } + + settings_api_object wallet_api::get_private_settings(const std::string& owner) { + return my->_remote_private_message->get_settings(owner); + } + + annotated_signed_transaction wallet_api::add_private_contact( + const std::string& owner, const std::string& contact, + private_contact_type type, fc::optional json_metadata, bool broadcast + ) { + WALLET_CHECK_UNLOCKED(); + + private_contact_operation op; + + op.owner = owner; + op.contact = contact; + op.type = type; + + if (type == golos::plugins::private_message::unknown) { + // op.json_metadata.clear(); + } else if (!json_metadata) { + op.json_metadata = my->_remote_private_message->get_contact_info(owner, contact).json_metadata; } else { - auto priv_key = wif_to_key( it->second ); - if( !priv_key ) return result; - shared_secret = priv_key->get_shared_secret( mo.to_memo_key ); + op.json_metadata = *json_metadata; } + private_message_plugin_operation pop = op; - fc::sha512::encoder enc; - fc::raw::pack( enc, mo.sent_time ); - fc::raw::pack( enc, shared_secret ); - auto encrypt_key = enc.result(); + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths.insert(owner); - uint32_t check = fc::sha256::hash( encrypt_key )._hash[0]; + signed_transaction trx; + trx.operations.push_back(jop); + trx.validate(); - if( mo.checksum != check ) - return result; + return my->sign_transaction(trx, broadcast); + } - auto decrypt_data = fc::aes_decrypt( encrypt_key, mo.encrypted_message ); - try { - return fc::raw::unpack( decrypt_data ); - } catch ( ... ) { - return result; - } + vector wallet_api::get_private_contacts( + const std::string& owner, private_contact_type type, uint16_t limit, uint32_t offset + ) { + return my->_remote_private_message->get_contacts(owner, type, limit, offset); + } + + contact_api_object wallet_api::get_private_contact( + const std::string& owner, const std::string& contact + ) { + return my->_remote_private_message->get_contact_info(owner, contact); + } + + annotated_signed_transaction wallet_api::edit_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, + const message_body& message, bool broadcast + ) { + return my->send_private_message(from, to, nonce, true, message, broadcast); + } + + annotated_signed_transaction wallet_api::send_private_message( + const std::string& from, const std::string& to, const message_body& message, bool broadcast + ) { + auto nonce = fc::time_point::now().time_since_epoch().count(); + return my->send_private_message(from, to, nonce, false, message, broadcast); + } + + annotated_signed_transaction wallet_api::delete_inbox_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, bool broadcast + ) { + return my->delete_private_message(to, from, to, nonce, broadcast); + } + + annotated_signed_transaction wallet_api::delete_inbox_private_messages( + const std::string& from, const std::string& to, + const std::string& start_date, const std::string& stop_date, + bool broadcast + ) { + return my->delete_private_messages(to, from, to, start_date, stop_date, broadcast); + } + + annotated_signed_transaction wallet_api::delete_outbox_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, bool broadcast + ) { + return my->delete_private_message(from, from, to, nonce, broadcast); + } + + annotated_signed_transaction wallet_api::delete_outbox_private_messages( + const std::string& from, const std::string& to, + const std::string& start_date, const std::string& stop_date, + bool broadcast + ) { + return my->delete_private_messages(from, from, to, start_date, stop_date, broadcast); + } + + annotated_signed_transaction wallet_api::mark_private_message( + const std::string& from, const std::string& to, const uint64_t nonce, bool broadcast + ) { + WALLET_CHECK_UNLOCKED(); + GOLOS_CHECK_PARAM(nonce, GOLOS_CHECK_VALUE(nonce != 0, "You should specify nonce of marked message")); + + private_mark_message_operation op; + op.from = from; + op.to = to; + op.nonce = nonce; + op.start_date = time_point_sec::min(); + op.stop_date = time_point_sec::min(); + + private_message_plugin_operation pop = op; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths.insert(to); + + signed_transaction trx; + trx.operations.push_back(jop); + trx.validate(); + + return my->sign_transaction(trx, broadcast); + } + + annotated_signed_transaction wallet_api::mark_private_messages( + const std::string& from, const std::string& to, + const std::string& start_date, const std::string& stop_date, + bool broadcast + ) { + WALLET_CHECK_UNLOCKED(); + + private_mark_message_operation op; + op.from = from; + op.to = to; + op.nonce = 0; + op.start_date = time_converter(start_date, time_point::now(), time_point_sec::min()).time(); + op.stop_date = time_converter(stop_date, time_point::now(), time_point::now()).time(); + + private_message_plugin_operation pop = op; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths.insert(to); + + signed_transaction trx; + trx.operations.push_back(jop); + trx.validate(); + + return my->sign_transaction(trx, broadcast); } annotated_signed_transaction wallet_api::follow( @@ -2577,17 +2975,17 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st const string& following, const set& what, const bool broadcast) { + + GOLOS_CHECK_PARAM(following, GOLOS_CHECK_VALUE(following.size() > 0, "Empty string is not allowed")); string _following = following; auto follwer_account = get_account( follower ); - FC_ASSERT( _following.size() ); if( _following[0] != '@' || _following[0] != '#' ) { _following = '@' + _following; } if( _following[0] == '@' ) { get_account( _following.substr(1) ); } - FC_ASSERT( _following.size() > 1 ); follow::follow_operation fop; fop.follower = follower; @@ -2607,5 +3005,12 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st return my->sign_transaction( trx, broadcast ); } - } } // steem::wallet +} } // golos::wallet +FC_REFLECT_ENUM(golos::wallet::logic_errors::types, + (detected_private_key_in_memo) + (owner_authority_change_would_render_account_irrecoverable) + (private_key_not_available) + (no_account_in_lut) + (malformed_private_key) +); diff --git a/plugins/account_history/include/golos/plugins/account_history/history_object.hpp b/plugins/account_history/include/golos/plugins/account_history/history_object.hpp index 19956a329f..96c8002f2f 100644 --- a/plugins/account_history/include/golos/plugins/account_history/history_object.hpp +++ b/plugins/account_history/include/golos/plugins/account_history/history_object.hpp @@ -3,13 +3,10 @@ #include #include #include - -#include - +#include #include #include - -#include +#include #include @@ -35,6 +32,19 @@ namespace golos { namespace plugins { namespace account_history { account_history_object_type = (ACCOUNT_HISTORY_SPACE_ID << 8) }; + enum operation_direction : uint8_t { + any = 0, + sender, + receiver, + dual, + }; + + struct account_history_query final { + fc::optional> select_ops; + fc::optional> filter_ops; + fc::optional direction; + }; + using namespace golos::chain; using namespace chainbase; @@ -43,19 +53,28 @@ namespace golos { namespace plugins { namespace account_history { class account_history_object final: public object { public: template - account_history_object(Constructor &&c, allocator a) { + account_history_object(Constructor&& c, allocator a) { c(*this); } id_type id; account_name_type account; + uint32_t block = 0; uint32_t sequence = 0; + uint8_t op_tag; + operation_direction dir; + operation_id_type op; }; +static_assert(protocol::operation::count() >= 0 && protocol::operation::count() <= 0xFF, + "There are more ops than u8 type can handle. Update op_tag type"); + using account_history_id_type = object_id; + struct by_operation; + struct by_location; struct by_account; using account_history_index = multi_index_container< account_history_object, @@ -63,7 +82,20 @@ namespace golos { namespace plugins { namespace account_history { ordered_unique< tag, member>, - ordered_unique, + ordered_non_unique< + tag, + member>, + ordered_unique< + tag, + composite_key, + member, + member, + member>, + composite_key_compare< + std::less, std::less, std::less, std::greater>>, + ordered_unique< + tag, composite_key, member>, @@ -72,7 +104,11 @@ namespace golos { namespace plugins { namespace account_history { } } } // golos::plugins::account_history +FC_REFLECT_ENUM(golos::plugins::account_history::operation_direction, (any)(sender)(receiver)(dual)) + +FC_REFLECT((golos::plugins::account_history::account_history_query), + (select_ops)(filter_ops)(direction)) + CHAINBASE_SET_INDEX_TYPE( golos::plugins::account_history::account_history_object, golos::plugins::account_history::account_history_index) - diff --git a/plugins/account_history/include/golos/plugins/account_history/plugin.hpp b/plugins/account_history/include/golos/plugins/account_history/plugin.hpp index 3b16a5d285..31e3bd6982 100644 --- a/plugins/account_history/include/golos/plugins/account_history/plugin.hpp +++ b/plugins/account_history/include/golos/plugins/account_history/plugin.hpp @@ -23,34 +23,26 @@ */ #pragma once -#include - #include -#include - #include - #include #include - #include #include - #include +#include + + namespace golos { namespace plugins { namespace account_history { using namespace chain; - using golos::plugins::operation_history::applied_operation; - - using get_account_history_return_type = std::map; - - using plugins::json_rpc::void_type; using plugins::json_rpc::msg_pack; - using plugins::json_rpc::msg_pack_transfer; - DEFINE_API_ARGS(get_account_history, msg_pack, get_account_history_return_type) + using history_operations = std::map; + + DEFINE_API_ARGS(get_account_history, msg_pack, history_operations) /** * This plugin is designed to track a range of operations by account so that one node @@ -81,11 +73,18 @@ namespace golos { namespace plugins { namespace account_history { DECLARE_API( /** - * Account operations have sequence numbers from 0 to N where N is the most recent operation. This method - * returns operations in the range [from-limit, from] + * Account operations have sequence numbers from 0 to N where N is the most recent operation. + * This method returns operations in the range [from-limit, from] * + * @param account - name of account, which history requested. * @param from - the absolute sequence number, -1 means most recent, limit is the number of operations before from. * @param limit - the maximum number of items that can be queried (0 to 1000], must be less than from + * @param query - filtering query - object with following optional fields: + * { + * select_ops - list of operations to include. special values: ALL, REAL, VIRTUAL. if skipped = ALL + * filter_ops - blacklist. if skipped = empty list (nothing blacklisted) + * dir - direction of operation in relation to account: any, sender, receiver, dual. Experimental + * } */ (get_account_history) ) diff --git a/plugins/account_history/plugin.cpp b/plugins/account_history/plugin.cpp index 4247b49f6a..c505912cbc 100644 --- a/plugins/account_history/plugin.cpp +++ b/plugins/account_history/plugin.cpp @@ -1,56 +1,71 @@ +#include +#include +#include #include #include - #include - -#include +#include #include -#define STEEM_NAMESPACE_PREFIX "golos::protocol::" +#include -#define CHECK_ARG_SIZE(s) \ - FC_ASSERT( args.args->size() == s, "Expected #s argument(s), was ${n}", ("n", args.args->size()) ); +#define ACCOUNT_HISTORY_MAX_LIMIT 10000 +#define ACCOUNT_HISTORY_DEFAULT_LIMIT 100 +#define GOLOS_OP_NAMESPACE "golos::protocol::" -namespace golos { namespace plugins { namespace account_history { - struct operation_visitor_filter; - void operation_get_impacted_accounts(const operation &op, flat_set &result); +namespace golos { namespace plugins { namespace account_history { using namespace golos::protocol; using namespace golos::chain; -// +namespace bpo = boost::program_options; +using impacted_accounts = fc::flat_map; + +struct operation_visitor_filter; +void operation_get_impacted_accounts(const operation& op, impacted_accounts& result); + + template T dejsonify(const string &s) { return fc::json::from_string(s).as(); } -#define DEFAULT_VALUE_VECTOR(value) default_value({fc::json::to_string(value)}, fc::json::to_string(value)) #define LOAD_VALUE_SET(options, name, container, type) \ -if( options.count(name) ) { \ +if (options.count(name)) { \ const std::vector& ops = options[name].as>(); \ std::transform(ops.begin(), ops.end(), std::inserter(container, container.end()), &dejsonify); \ } -// + + struct op_name_visitor { + using result_type = std::string; + template + std::string operator()(const T&) const { + return fc::get_typename::name(); + } + }; struct operation_visitor final { operation_visitor( golos::chain::database& db, const golos::chain::operation_notification& op_note, - std::string op_account) - : database(db), + std::string op_account, + operation_direction dir) + : db(db), note(op_note), - account(op_account){ + account(op_account), + dir(dir) { } using result_type = void; - golos::chain::database& database; + golos::chain::database& db; const golos::chain::operation_notification& note; std::string account; + operation_direction dir; template void operator()(Op &&) const { - const auto& idx = database.get_index().indices().get(); + const auto& idx = db.get_index().indices().get(); auto itr = idx.lower_bound(std::make_tuple(account, uint32_t(-1))); uint32_t sequence = 0; @@ -58,9 +73,12 @@ if( options.count(name) ) { \ sequence = itr->sequence + 1; } - database.create([&](account_history_object& history) { + db.create([&](account_history_object& history) { + history.block = note.block; history.account = account; history.sequence = sequence; + history.dir = dir; + history.op_tag = note.op.which(); history.op = operation_history::operation_id_type(note.db_id); }); } @@ -68,70 +86,181 @@ if( options.count(name) ) { \ struct plugin::plugin_impl final { public: - plugin_impl( ) - : database(appbase::app().get_plugin().db()) { + plugin_impl(): db(appbase::app().get_plugin().db()) { } ~plugin_impl() = default; + void erase_old_blocks() { + uint32_t head_block = db.head_block_num(); + if (history_blocks <= head_block) { + uint32_t need_block = head_block - history_blocks; + const auto& idx = db.get_index().indices().get(); + auto it = idx.begin(); + while (it != idx.end() && it->block <= need_block) { + auto next_it = it; + ++next_it; + db.remove(*it); + it = next_it; + } + } + } + void on_operation(const golos::chain::operation_notification& note) { if (!note.stored_in_db) { return; } - fc::flat_set impacted; + impacted_accounts impacted; operation_get_impacted_accounts(note.op, impacted); for (const auto& item : impacted) { - auto itr = tracked_accounts.lower_bound(item); + auto itr = tracked_accounts.lower_bound(item.first); if (!tracked_accounts.size() || - (itr != tracked_accounts.end() && itr->first <= item && item <= itr->second) + (itr != tracked_accounts.end() && itr->first <= item.first && item.first <= itr->second) ) { - note.op.visit(operation_visitor(database, note, item)); + note.op.visit(operation_visitor(db, note, item.first, item.second)); } } } - std::map get_account_history( - std::string account, - uint64_t from, - uint32_t limit - ) { - FC_ASSERT(limit <= 10000, "Limit of ${l} is greater than maxmimum allowed", ("l", limit)); - FC_ASSERT(from >= limit, "From must be greater than limit"); - // idump((account)(from)(limit)); - const auto& idx = database.get_index().indices().get(); + /////////////////////////////////////////////////////// + // API + history_operations fetch_unfiltered(string account, uint32_t from, uint32_t limit) { + history_operations result; + const auto& idx = db.get_index().indices().get(); auto itr = idx.lower_bound(std::make_tuple(account, from)); - // if( itr != idx.end() ) idump((*itr)); auto end = idx.upper_bound(std::make_tuple(account, std::max(int64_t(0), int64_t(itr->sequence) - limit))); - // if( end != idx.end() ) idump((*end)); - - std::map result; for (; itr != end; ++itr) { - result[itr->sequence] = database.get(itr->op); + result[itr->sequence] = db.get(itr->op); + } + return result; + } + + using op_tag_type = int; + using op_tags = fc::flat_set; + using op_names = fc::flat_set; + + op_tags op_names_to_tags(op_names names) { + op_tags result; + for (const auto& n: names) { + int from = 0, to = 0; + if (n == "ALL") { + to = operation::count(); + } else if (n == "REAL") { + to = virtual_op_tag; + } else if (n == "VIRTUAL") { + from = virtual_op_tag; + to = operation::count(); + } + if (to > 0) { + for (; from < to; from++) { + result.insert(from); + } + } else { + GOLOS_CHECK_VALUE(op_name2tag.count(n), "Unknown operation: ${o}", ("o",n)); + result.insert(op_name2tag[n]); + } + } + return result; + } + + struct sequenced_itr { + using op_idx_type = account_history_index::index::type; + using op_itr_type = account_history_index::index_const_iterator::type; + + op_itr_type itr; + + sequenced_itr(const op_idx_type& idx, account_name_type a, uint8_t o, operation_direction d, uint32_t s) + : itr(idx.lower_bound(std::make_tuple(a, o, d, s))) { + } + + // reconstruct to put next value into queue (can't reuse previous because const in queue) + sequenced_itr(op_itr_type itr): itr(itr) { + } + + bool operator<(const sequenced_itr& other) const { + return itr->sequence < other.itr->sequence; + } + }; + + history_operations get_account_history( + std::string account, + uint32_t from, + uint32_t limit, + account_history_query query + ) { + GOLOS_CHECK_LIMIT_PARAM(limit, ACCOUNT_HISTORY_MAX_LIMIT); + GOLOS_CHECK_PARAM(from, GOLOS_CHECK_VALUE(from >= limit, "From must be greater then limit")); + op_tags select_ops; + GOLOS_CHECK_PARAM(query, { + select_ops = op_names_to_tags(query.select_ops ? *query.select_ops : op_names({"ALL"})); + auto filter_ops = op_names_to_tags(query.filter_ops ? *query.filter_ops : op_names({})); + for (auto t: filter_ops) { + select_ops.erase(t); + } + GOLOS_CHECK_VALUE(!select_ops.empty(), "Query contains no operations to select"); + }); + auto dir = query.direction ? *query.direction : operation_direction::any; + + bool is_all_ops = select_ops.size() == operation::count(); + if (is_all_ops && dir == operation_direction::any) { + return fetch_unfiltered(account, from, limit); + } + std::priority_queue itrs; + const auto& idx = db.get_index().indices().get(); + const auto& end = idx.end(); + + auto put_itr = [&](op_tag_type o, operation_direction d, bool force = false) { + if (force || operation_direction::any == dir || d == dir) { + auto i = sequenced_itr(idx, account, uint8_t(o), d, from); + if (i.itr != end && i.itr->op_tag == o && i.itr->dir == d) + itrs.push(i); + } + }; + for (const auto o: select_ops) { + put_itr(o, operation_direction::sender); + put_itr(o, operation_direction::receiver); + put_itr(o, operation_direction::dual, dir == sender || dir == receiver); + } + + history_operations result; + while (!itrs.empty() && result.size() <= limit) { + auto itr = itrs.top().itr; + itrs.pop(); + result[itr->sequence] = db.get(itr->op); + auto o = itr->op_tag; + auto d = itr->dir; + auto next = sequenced_itr(++itr); + if (next.itr != end && next.itr->op_tag == o && next.itr->dir == d) + itrs.push(next); } return result; } + op_tag_type virtual_op_tag = -1; // all operations >= this value are virtual + fc::flat_map op_name2tag; fc::flat_map tracked_accounts; - golos::chain::database& database; + golos::chain::database& db; + uint32_t history_blocks = UINT32_MAX; }; DEFINE_API(plugin, get_account_history) { - CHECK_ARG_SIZE(3) - auto account = args.args->at(0).as(); - auto from = args.args->at(1).as(); - auto limit = args.args->at(2).as(); - - return pimpl->database.with_weak_read_lock([&]() { - return pimpl->get_account_history(account, from, limit); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, account) + (uint32_t, from, 0xFFFFFFFF) + (uint32_t, limit, ACCOUNT_HISTORY_DEFAULT_LIMIT) + (account_history_query, query, account_history_query()) + ); + return pimpl->db.with_weak_read_lock([&]() { + return pimpl->get_account_history(account, from, limit, query); }); } struct get_impacted_account_visitor final { - fc::flat_set& impacted; + impacted_accounts& impacted; - get_impacted_account_visitor(fc::flat_set& impact) + get_impacted_account_visitor(impacted_accounts& impact) : impacted(impact) { } @@ -139,254 +268,333 @@ if( options.count(name) ) { \ template void operator()(const T& op) { - op.get_required_posting_authorities(impacted); - op.get_required_active_authorities(impacted); - op.get_required_owner_authorities(impacted); + fc::flat_set impd; + op.get_required_posting_authorities(impd); + op.get_required_active_authorities(impd); + op.get_required_owner_authorities(impd); + for (auto i : impd) { + impacted.insert(make_pair(i, operation_direction::dual)); + } + } + + void insert_account(account_name_type a, operation_direction d) { + impacted.insert(make_pair(a, d)); + } + void insert_sender(account_name_type a) { + insert_account(a, operation_direction::sender); + } + void insert_receiver(account_name_type a) { + insert_account(a, operation_direction::receiver); + } + void insert_dual(account_name_type a) { + insert_account(a, operation_direction::dual); + } + + void insert_pair(account_name_type sender, account_name_type receiver, bool have_receiver = true) { + if (sender == receiver) { + insert_dual(sender); + } else { + insert_sender(sender); + if (have_receiver) + insert_receiver(receiver); + } } void operator()(const account_create_operation& op) { - impacted.insert(op.new_account_name); - impacted.insert(op.creator); + insert_pair(op.creator, op.new_account_name); } void operator()(const account_create_with_delegation_operation& op) { - impacted.insert(op.new_account_name); - impacted.insert(op.creator); + insert_pair(op.creator, op.new_account_name); } void operator()(const account_update_operation& op) { - impacted.insert(op.account); + insert_dual(op.account); } void operator()(const account_metadata_operation& op) { - impacted.insert(op.account); + insert_dual(op.account); } void operator()(const comment_operation& op) { - impacted.insert(op.author); - if (op.parent_author.size()) { - impacted.insert(op.parent_author); - } + insert_pair(op.author, op.parent_author, op.parent_author.size()); } void operator()(const delete_comment_operation& op) { - impacted.insert(op.author); + insert_dual(op.author); } void operator()(const vote_operation& op) { - impacted.insert(op.voter); - impacted.insert(op.author); + insert_pair(op.voter, op.author); } void operator()(const author_reward_operation& op) { - impacted.insert(op.author); + insert_receiver(op.author); } void operator()(const curation_reward_operation& op) { - impacted.insert(op.curator); + insert_receiver(op.curator); } void operator()(const liquidity_reward_operation& op) { - impacted.insert(op.owner); + insert_dual(op.owner); } void operator()(const interest_operation& op) { - impacted.insert(op.owner); + insert_dual(op.owner); } void operator()(const fill_convert_request_operation& op) { - impacted.insert(op.owner); + insert_dual(op.owner); } void operator()(const transfer_operation& op) { - impacted.insert(op.from); - impacted.insert(op.to); + insert_pair(op.from, op.to); } void operator()(const transfer_to_vesting_operation& op) { - impacted.insert(op.from); - - if (op.to != golos::chain::account_name_type() && op.to != op.from) { - impacted.insert(op.to); - } + auto have_to = op.to != account_name_type(); + insert_pair(op.from, have_to ? op.to: op.from); } void operator()(const withdraw_vesting_operation& op) { - impacted.insert(op.account); + insert_dual(op.account); } void operator()(const witness_update_operation& op) { - impacted.insert(op.owner); + insert_dual(op.owner); + } + + void operator()(const chain_properties_update_operation& op) { + insert_dual(op.owner); } void operator()(const account_witness_vote_operation& op) { - impacted.insert(op.account); - impacted.insert(op.witness); + insert_pair(op.account, op.witness); } void operator()(const account_witness_proxy_operation& op) { - impacted.insert(op.account); - impacted.insert(op.proxy); + insert_pair(op.account, op.proxy); } void operator()(const feed_publish_operation& op) { - impacted.insert(op.publisher); + insert_dual(op.publisher); } void operator()(const limit_order_create_operation& op) { - impacted.insert(op.owner); + insert_dual(op.owner); } void operator()(const fill_order_operation& op) { - impacted.insert(op.current_owner); - impacted.insert(op.open_owner); + insert_pair(op.current_owner, op.open_owner); } void operator()(const limit_order_cancel_operation& op) { - impacted.insert(op.owner); + insert_dual(op.owner); } void operator()(const pow_operation& op) { - impacted.insert(op.worker_account); + insert_dual(op.worker_account); } void operator()(const fill_vesting_withdraw_operation& op) { - impacted.insert(op.from_account); - impacted.insert(op.to_account); + insert_pair(op.from_account, op.to_account); } void operator()(const shutdown_witness_operation& op) { - impacted.insert(op.owner); + insert_dual(op.owner); } void operator()(const custom_operation& op) { for (auto& s: op.required_auths) { - impacted.insert(s); + insert_dual(s); } } void operator()(const request_account_recovery_operation& op) { - impacted.insert(op.account_to_recover); + insert_dual(op.account_to_recover); } void operator()(const recover_account_operation& op) { - impacted.insert(op.account_to_recover); + insert_dual(op.account_to_recover); } void operator()(const change_recovery_account_operation& op) { - impacted.insert(op.account_to_recover); + insert_pair(op.account_to_recover, op.new_recovery_account); + } + + template + void insert_from_to_agent_direction(const Op& op) { + insert_sender(op.from); + insert_receiver(op.to); + insert_receiver(op.agent); } void operator()(const escrow_transfer_operation& op) { - impacted.insert(op.from); - impacted.insert(op.to); - impacted.insert(op.agent); + insert_from_to_agent_direction(op); } + // note: the initiator (signer) of escrow_approve_operation is who. he can be either to or agent void operator()(const escrow_approve_operation& op) { - impacted.insert(op.from); - impacted.insert(op.to); - impacted.insert(op.agent); + insert_from_to_agent_direction(op); } + // note: the initiator (signer) of escrow_dispute_operation is who. he can be either from or to void operator()(const escrow_dispute_operation& op) { - impacted.insert(op.from); - impacted.insert(op.to); - impacted.insert(op.agent); + insert_from_to_agent_direction(op); } + // note: the initiator (signer) of escrow_release_operation is who. he can be either from or to or agent. + // the receiver receives funds, he can be either from or to void operator()(const escrow_release_operation& op) { - impacted.insert(op.from); - impacted.insert(op.to); - impacted.insert(op.agent); + insert_from_to_agent_direction(op); } void operator()(const transfer_to_savings_operation& op) { - impacted.insert(op.from); - impacted.insert(op.to); + insert_pair(op.from, op.to); } void operator()(const transfer_from_savings_operation& op) { - impacted.insert(op.from); - impacted.insert(op.to); + insert_pair(op.from, op.to); } void operator()(const cancel_transfer_from_savings_operation& op) { - impacted.insert(op.from); + insert_dual(op.from); } void operator()(const decline_voting_rights_operation& op) { - impacted.insert(op.account); + insert_dual(op.account); } void operator()(const comment_benefactor_reward_operation& op) { - impacted.insert(op.benefactor); - impacted.insert(op.author); + insert_pair(op.author, op.benefactor); + } + + void operator()(const producer_reward_operation& op) { + insert_receiver(op.producer); } void operator()(const delegate_vesting_shares_operation& op) { - impacted.insert(op.delegator); - impacted.insert(op.delegatee); + insert_pair(op.delegator, op.delegatee); } void operator()(const return_vesting_delegation_operation& op) { - impacted.insert(op.account); + insert_receiver(op.account); } + // todo: proposal tx signers are receivers void operator()(const proposal_create_operation& op) { - impacted.insert(op.author); + insert_dual(op.author); } void operator()(const proposal_update_operation& op) { - impacted.insert(op.active_approvals_to_add.begin(), op.active_approvals_to_add.end()); - impacted.insert(op.owner_approvals_to_add.begin(), op.owner_approvals_to_add.end()); - impacted.insert(op.posting_approvals_to_add.begin(), op.posting_approvals_to_add.end()); - impacted.insert(op.active_approvals_to_remove.begin(), op.active_approvals_to_remove.end()); - impacted.insert(op.owner_approvals_to_remove.begin(), op.owner_approvals_to_remove.end()); - impacted.insert(op.posting_approvals_to_remove.begin(), op.posting_approvals_to_remove.end()); + insert_receiver(op.author); + auto insert_set = [this](const fc::flat_set& impd) { + for (auto i : impd) { + insert_dual(i); + } + }; + insert_set(op.active_approvals_to_add); + insert_set(op.owner_approvals_to_add); + insert_set(op.posting_approvals_to_add); + insert_set(op.active_approvals_to_remove); + insert_set(op.owner_approvals_to_remove); + insert_set(op.posting_approvals_to_remove); } void operator()(const proposal_delete_operation& op) { - impacted.insert(op.requester); + insert_pair(op.requester, op.author); } - //void operator()( const operation& op ){} }; - void operation_get_impacted_accounts( - const operation& op, fc::flat_set& result - ) { + void operation_get_impacted_accounts(const operation& op, impacted_accounts& result) { get_impacted_account_visitor vtor = get_impacted_account_visitor(result); op.visit(vtor); } - void plugin::set_program_options( - boost::program_options::options_description& cli, - boost::program_options::options_description& cfg - ) { + void plugin::set_program_options(bpo::options_description& cli, bpo::options_description& cfg) { cli.add_options()( "track-account-range", - boost::program_options::value>()->composing()->multitoken(), + bpo::value>()->composing()->multitoken(), "Defines a range of accounts to track as a json pair [\"from\",\"to\"] [from,to]. " "Can be specified multiple times" + ) + ( + "track-account", + bpo::value>()->composing(), + "Defines a individual account to track (in addition to ranges). " + "Can be specified multiple times" ); cfg.add(cli); } - void plugin::plugin_initialize(const boost::program_options::variables_map& options) { + void plugin::plugin_initialize(const bpo::variables_map& options) { ilog("account_history plugin: plugin_initialize() begin"); pimpl = std::make_unique(); + + if (options.count("history-blocks")) { + uint32_t history_blocks = options.at("history-blocks").as(); + pimpl->history_blocks = history_blocks; + pimpl->db.applied_block.connect([&](const signed_block& block){ + pimpl->erase_old_blocks(); + }); + } else { + pimpl->history_blocks = UINT32_MAX; + } + ilog("account_history: history-blocks ${s}", ("s", pimpl->history_blocks)); + // this is worked, because the appbase initialize required plugins at first - pimpl->database.pre_apply_operation.connect([&](golos::chain::operation_notification& note){ + pimpl->db.pre_apply_operation.connect([&](operation_notification& note) { pimpl->on_operation(note); }); - golos::chain::add_plugin_index(pimpl->database); + add_plugin_index(pimpl->db); using pairstring = std::pair; - LOAD_VALUE_SET(options, "track-account-range", pimpl->tracked_accounts, pairstring); - + fc::flat_map ranges; + LOAD_VALUE_SET(options, "track-account-range", ranges, pairstring); + + if (options.count("track-account") > 0) { + auto accounts = options.at("track-account").as>(); + for (auto& a : accounts) { + std::vector names; + boost::split(names, a, boost::is_any_of(" \t,")); + for (auto& n : names) { + if (!n.empty()) + ranges[n] = n; // construct "range" with 1 account name in it + } + } + } + // exclude embedded ranges (fix #701) + const auto end = ranges.end(); + for (auto i = ranges.begin(); i < end; ++i) { + bool bad = false; + for (auto j = ranges.begin(); !bad && j < end && j->first <= i->second; ++j) { + if (j == i) continue; + bad = i->first >= j->first && i->second <= j->second; + } + if (!bad) + pimpl->tracked_accounts[i->first] = i->second; + } ilog("account_history: tracked_accounts ${s}", ("s", pimpl->tracked_accounts)); + // prepare map to convert operation name to operation tag + pimpl->op_name2tag = {}; + op_name_visitor nvisit; + operation op; + auto count = operation::count(); + for (auto i = 0; i < count; i++) { + op.set_which(i); + auto name = op.visit(nvisit); + name = name.substr(sizeof(GOLOS_OP_NAMESPACE) - 1); // cut "golos::protocol::" + pimpl->op_name2tag[name] = i; + name = name.substr(0, name.size() + 1 - sizeof("_operation")); // support names without "_operation" too + pimpl->op_name2tag[name] = i; + if (pimpl->virtual_op_tag == -1 && is_virtual_operation(op)) { + pimpl->virtual_op_tag = i; + } + } + JSON_RPC_REGISTER_API(name()); ilog("account_history plugin: plugin_initialize() end"); } diff --git a/plugins/auth_util/plugin.cpp b/plugins/auth_util/plugin.cpp index c7c165c716..74b531ed49 100644 --- a/plugins/auth_util/plugin.cpp +++ b/plugins/auth_util/plugin.cpp @@ -37,7 +37,7 @@ struct plugin::plugin_impl { std::vector result; const golos::chain::account_authority_object &acct = - db.get(account_name); + db.get_authority(account_name); protocol::authority auth; if ((level == "posting") || (level == "p")) { auth = protocol::authority(acct.posting); @@ -57,7 +57,7 @@ struct plugin::plugin_impl { flat_set avail; protocol::sign_state ss(signing_keys, [&db](const std::string &account_name) -> const protocol::authority { - return protocol::authority(db.get(account_name).active); + return protocol::authority(db.get_authority(account_name).active); }, avail); bool has_authority = ss.check_authority(auth); diff --git a/plugins/block_info/plugin.cpp b/plugins/block_info/plugin.cpp index 52bc5b2ea6..5d2e898652 100644 --- a/plugins/block_info/plugin.cpp +++ b/plugins/block_info/plugin.cpp @@ -3,8 +3,11 @@ #include #include +#include +#include #include #include +#include namespace golos { namespace plugins { @@ -43,8 +46,8 @@ struct plugin::plugin_impl { std::vector plugin::plugin_impl::get_block_info(uint32_t start_block_num, uint32_t count) { std::vector result; - FC_ASSERT(start_block_num > 0); - FC_ASSERT(count <= 10000); + GOLOS_CHECK_PARAM(start_block_num, GOLOS_CHECK_VALUE_GT(start_block_num, 0)); + GOLOS_CHECK_LIMIT_PARAM(count, 10000); uint32_t n = std::min(uint32_t(block_info_.size()), start_block_num + count); @@ -61,8 +64,8 @@ std::vector plugin::plugin_impl::get_blocks_with_info( std::vector result; const auto & db = database(); - FC_ASSERT(start_block_num > 0); - FC_ASSERT(count <= 10000); + GOLOS_CHECK_PARAM(start_block_num, GOLOS_CHECK_VALUE_GT(start_block_num, 0)); + GOLOS_CHECK_LIMIT_PARAM(count, 10000); uint32_t n = std::min( uint32_t( block_info_.size() ), start_block_num + count ); uint64_t total_size = 0; @@ -104,8 +107,10 @@ void plugin::plugin_impl::on_applied_block(const protocol::signed_block &b) { } DEFINE_API ( plugin, get_block_info ) { - auto start_block_num = args.args->at(0).as(); - auto count = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, start_block_num) + (uint32_t, count) + ); auto &db = my->database(); return db.with_weak_read_lock([&]() { return my->get_block_info(start_block_num, count); @@ -113,8 +118,10 @@ DEFINE_API ( plugin, get_block_info ) { } DEFINE_API ( plugin, get_blocks_with_info ) { - auto start_block_num = args.args->at(0).as(); - auto count = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, start_block_num) + (uint32_t, count) + ); auto &db = my->database(); return db.with_weak_read_lock([&]() { return my->get_blocks_with_info(start_block_num, count); diff --git a/plugins/chain/include/golos/plugins/chain/plugin.hpp b/plugins/chain/include/golos/plugins/chain/plugin.hpp index 967c66dfe4..357c645957 100644 --- a/plugins/chain/include/golos/plugins/chain/plugin.hpp +++ b/plugins/chain/include/golos/plugins/chain/plugin.hpp @@ -2,21 +2,19 @@ #include -#include #include -#include #include - -#include +#include #include -// for api -#include -namespace golos { - namespace plugins { - namespace chain { +#include + + +namespace golos { namespace chain { +struct database_fixture; +} } // golos::chain - using golos::plugins::json_rpc::msg_pack; +namespace golos { namespace plugins { namespace chain { class plugin final : public appbase::plugin { public: @@ -26,9 +24,9 @@ namespace golos { ~plugin(); - constexpr const static char *plugin_name = "chain"; + constexpr const static char* plugin_name = "chain"; - static const std::string &name() { + static const std::string& name() { static std::string name = plugin_name; return name; } @@ -90,10 +88,10 @@ namespace golos { boost::signals2::signal on_sync; private: - class plugin_impl; + class impl; + std::unique_ptr my; - std::unique_ptr my; + friend struct golos::chain::database_fixture; // need to set skip_startup field + bool skip_startup = false; }; - } - } -} // golos::plugins::chain +} } } // golos::plugins::chain diff --git a/plugins/chain/plugin.cpp b/plugins/chain/plugin.cpp index 35abd8c294..a4501e4bdd 100644 --- a/plugins/chain/plugin.cpp +++ b/plugins/chain/plugin.cpp @@ -1,28 +1,26 @@ -#include -#include #include +#include +#include +#include +#include #include #include #include -#include -#include #include -namespace golos { -namespace plugins { -namespace chain { +namespace golos { namespace plugins { namespace chain { namespace bfs = boost::filesystem; + namespace bpo = boost::program_options; using fc::flat_map; using protocol::block_id_type; - class plugin::plugin_impl { + class plugin::impl final { public: - uint64_t shared_memory_size = 0; - boost::filesystem::path shared_memory_dir; + bfs::path shared_memory_dir; bool replay = false; bool replay_if_corrupted = true; bool force_replay = false; @@ -31,7 +29,7 @@ namespace chain { bool check_locks = false; bool validate_invariants = false; uint32_t flush_interval = 0; - flat_map loaded_checkpoints; + flat_map loaded_checkpoints; uint32_t allow_future_time = 5; @@ -45,6 +43,7 @@ namespace chain { size_t min_free_shared_memory_size; uint32_t clear_votes_before_block = 0; + uint32_t clear_votes_older_n_blocks = 0xFFFFFFFF; bool enable_plugins_on_push_transaction; uint32_t block_num_check_free_size = 0; @@ -55,7 +54,11 @@ namespace chain { bool single_write_thread = false; - plugin_impl() { + golos::chain::database::store_metadata_modes store_account_metadata; + std::vector accounts_to_store_metadata; + bool store_memo_in_savings_withdraws = true; + + impl() { // get default settings read_wait_micro = db.read_wait_micro(); max_read_wait_retries = db.max_read_wait_retries(); @@ -65,39 +68,50 @@ namespace chain { } // HELPERS - golos::chain::database &database() { - return db; - } - boost::asio::io_service& io_service() { return appbase::app().get_io_service(); } - constexpr const static char *plugin_name = "chain_api"; - static const std::string &name() { - static std::string name = plugin_name; - return name; - } + void check_time_in_block(const protocol::signed_block& block); + bool accept_block(const protocol::signed_block& block, bool currently_syncing, uint32_t skip); + void accept_transaction(const protocol::signed_transaction& trx); + void wipe_db(const bfs::path& data_dir, bool wipe_block_log); + void replay_db(const bfs::path& data_dir, bool force_replay); - void check_time_in_block(const protocol::signed_block &block); - bool accept_block(const protocol::signed_block &block, bool currently_syncing, uint32_t skip); - void accept_transaction(const protocol::signed_transaction &trx); - void wipe_db(const bfs::path &data_dir, bool wipe_block_log); - void replay_db(const bfs::path &data_dir, bool force_replay); + void on_block (const protocol::signed_block& b); }; - void plugin::plugin_impl::check_time_in_block(const protocol::signed_block &block) { + + void plugin::impl::on_block(const protocol::signed_block& b) { + auto n = b.block_num(); + bool del_any = n < clear_votes_before_block; + const auto now = db.head_block_time(); // don't make one constant from now and ttl because of overflow + const auto ttl = uint64_t(clear_votes_older_n_blocks) * STEEMIT_BLOCK_INTERVAL; + + const auto& idx = db.get_index().indices().get(); + auto itr = idx.begin(); + while (itr != idx.end() && itr->num_changes == -1 && (del_any || now - itr->last_update > fc::seconds(ttl))) { + const auto& vote = *itr; + ++itr; + db.remove(vote); + } + } + + void plugin::impl::check_time_in_block(const protocol::signed_block& block) { time_point_sec now = fc::time_point::now(); uint64_t max_accept_time = now.sec_since_epoch(); max_accept_time += allow_future_time; - FC_ASSERT(block.timestamp.sec_since_epoch() <= max_accept_time); + GOLOS_CHECK_VALUE(block.timestamp.sec_since_epoch() <= max_accept_time, + "Block timestamp is too far in the future", + ("timestamp", block.timestamp.sec_since_epoch()) + ("max_accept_time", max_accept_time)); } - bool plugin::plugin_impl::accept_block(const protocol::signed_block &block, bool currently_syncing, uint32_t skip) { + bool plugin::impl::accept_block(const protocol::signed_block& block, bool currently_syncing, uint32_t skip) { if (currently_syncing && block.block_num() % 10000 == 0) { ilog("Syncing Blockchain --- Got block: #${n} time: ${t} producer: ${p}", - ("t", block.timestamp)("n", block.block_num())("p", block.witness)); + ("t", block.timestamp)("n", block.block_num())("p", block.witness)); } check_time_in_block(block); @@ -111,7 +125,7 @@ namespace chain { io_service().post([&]{ try { promise.set_value(db.push_block(block, skip)); - } catch(...) { + } catch (...) { promise.set_exception(std::current_exception()); } }); @@ -121,7 +135,7 @@ namespace chain { } } - void plugin::plugin_impl::wipe_db(const bfs::path &data_dir, bool wipe_block_log) { + void plugin::impl::wipe_db(const bfs::path& data_dir, bool wipe_block_log) { if (wipe_block_log) { ilog("Wiping blockchain with block log."); } else { @@ -129,10 +143,10 @@ namespace chain { } db.wipe(data_dir, shared_memory_dir, wipe_block_log); - db.open(data_dir, shared_memory_dir, STEEMIT_INIT_SUPPLY, shared_memory_size, chainbase::database::read_write/*, validate_invariants*/ ); + db.open(data_dir, shared_memory_dir, STEEMIT_INIT_SUPPLY, shared_memory_size, chainbase::database::read_write/*, validate_invariants*/); }; - void plugin::plugin_impl::replay_db(const bfs::path &data_dir, bool force_replay) { + void plugin::impl::replay_db(const bfs::path& data_dir, bool force_replay) { auto head_block_log = db.get_block_log().head(); force_replay |= head_block_log && db.revision() >= head_block_log->block_num(); @@ -146,7 +160,7 @@ namespace chain { db.reindex(data_dir, shared_memory_dir, from_block_num, shared_memory_size); }; - void plugin::plugin_impl::accept_transaction(const protocol::signed_transaction &trx) { + void plugin::impl::accept_transaction(const protocol::signed_transaction& trx) { uint32_t skip = db.validate_transaction(trx, db.skip_apply_transaction); if (single_write_thread) { @@ -157,7 +171,7 @@ namespace chain { try { db.push_transaction(trx, skip); promise.set_value(true); - } catch(...) { + } catch (...) { promise.set_exception(std::current_exception()); } }); @@ -173,90 +187,107 @@ namespace chain { plugin::~plugin() { } - golos::chain::database &plugin::db() { + golos::chain::database& plugin::db() { return my->db; } - const golos::chain::database &plugin::db() const { + const golos::chain::database& plugin::db() const { return my->db; } - void plugin::set_program_options(boost::program_options::options_description &cli, - boost::program_options::options_description &cfg) { + void plugin::set_program_options(bpo::options_description& cli, bpo::options_description& cfg) { cfg.add_options() ( - "shared-file-dir", boost::program_options::value()->default_value("blockchain"), + "shared-file-dir", bpo::value()->default_value("blockchain"), "the location of the chain shared memory files (absolute path or relative to application data dir)" ) ( - "shared-file-size", boost::program_options::value()->default_value("2G"), + "shared-file-size", bpo::value()->default_value("2G"), "Start size of the shared memory file. Default: 2G" ) ( - "inc-shared-file-size", boost::program_options::value()->default_value("2G"), + "inc-shared-file-size", bpo::value()->default_value("2G"), "Increasing size on reaching limit of free space in shared memory file (see min-free-shared-file-size). Default: 2G" ) ( - "min-free-shared-file-size", boost::program_options::value()->default_value("500M"), + "min-free-shared-file-size", bpo::value()->default_value("500M"), "Minimum free space in shared memory file (see inc-shared-file-size). Default: 500M" ) ( - "block-num-check-free-size", boost::program_options::value()->default_value(1000), + "block-num-check-free-size", bpo::value()->default_value(1000), "Check free space in shared memory each N blocks. Default: 1000 (each 3000 seconds)." ) ( - "checkpoint", boost::program_options::value>()->composing(), + "checkpoint", bpo::value>()->composing(), "Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints." ) ( - "flush-state-interval", boost::program_options::value(), + "flush-state-interval", bpo::value(), "flush shared memory changes to disk every N blocks" ) ( - "read-wait-micro", boost::program_options::value(), + "read-wait-micro", bpo::value(), "maximum microseconds for trying to get read lock" ) ( - "max-read-wait-retries", boost::program_options::value(), + "max-read-wait-retries", bpo::value(), "maximum number of retries to get read lock" ) ( - "write-wait-micro", boost::program_options::value(), + "write-wait-micro", bpo::value(), "maximum microseconds for trying to get write lock" ) ( - "max-write-wait-retries", boost::program_options::value(), + "max-write-wait-retries", bpo::value(), "maximum number of retries to get write lock" ) ( - "single-write-thread", boost::program_options::value()->default_value(false), + "single-write-thread", bpo::value()->default_value(false), "push blocks and transactions from one thread" ) ( - "clear-votes-before-block", boost::program_options::value()->default_value(0), + "clear-votes-before-block", bpo::value()->default_value(0), "remove votes before defined block, should speedup initial synchronization" ) ( - "skip-virtual-ops", boost::program_options::value()->default_value(false), + "clear-votes-older-n-blocks", bpo::value()->default_value(0xFFFFFFFF), + "if set, remove votes older than specified number of blocks. " + "-1 = do not remove; 0 = remove after cashout; any other value N - remove votes older than N blocks. " + "note: votes don't removed before post cashout" + ) ( + "skip-virtual-ops", bpo::value()->default_value(false), "virtual operations will not be passed to the plugins, helps to save some memory" ) ( - "enable-plugins-on-push-transaction", boost::program_options::value()->default_value(true), + "enable-plugins-on-push-transaction", bpo::value()->default_value(true), "enable calling of plugins for operations on push_transaction" ) ( - "replay-if-corrupted", boost::program_options::bool_switch()->default_value(true), + "replay-if-corrupted", bpo::bool_switch()->default_value(true), "replay all blocks if shared memory is corrupted" + ) ( + "store-account-metadata", bpo::value(), + "store account metadata for all accounts if true, for no one if else, otherwise for specified in store-account-metadata-list" + ) ( + "store-account-metadata-list", bpo::value(), + "names of accounts to store metadata" + ) ( + "store-memo-in-savings-withdraws", bpo::value()->default_value(true), + "store memo for all savings withdraws" ); + // Do not use bool_switch() in cfg! cli.add_options() ( - "replay-blockchain", boost::program_options::bool_switch()->default_value(false), + "replay-blockchain", bpo::bool_switch()->default_value(false), "clear chain database and replay all blocks" ) ( - "force-replay-blockchain", boost::program_options::bool_switch()->default_value(false), + "force-replay-blockchain", bpo::bool_switch()->default_value(false), "force clear chain database and replay all blocks" ) ( - "resync-blockchain", boost::program_options::bool_switch()->default_value(false), + "resync-blockchain", bpo::bool_switch()->default_value(false), "clear chain database and block log" ) ( - "check-locks", boost::program_options::bool_switch()->default_value(false), + "check-locks", bpo::bool_switch()->default_value(false), "Check correctness of chainbase locking" ) ( - "validate-database-invariants", boost::program_options::bool_switch()->default_value(false), + "validate-database-invariants", bpo::bool_switch()->default_value(false), "Validate all supply invariants check out" ); } - void plugin::plugin_initialize(const boost::program_options::variables_map &options) { + void plugin::plugin_initialize(const bpo::variables_map& options) { + my.reset(new impl()); - my.reset(new plugin_impl()); + my->db.applied_block.connect([&](const protocol::signed_block& b) { + my->on_block(b); + }); - auto sfd = options.at("shared-file-dir").as(); + auto sfd = options.at("shared-file-dir").as(); if (sfd.is_relative()) { my->shared_memory_dir = appbase::app().data_dir() / sfd; } else { @@ -287,6 +318,7 @@ namespace chain { my->inc_shared_memory_size = fc::parse_size(options.at("inc-shared-file-size").as()); my->min_free_shared_memory_size = fc::parse_size(options.at("min-free-shared-file-size").as()); my->clear_votes_before_block = options.at("clear-votes-before-block").as(); + my->clear_votes_older_n_blocks = options.at("clear-votes-older-n-blocks").as(); my->skip_virtual_ops = options.at("skip-virtual-ops").as(); if (options.count("block-num-check-free-size")) { @@ -308,14 +340,35 @@ namespace chain { if (options.count("checkpoint")) { auto cps = options.at("checkpoint").as>(); my->loaded_checkpoints.reserve(cps.size()); - for (const auto &cp : cps) { + for (const auto& cp : cps) { auto item = fc::json::from_string(cp).as>(); my->loaded_checkpoints[item.first] = item.second; } } + + my->store_account_metadata = golos::chain::database::store_metadata_for_all; + + if (options.count("store-account-metadata-list")) { + my->store_account_metadata = golos::chain::database::store_metadata_for_listed; + std::string str_accs = options["store-account-metadata-list"].as(); + my->accounts_to_store_metadata = fc::json::from_string(str_accs).as>(); + } + + if (options.count("store-account-metadata")) { + if (!options.at("store-account-metadata").as()) { + my->store_account_metadata = golos::chain::database::store_metadata_for_nobody; + wlog( + "Account metadata will be not stored for any item of store-account-metadata-list" + " because store-account-metadata is false"); + } + } + + my->store_memo_in_savings_withdraws = options.at("store-memo-in-savings-withdraws").as(); } void plugin::plugin_startup() { + if (skip_startup) return; + ilog("Starting chain with shared_file_size: ${n} bytes", ("n", my->shared_memory_size)); auto data_dir = appbase::app().data_dir() / "blockchain"; @@ -337,9 +390,14 @@ namespace chain { my->db.set_inc_shared_memory_size(my->inc_shared_memory_size); my->db.set_min_free_shared_memory_size(my->min_free_shared_memory_size); - my->db.set_clear_votes(my->clear_votes_before_block); - if(my->skip_virtual_ops) { + my->db.set_store_account_metadata(my->store_account_metadata); + + my->db.set_accounts_to_store_metadata(my->accounts_to_store_metadata); + + my->db.set_store_memo_in_savings_withdraws(my->store_memo_in_savings_withdraws); + + if (my->skip_virtual_ops) { my->db.set_skip_virtual_ops(); } @@ -351,20 +409,20 @@ namespace chain { try { ilog("Opening shared memory from ${path}", ("path", my->shared_memory_dir.generic_string())); - my->db.open(data_dir, my->shared_memory_dir, STEEMIT_INIT_SUPPLY, my->shared_memory_size, chainbase::database::read_write/*, my->validate_invariants*/ ); + my->db.open(data_dir, my->shared_memory_dir, STEEMIT_INIT_SUPPLY, my->shared_memory_size, chainbase::database::read_write/*, my->validate_invariants*/); auto head_block_log = my->db.get_block_log().head(); my->replay |= head_block_log && my->db.revision() != head_block_log->block_num(); if (my->replay) { my->replay_db(data_dir, my->force_replay); } - } catch (const golos::chain::database_revision_exception &) { + } catch (const golos::chain::database_revision_exception&) { if (my->replay_if_corrupted) { wlog("Error opening database, attempting to replay blockchain."); my->force_replay |= my->db.revision() >= my->db.head_block_num(); try { my->replay_db(data_dir, my->force_replay); - } catch (const golos::chain::block_log_exception &) { + } catch (const golos::chain::block_log_exception&) { wlog("Error opening block log. Having to resync from network..."); my->wipe_db(data_dir, true); } @@ -378,7 +436,7 @@ namespace chain { wlog("Error opening database, attempting to replay blockchain."); try { my->replay_db(data_dir, true); - } catch (const golos::chain::block_log_exception &) { + } catch (const golos::chain::block_log_exception&) { wlog("Error opening block log. Having to resync from network..."); my->wipe_db(data_dir, true); } @@ -399,15 +457,15 @@ namespace chain { ilog("database closed successfully"); } - bool plugin::accept_block(const protocol::signed_block &block, bool currently_syncing, uint32_t skip) { + bool plugin::accept_block(const protocol::signed_block& block, bool currently_syncing, uint32_t skip) { return my->accept_block(block, currently_syncing, skip); } - void plugin::accept_transaction(const protocol::signed_transaction &trx) { + void plugin::accept_transaction(const protocol::signed_transaction& trx) { my->accept_transaction(trx); } - bool plugin::block_is_on_preferred_chain(const protocol::block_id_type &block_id) { + bool plugin::block_is_on_preferred_chain(const protocol::block_id_type& block_id) { // If it's not known, it's not preferred. if (!db().is_known_block(block_id)) { return false; @@ -418,10 +476,8 @@ namespace chain { return db().get_block_id_for_num(protocol::block_header::num_from_id(block_id)) == block_id; } - void plugin::check_time_in_block(const protocol::signed_block &block) { + void plugin::check_time_in_block(const protocol::signed_block& block) { my->check_time_in_block(block); } -} -} -} // namespace steem::plugis::chain::chain_apis +} } } // golos::plugins::chain diff --git a/plugins/database_api/api.cpp b/plugins/database_api/api.cpp index 6fa3ea69e0..63d8b60683 100755 --- a/plugins/database_api/api.cpp +++ b/plugins/database_api/api.cpp @@ -1,45 +1,40 @@ #include - +#include +#include #include - #include +#include +#include -#include #include #include #include #include -#include - -#define GET_REQUIRED_FEES_MAX_RECURSION 4 - -#define CHECK_ARG_SIZE(s) \ - FC_ASSERT( args.args->size() == s, "Expected #s argument(s), was ${n}", ("n", args.args->size()) ); -#define CHECK_ARGS_COUNT(min, max) \ - FC_ASSERT(n_args >= min && n_args <= max, "Expected #min-#max arguments, got ${n}", ("n", n_args)); namespace golos { namespace plugins { namespace database_api { -struct block_applied_callback_info { - using ptr = std::shared_ptr; + +template +struct callback_info { + using callback_t = std::function; + using ptr = std::shared_ptr; using cont = std::list; - block_applied_callback callback; + callback_t callback; boost::signals2::connection connection; - cont::iterator it; + typename cont::iterator it; void connect( - boost::signals2::signal &sig, - cont &free_cont, - block_applied_callback cb + boost::signals2::signal& sig, + cont& free_cont, + callback_t cb ) { callback = cb; - - connection = sig.connect([this, &free_cont](const signed_block &block) { + connection = sig.connect([this, &free_cont](arg item) { try { - this->callback(fc::variant(block)); + this->callback(item); } catch (...) { free_cont.push_back(*this->it); this->connection.disconnect(); @@ -48,6 +43,52 @@ struct block_applied_callback_info { } }; +using block_applied_callback_info = callback_info; +using block_applied_callback = block_applied_callback_info::callback_t; +using pending_tx_callback_info = callback_info; +using pending_tx_callback = pending_tx_callback_info::callback_t; + + +// block_operation used in block_applied_callback to represent virtual operations. +// default operation type have no position info (trx, op_in_trx) +struct block_operation { + block_operation(const operation_notification& o) : + trx_in_block(o.trx_in_block), + op_in_trx(o.op_in_trx), + virtual_op(o.virtual_op), + op(o.op) {}; + + uint32_t trx_in_block = 0; + uint16_t op_in_trx = 0; + uint32_t virtual_op = 0; + operation op; +}; + +using block_operations = std::vector; + +struct block_with_vops : public signed_block { + block_with_vops(signed_block b, block_operations ops): signed_block(b), _virtual_operations(ops) { + }; + + // name field starting with _ coz it's not directly related to block + block_operations _virtual_operations; +}; + +struct virtual_operations { + virtual_operations(uint32_t block_num, block_operations ops): block_num(block_num), operations(ops) { + }; + + uint32_t block_num; + block_operations operations; +}; + +enum block_applied_callback_result_type { + block = 0, // send signed blocks + header = 1, // send only block headers + virtual_ops = 2, // send only virtual operations + full = 3 // send signed block + virtual operations +}; + struct plugin::api_impl final { public: @@ -58,11 +99,10 @@ struct plugin::api_impl final { } // Subscriptions - void set_subscribe_callback(std::function cb, bool clear_filter); - void set_pending_transaction_callback(std::function cb); void set_block_applied_callback(block_applied_callback cb); - void clear_block_applied_callback(); - void cancel_all_subscriptions(); + void set_pending_tx_callback(pending_tx_callback cb); + void clear_outdated_callbacks(bool clear_blocks); + void op_applied_callback(const operation_notification& o); // Blocks and transactions optional get_block_header(uint32_t block_num) const; @@ -88,99 +128,27 @@ struct plugin::api_impl final { std::vector get_withdraw_routes(std::string account, withdraw_route_type type) const; std::vector get_proposed_transactions(const std::string&, uint32_t, uint32_t) const; - template - void subscribe_to_item(const T &i) const { - auto vec = fc::raw::pack(i); - if (!_subscribe_callback) { - return; - } - - if (!is_subscribed_to_item(i)) { - idump((i)); - _subscribe_filter.insert(vec.data(), vec.size()); - } - } - - template - bool is_subscribed_to_item(const T &i) const { - if (!_subscribe_callback) { - return false; - } - - return _subscribe_filter.contains(i); - } - - mutable fc::bloom_filter _subscribe_filter; - std::function _subscribe_callback; - std::function _pending_trx_callback; - - - golos::chain::database &database() const { + golos::chain::database& database() const { return _db; } - - std::map, std::function> _market_subscriptions; - + // Callbacks block_applied_callback_info::cont active_block_applied_callback; block_applied_callback_info::cont free_block_applied_callback; + pending_tx_callback_info::cont active_pending_tx_callback; + pending_tx_callback_info::cont free_pending_tx_callback; -private: - - golos::chain::database &_db; -}; - - -//void find_accounts(std::set &accounts, const discussion &d) { -// accounts.insert(d.author); -//} - -////////////////////////////////////////////////////////////////////// -// // -// Subscriptions // -// // -////////////////////////////////////////////////////////////////////// - -void plugin::set_subscribe_callback(std::function cb, bool clear_filter) { - my->database().with_weak_read_lock([&]() { - my->set_subscribe_callback(cb, clear_filter); - }); -} - -void plugin::api_impl::set_subscribe_callback( - std::function cb, - bool clear_filter -) { - _subscribe_callback = cb; - if (clear_filter || !cb) { - static fc::bloom_parameters param; - param.projected_element_count = 10000; - param.false_positive_probability = 1.0 / 10000; - param.maximum_size = 1024 * 8 * 8 * 2; - param.compute_optimal_parameters(); - _subscribe_filter = fc::bloom_filter(param); + block_operations& get_block_vops() { + return _block_virtual_ops; } -} -void plugin::set_pending_transaction_callback(std::function cb) { - my->database().with_weak_read_lock([&]() { - my->set_pending_transaction_callback(cb); - }); -} - -void plugin::api_impl::set_pending_transaction_callback(std::function cb) { - _pending_trx_callback = cb; -} +private: + golos::chain::database& _db; -void plugin::cancel_all_subscriptions() { - my->database().with_weak_read_lock([&]() { - my->cancel_all_subscriptions(); - }); -} + uint32_t _block_virtual_ops_block_num = 0; + block_operations _block_virtual_ops; +}; -void plugin::api_impl::cancel_all_subscriptions() { - set_subscribe_callback(std::function(), true); -} ////////////////////////////////////////////////////////////////////// // // @@ -194,8 +162,7 @@ plugin::plugin() { plugin::~plugin() { } -plugin::api_impl::api_impl() : _db(appbase::app().get_plugin().db()) -{ +plugin::api_impl::api_impl() : _db(appbase::app().get_plugin().db()) { wlog("creating database plugin ${x}", ("x", int64_t(this))); } @@ -210,9 +177,11 @@ plugin::api_impl::~api_impl() { ////////////////////////////////////////////////////////////////////// DEFINE_API(plugin, get_block_header) { - CHECK_ARG_SIZE(1) + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, block_num) + ); return my->database().with_weak_read_lock([&]() { - return my->get_block_header(args.args->at(0).as()); + return my->get_block_header(block_num); }); } @@ -225,9 +194,11 @@ optional plugin::api_impl::get_block_header(uint32_t block_num) co } DEFINE_API(plugin, get_block) { - CHECK_ARG_SIZE(1) + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, block_num) + ); return my->database().with_weak_read_lock([&]() { - return my->get_block(args.args->at(0).as()); + return my->get_block(block_num); }); } @@ -235,15 +206,49 @@ optional plugin::api_impl::get_block(uint32_t block_num) const { return database().fetch_block_by_number(block_num); } +////////////////////////////////////////////////////////////////////// +// // +// Subscriptions // +// // +////////////////////////////////////////////////////////////////////// + DEFINE_API(plugin, set_block_applied_callback) { - CHECK_ARG_SIZE(1) + auto n_args = args.args->size(); + GOLOS_ASSERT(n_args == 1, golos::invalid_arguments_count, "Expected 1 parameter, received ${n}", ("n", n_args)("required",1)); + + // Use default value in case of converting errors to preserve + // previous HF behaviour, where 1st argument can be any integer + block_applied_callback_result_type type = block; + auto arg = args.args->at(0); + try { + type = arg.as(); + } catch (...) { + ilog("Bad argument (${a}) passed to set_block_applied_callback, using default", ("a",arg)); + } // Delegate connection handlers to callback msg_pack_transfer transfer(args); my->database().with_weak_read_lock([&]{ - my->set_block_applied_callback([msg = transfer.msg()](const fc::variant & block_header) { - msg->unsafe_result(fc::variant(block_header)); + my->set_block_applied_callback([this,type,msg = transfer.msg()](const signed_block& block) { + fc::variant r; + switch (type) { + case block_applied_callback_result_type::block: + r = fc::variant(block); + break; + case header: + r = fc::variant(block_header(block)); + break; + case virtual_ops: + r = fc::variant(virtual_operations(block.block_num(), my->get_block_vops())); + break; + case full: + r = fc::variant(block_with_vops(block, my->get_block_vops())); + break; + default: + break; + } + msg->unsafe_result(r); }); }); @@ -252,24 +257,54 @@ DEFINE_API(plugin, set_block_applied_callback) { return {}; } -void plugin::api_impl::set_block_applied_callback(std::function callback) { - auto info_ptr = std::make_shared(); +DEFINE_API(plugin, set_pending_transaction_callback) { + // Delegate connection handlers to callback + msg_pack_transfer transfer(args); + my->database().with_weak_read_lock([&]{ + my->set_pending_tx_callback([this,msg = transfer.msg()](const signed_transaction& tx) { + msg->unsafe_result(fc::variant(tx)); + }); + }); + transfer.complete(); + return {}; +} +void plugin::api_impl::set_block_applied_callback(block_applied_callback callback) { + auto info_ptr = std::make_shared(); active_block_applied_callback.push_back(info_ptr); info_ptr->it = std::prev(active_block_applied_callback.end()); - info_ptr->connect(database().applied_block, free_block_applied_callback, callback); } -void plugin::api_impl::clear_block_applied_callback() { - for (auto &info: free_block_applied_callback) { - active_block_applied_callback.erase(info->it); +void plugin::api_impl::set_pending_tx_callback(pending_tx_callback callback) { + auto info_ptr = std::make_shared(); + active_pending_tx_callback.push_back(info_ptr); + info_ptr->it = std::prev(active_pending_tx_callback.end()); + info_ptr->connect(database().on_pending_transaction, free_pending_tx_callback, callback); +} + +void plugin::api_impl::clear_outdated_callbacks(bool clear_blocks) { + auto clear_bad = [&](auto& free_list, auto& active_list) { + for (auto& info: free_list) { + active_list.erase(info->it); + } + free_list.clear(); + }; + if (clear_blocks) { + clear_bad(free_block_applied_callback, active_block_applied_callback); + } else { + clear_bad(free_pending_tx_callback, active_pending_tx_callback); } - free_block_applied_callback.clear(); } -void plugin::clear_block_applied_callback() { - my->clear_block_applied_callback(); +void plugin::api_impl::op_applied_callback(const operation_notification& o) { + if (o.block != _block_virtual_ops_block_num) { + _block_virtual_ops.clear(); + _block_virtual_ops_block_num = o.block; + } + if (is_virtual_operation(o.op)) { + _block_virtual_ops.push_back(o); + } } ////////////////////////////////////////////////////////////////////// @@ -279,6 +314,7 @@ void plugin::clear_block_applied_callback() { ////////////////////////////////////////////////////////////////////// DEFINE_API(plugin, get_config) { + PLUGIN_API_VALIDATE_ARGS(); return my->database().with_weak_read_lock([&]() { return my->get_config(); }); @@ -289,12 +325,14 @@ fc::variant_object plugin::api_impl::get_config() const { } DEFINE_API(plugin, get_dynamic_global_properties) { + PLUGIN_API_VALIDATE_ARGS(); return my->database().with_weak_read_lock([&]() { return my->get_dynamic_global_properties(); }); } DEFINE_API(plugin, get_chain_properties) { + PLUGIN_API_VALIDATE_ARGS(); return my->database().with_weak_read_lock([&]() { return chain_api_properties(my->database().get_witness_schedule_object().median_props, my->database()); }); @@ -305,12 +343,14 @@ dynamic_global_property_api_object plugin::api_impl::get_dynamic_global_properti } DEFINE_API(plugin, get_hardfork_version) { + PLUGIN_API_VALIDATE_ARGS(); return my->database().with_weak_read_lock([&]() { return my->database().get(hardfork_property_object::id_type()).current_hardfork_version; }); } DEFINE_API(plugin, get_next_scheduled_hardfork) { + PLUGIN_API_VALIDATE_ARGS(); return my->database().with_weak_read_lock([&]() { scheduled_hardfork shf; const auto &hpo = my->database().get(hardfork_property_object::id_type()); @@ -327,9 +367,11 @@ DEFINE_API(plugin, get_next_scheduled_hardfork) { ////////////////////////////////////////////////////////////////////// DEFINE_API(plugin, get_accounts) { - CHECK_ARG_SIZE(1) + PLUGIN_API_VALIDATE_ARGS( + (vector, account_names) + ); return my->database().with_weak_read_lock([&]() { - return my->get_accounts(args.args->at(0).as >()); + return my->get_accounts(account_names); }); } @@ -355,9 +397,11 @@ std::vector plugin::api_impl::get_accounts(std::vector, account_names) + ); return my->database().with_weak_read_lock([&]() { - return my->lookup_account_names(args.args->at(0).as >()); + return my->lookup_account_names(account_names); }); } @@ -381,9 +425,10 @@ std::vector> plugin::api_impl::lookup_account_names } DEFINE_API(plugin, lookup_accounts) { - CHECK_ARG_SIZE(2) - account_name_type lower_bound_name = args.args->at(0).as(); - uint32_t limit = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, lower_bound_name) + (uint32_t, limit) + ); return my->database().with_weak_read_lock([&]() { return my->lookup_accounts(lower_bound_name, limit); }); @@ -393,7 +438,7 @@ std::set plugin::api_impl::lookup_accounts( const std::string &lower_bound_name, uint32_t limit ) const { - FC_ASSERT(limit <= 1000); + GOLOS_CHECK_LIMIT_PARAM(limit, 1000); const auto &accounts_by_name = database().get_index().indices().get(); std::set result; @@ -416,8 +461,9 @@ uint64_t plugin::api_impl::get_account_count() const { } DEFINE_API(plugin, get_owner_history) { - CHECK_ARG_SIZE(1) - auto account = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + ); return my->database().with_weak_read_lock([&]() { std::vector results; const auto &hist_idx = my->database().get_index().indices().get< @@ -434,8 +480,9 @@ DEFINE_API(plugin, get_owner_history) { } DEFINE_API(plugin, get_recovery_request) { - CHECK_ARG_SIZE(1) - auto account = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, account) + ); return my->database().with_weak_read_lock([&]() { optional result; @@ -452,9 +499,10 @@ DEFINE_API(plugin, get_recovery_request) { } DEFINE_API(plugin, get_escrow) { - CHECK_ARG_SIZE(2) - auto from = args.args->at(0).as(); - auto escrow_id = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, from) + (uint32_t, escrow_id) + ); return my->database().with_weak_read_lock([&]() { optional result; @@ -515,19 +563,20 @@ std::vector plugin::api_impl::get_withdraw_routes( DEFINE_API(plugin, get_withdraw_routes) { - FC_ASSERT(args.args->size() == 1 || args.args->size() == 2, "Expected 1-2 arguments, was ${n}", - ("n", args.args->size())); - auto account = args.args->at(0).as(); - auto type = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + (withdraw_route_type, type, incoming) + ); return my->database().with_weak_read_lock([&]() { return my->get_withdraw_routes(account, type); }); } DEFINE_API(plugin, get_account_bandwidth) { - CHECK_ARG_SIZE(2) - auto account = args.args->at(0).as(); - auto type = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + (bandwidth_type, type) + ); optional result; auto band = my->database().find( boost::make_tuple(account, type)); @@ -546,8 +595,9 @@ DEFINE_API(plugin, get_account_bandwidth) { ////////////////////////////////////////////////////////////////////// DEFINE_API(plugin, get_transaction_hex) { - CHECK_ARG_SIZE(1) - auto trx = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (signed_transaction, trx) + ); return my->database().with_weak_read_lock([&]() { return my->get_transaction_hex(trx); }); @@ -558,9 +608,10 @@ std::string plugin::api_impl::get_transaction_hex(const signed_transaction &trx) } DEFINE_API(plugin, get_required_signatures) { - CHECK_ARG_SIZE(2) - auto trx = args.args->at(0).as(); - auto available_keys = args.args->at(1).as>(); + PLUGIN_API_VALIDATE_ARGS( + (signed_transaction, trx) + (flat_set, available_keys) + ); return my->database().with_weak_read_lock([&]() { return my->get_required_signatures(trx, available_keys); }); @@ -574,13 +625,13 @@ std::set plugin::api_impl::get_required_signatures( auto result = trx.get_required_signatures( STEEMIT_CHAIN_ID, available_keys, [&](std::string account_name) { - return authority(database().get(account_name).active); + return authority(database().get_authority(account_name).active); }, [&](std::string account_name) { - return authority(database().get(account_name).owner); + return authority(database().get_authority(account_name).owner); }, [&](std::string account_name) { - return authority(database().get(account_name).posting); + return authority(database().get_authority(account_name).posting); }, STEEMIT_MAX_SIG_CHECK_DEPTH ); @@ -589,9 +640,11 @@ std::set plugin::api_impl::get_required_signatures( } DEFINE_API(plugin, get_potential_signatures) { - CHECK_ARG_SIZE(1) + PLUGIN_API_VALIDATE_ARGS( + (signed_transaction, trx) + ); return my->database().with_weak_read_lock([&]() { - return my->get_potential_signatures(args.args->at(0).as()); + return my->get_potential_signatures(trx); }); } @@ -600,21 +653,21 @@ std::set plugin::api_impl::get_potential_signatures(const signe std::set result; trx.get_required_signatures(STEEMIT_CHAIN_ID, flat_set(), [&](account_name_type account_name) { - const auto &auth = database().get(account_name).active; + const auto &auth = database().get_authority(account_name).active; for (const auto &k : auth.get_keys()) { result.insert(k); } return authority(auth); }, [&](account_name_type account_name) { - const auto &auth = database().get(account_name).owner; + const auto &auth = database().get_authority(account_name).owner; for (const auto &k : auth.get_keys()) { result.insert(k); } return authority(auth); }, [&](account_name_type account_name) { - const auto &auth = database().get(account_name).posting; + const auto &auth = database().get_authority(account_name).posting; for (const auto &k : auth.get_keys()) { result.insert(k); } @@ -628,28 +681,32 @@ std::set plugin::api_impl::get_potential_signatures(const signe } DEFINE_API(plugin, verify_authority) { - CHECK_ARG_SIZE(1) + PLUGIN_API_VALIDATE_ARGS( + (signed_transaction, trx) + ); return my->database().with_weak_read_lock([&]() { - return my->verify_authority(args.args->at(0).as()); + return my->verify_authority(trx); }); } bool plugin::api_impl::verify_authority(const signed_transaction &trx) const { trx.verify_authority(STEEMIT_CHAIN_ID, [&](std::string account_name) { - return authority(database().get(account_name).active); + return authority(database().get_authority(account_name).active); }, [&](std::string account_name) { - return authority(database().get(account_name).owner); + return authority(database().get_authority(account_name).owner); }, [&](std::string account_name) { - return authority(database().get(account_name).posting); + return authority(database().get_authority(account_name).posting); }, STEEMIT_MAX_SIG_CHECK_DEPTH); return true; } DEFINE_API(plugin, verify_account_authority) { - CHECK_ARG_SIZE(2) + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, name) + (flat_set, keys) + ); return my->database().with_weak_read_lock([&]() { - return my->verify_account_authority(args.args->at(0).as(), - args.args->at(1).as >()); + return my->verify_account_authority(name, keys); }); } @@ -657,22 +714,22 @@ bool plugin::api_impl::verify_account_authority( const std::string &name, const flat_set &keys ) const { - FC_ASSERT(name.size() > 0); - auto account = database().find(name); - FC_ASSERT(account, "no such account"); + GOLOS_CHECK_PARAM(name, GOLOS_CHECK_VALUE(name.size() > 0, "Account must be not empty")); + auto account = database().get_account(name); /// reuse trx.verify_authority by creating a dummy transfer signed_transaction trx; transfer_operation op; - op.from = account->name; + op.from = account.name; trx.operations.emplace_back(op); return verify_authority(trx); } DEFINE_API(plugin, get_conversion_requests) { - CHECK_ARG_SIZE(1) - auto account = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + ); return my->database().with_weak_read_lock([&]() { const auto &idx = my->database().get_index().indices().get(); std::vector result; @@ -687,8 +744,9 @@ DEFINE_API(plugin, get_conversion_requests) { DEFINE_API(plugin, get_savings_withdraw_from) { - CHECK_ARG_SIZE(1) - auto account = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + ); return my->database().with_weak_read_lock([&]() { std::vector result; @@ -703,8 +761,9 @@ DEFINE_API(plugin, get_savings_withdraw_from) { } DEFINE_API(plugin, get_savings_withdraw_to) { - CHECK_ARG_SIZE(1) - auto account = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + ); return my->database().with_weak_read_lock([&]() { std::vector result; @@ -720,14 +779,14 @@ DEFINE_API(plugin, get_savings_withdraw_to) { //vector get_vesting_delegations(string account, string from, uint32_t limit, delegations_type type = delegated) const; DEFINE_API(plugin, get_vesting_delegations) { - size_t n_args = args.args->size(); - CHECK_ARGS_COUNT(2, 4); - auto account = args.args->at(0).as(); - auto from = args.args->at(1).as(); - auto limit = n_args >= 3 ? args.args->at(2).as() : 100; - auto type = n_args >= 4 ? args.args->at(3).as() : delegated; + PLUGIN_API_VALIDATE_ARGS( + (string, account) + (string, from) + (uint32_t, limit, 100) + (delegations_type, type, delegated) + ); bool sent = type == delegated; - FC_ASSERT(limit <= 1000); + GOLOS_CHECK_LIMIT_PARAM(limit, 1000); vector result; result.reserve(limit); @@ -749,12 +808,12 @@ DEFINE_API(plugin, get_vesting_delegations) { //vector get_expiring_vesting_delegations(string account, time_point_sec from, uint32_t limit = 100) const; DEFINE_API(plugin, get_expiring_vesting_delegations) { - size_t n_args = args.args->size(); - CHECK_ARGS_COUNT(2, 3); - auto account = args.args->at(0).as(); - auto from = args.args->at(1).as(); - uint32_t limit = n_args >= 3 ? args.args->at(2).as() : 100; - FC_ASSERT(limit <= 1000); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + (time_point_sec, from) + (uint32_t, limit, 100) + ); + GOLOS_CHECK_LIMIT_PARAM(limit, 1000); return my->database().with_weak_read_lock([&]() { vector result; @@ -770,8 +829,7 @@ DEFINE_API(plugin, get_expiring_vesting_delegations) { } DEFINE_API(plugin, get_database_info) { - CHECK_ARG_SIZE(0); - + PLUGIN_API_VALIDATE_ARGS(); // read lock doesn't seem needed... database_info info; @@ -832,23 +890,31 @@ std::vector plugin::api_impl::get_proposed_transactions( } DEFINE_API(plugin, get_proposed_transactions) { - CHECK_ARG_SIZE(3); - auto account = args.args->at(0).as(); - auto from = args.args->at(1).as(); - auto limit = args.args->at(2).as(); - FC_ASSERT(limit <= 100); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + (uint32_t, from) + (uint32_t, limit) + ); + GOLOS_CHECK_LIMIT_PARAM(limit, 100); return my->database().with_weak_read_lock([&]() { return my->get_proposed_transactions(account, from, limit); }); } -void plugin::plugin_initialize(const boost::program_options::variables_map &options) { +void plugin::plugin_initialize(const boost::program_options::variables_map& options) { ilog("database_api plugin: plugin_initialize() begin"); my = std::make_unique(); JSON_RPC_REGISTER_API(plugin_name) - my->database().applied_block.connect([this](const protocol::signed_block &) { - this->clear_block_applied_callback(); + auto& db = my->database(); + db.applied_block.connect([&](const signed_block&) { + my->clear_outdated_callbacks(true); + }); + db.on_pending_transaction.connect([&](const signed_transaction& tx) { + my->clear_outdated_callbacks(false); + }); + db.pre_apply_operation.connect([&](const operation_notification& o) { + my->op_applied_callback(o); }); ilog("database_api plugin: plugin_initialize() end"); } @@ -858,3 +924,11 @@ void plugin::plugin_startup() { } } } } // golos::plugins::database_api + +FC_REFLECT((golos::plugins::database_api::virtual_operations), (block_num)(operations)) +FC_REFLECT((golos::plugins::database_api::block_operation), + (trx_in_block)(op_in_trx)(virtual_op)(op)) +FC_REFLECT_DERIVED((golos::plugins::database_api::block_with_vops), ((golos::protocol::signed_block)), + (_virtual_operations)) +FC_REFLECT_ENUM(golos::plugins::database_api::block_applied_callback_result_type, + (block)(header)(virtual_ops)(full)) diff --git a/plugins/database_api/include/golos/plugins/database_api/plugin.hpp b/plugins/database_api/include/golos/plugins/database_api/plugin.hpp index 1ad7997d4b..d997db5567 100755 --- a/plugins/database_api/include/golos/plugins/database_api/plugin.hpp +++ b/plugins/database_api/include/golos/plugins/database_api/plugin.hpp @@ -94,12 +94,11 @@ struct signed_block_api_object : public signed_block { }; -using block_applied_callback = std::function; - /// API, args, return DEFINE_API_ARGS(get_block_header, msg_pack, optional) DEFINE_API_ARGS(get_block, msg_pack, optional) DEFINE_API_ARGS(set_block_applied_callback, msg_pack, void_type) +DEFINE_API_ARGS(set_pending_transaction_callback, msg_pack, void_type) DEFINE_API_ARGS(get_config, msg_pack, variant_object) DEFINE_API_ARGS(get_dynamic_global_properties, msg_pack, dynamic_global_property_api_object) DEFINE_API_ARGS(get_chain_properties, msg_pack, chain_api_properties) @@ -139,52 +138,26 @@ DEFINE_API_ARGS(get_proposed_transactions, msg_pack, std::vector { public: - constexpr static const char *plugin_name = "database_api"; + constexpr static const char* plugin_name = "database_api"; - static const std::string &name() { + static const std::string& name() { static std::string name = plugin_name; return name; } APPBASE_PLUGIN_REQUIRES( - (json_rpc::plugin) - (chain::plugin) + (json_rpc::plugin) + (chain::plugin) ) - void set_program_options(boost::program_options::options_description &cli, boost::program_options::options_description &cfg) override{} - - void plugin_initialize(const boost::program_options::variables_map &options) override; - + void set_program_options(boost::program_options::options_description& cli, boost::program_options::options_description& cfg) override{} + void plugin_initialize(const boost::program_options::variables_map& options) override; void plugin_startup() override; - void plugin_shutdown() override{} plugin(); - ~plugin(); - /////////////////// - // Subscriptions // - /////////////////// - - void set_subscribe_callback(std::function cb, bool clear_filter); - - void set_pending_transaction_callback(std::function cb); - - /** - * @brief Stop receiving any notifications - * - * This unsubscribes from all subscribed markets and objects. - */ - void cancel_all_subscriptions(); - - - /** - * @brief Clear disconnected callbacks on applied block - */ - - void clear_block_applied_callback(); - DECLARE_API( /** * This API is a short-cut for returning all of the state required for a particular URL @@ -212,10 +185,15 @@ class plugin final : public appbase::plugin { /** * @brief Set callback which is triggered on each generated block - * @param callback function which should be called + * @param type of data, callback will send (block, header, virtual_ops, full) */ (set_block_applied_callback) + /** + * @brief Set callback which is triggered on each received transaction (before applying) + */ + (set_pending_transaction_callback) + ///////////// // Globals // ///////////// @@ -292,11 +270,6 @@ class plugin final : public appbase::plugin { (get_vesting_delegations) (get_expiring_vesting_delegations) - // (list_vesting_delegations) - // (find_vesting_delegations) - // (list_vesting_delegation_expirations) - // (find_vesting_delegation_expirations) - (get_conversion_requests) diff --git a/plugins/debug_node/plugin.cpp b/plugins/debug_node/plugin.cpp index c78312a333..8b7d455078 100644 --- a/plugins/debug_node/plugin.cpp +++ b/plugins/debug_node/plugin.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -478,7 +479,7 @@ bool plugin::plugin_impl::debug_has_hardfork( uint32_t hardfork_id ) { // -#include +#include #define DEFINE_PLUGIN_API(name) DEFINE_API(plugin, name) diff --git a/plugins/follow/CMakeLists.txt b/plugins/follow/CMakeLists.txt index 24aec52fce..8ca06fbfd3 100644 --- a/plugins/follow/CMakeLists.txt +++ b/plugins/follow/CMakeLists.txt @@ -37,6 +37,7 @@ target_link_libraries( golos::chain_plugin golos::protocol golos::api + golos::social_network appbase fc ) diff --git a/plugins/follow/follow_evaluators.cpp b/plugins/follow/follow_evaluators.cpp index ab2ca8f538..5de28d0179 100644 --- a/plugins/follow/follow_evaluators.cpp +++ b/plugins/follow/follow_evaluators.cpp @@ -8,7 +8,28 @@ namespace golos { namespace plugins { namespace follow { - void follow_evaluator::do_apply(const follow_operation &o) { + void save_blog_stats(database& db, account_name_type blogger, account_name_type guest, uint32_t start_count = 0) { + + const auto& stats_idx = db.get_index(); + auto stats_itr = stats_idx.lower_bound(boost::make_tuple(blogger, guest)); + if (stats_itr != stats_idx.end() && stats_itr->blogger == blogger && stats_itr->guest == guest) { + db.modify(*stats_itr, [&](blog_author_stats_object& s) { + if (start_count > 0) { + ++s.count; + } else { + --s.count; + } + }); + } else { + db.create([&](blog_author_stats_object& s) { + s.count = start_count; + s.blogger = blogger; + s.guest = guest; + }); + } + } + + void follow_evaluator::do_apply(const follow_operation& o) { try { static map follow_type_map = []() { map follow_map; @@ -19,7 +40,7 @@ namespace golos { return follow_map; }(); - const auto &idx = db().get_index().indices().get(); + const auto& idx = db().get_index().indices().get(); auto itr = idx.find(boost::make_tuple(o.follower, o.following)); uint16_t what = 0; @@ -41,13 +62,14 @@ namespace golos { } if (what & (1 << ignore)) - FC_ASSERT(!(what & (1 - << blog)), "Cannot follow blog and ignore author at the same time"); + GOLOS_CHECK_LOGIC(!(what & (1 << blog)), + logic_errors::cannot_follow_and_ignore_simultaneously, + "Cannot follow blog and ignore author at the same time"); bool was_followed = false; if (itr == idx.end()) { - db().create([&](follow_object &obj) { + db().create([&](follow_object& obj) { obj.follower = o.follower; obj.following = o.following; obj.what = what; @@ -55,15 +77,15 @@ namespace golos { } else { was_followed = itr->what & 1 << blog; - db().modify(*itr, [&](follow_object &obj) { + db().modify(*itr, [&](follow_object& obj) { obj.what = what; }); } - const auto &follower = db().find(o.follower); + const auto& follower = db().find(o.follower); if (follower == nullptr) { - db().create([&](follow_count_object &obj) { + db().create([&](follow_count_object& obj) { obj.account = o.follower; if (is_following) { @@ -71,7 +93,7 @@ namespace golos { } }); } else { - db().modify(*follower, [&](follow_count_object &obj) { + db().modify(*follower, [&](follow_count_object& obj) { if (was_followed) { obj.following_count--; } @@ -81,10 +103,10 @@ namespace golos { }); } - const auto &following = db().find(o.following); + const auto& following = db().find(o.following); if (following == nullptr) { - db().create([&](follow_count_object &obj) { + db().create([&](follow_count_object& obj) { obj.account = o.following; if (is_following) { @@ -92,7 +114,7 @@ namespace golos { } }); } else { - db().modify(*following, [&](follow_count_object &obj) { + db().modify(*following, [&](follow_count_object& obj) { if (was_followed) { obj.follower_count--; } @@ -105,13 +127,15 @@ namespace golos { FC_CAPTURE_AND_RETHROW((o)) } - void reblog_evaluator::do_apply(const reblog_operation &o) { + void reblog_evaluator::do_apply(const reblog_operation& o) { try { - const auto &c = db().get_comment(o.author, o.permlink); - FC_ASSERT(c.parent_author.size() == 0, "Only top level posts can be reblogged"); + const auto& c = db().get_comment(o.author, o.permlink); + GOLOS_CHECK_LOGIC(c.parent_author.size() == 0, + logic_errors::only_top_level_posts_reblogged, + "Only top level posts can be reblogged"); - const auto &blog_idx = db().get_index().indices().get(); - const auto &blog_comment_idx = db().get_index().indices().get(); + const auto& blog_idx = db().get_index().indices().get(); + const auto& blog_comment_idx = db().get_index().indices().get(); auto next_blog_id = 0; auto last_blog = blog_idx.lower_bound(o.account); @@ -122,35 +146,23 @@ namespace golos { auto blog_itr = blog_comment_idx.find(boost::make_tuple(c.id, o.account)); - FC_ASSERT(blog_itr == blog_comment_idx.end(), "Account has already reblogged this post"); - db().create([&](blog_object &b) { + GOLOS_CHECK_LOGIC(blog_itr == blog_comment_idx.end(), + logic_errors::account_already_reblogged_this_post, + "Account has already reblogged this post"); + db().create([&](blog_object& b) { b.account = o.account; b.comment = c.id; b.reblogged_on = db().head_block_time(); b.blog_feed_id = next_blog_id; }); - const auto &stats_idx = db().get_index(); - auto stats_itr = stats_idx.lower_bound(boost::make_tuple(o.account, c.author)); - if (stats_itr != stats_idx.end() && stats_itr->blogger == o.account && - stats_itr->guest == c.author) { - db().modify(*stats_itr, [&](blog_author_stats_object &s) { - ++s.count; - }); - } else { - db().create([&](blog_author_stats_object &s) { - s.count = 1; - s.blogger = o.account; - s.guest = c.author; - }); - } - - const auto &feed_idx = db().get_index().indices().get(); - const auto &comment_idx = db().get_index().indices().get(); - const auto &idx = db().get_index().indices().get(); - auto itr = idx.find(o.account); + save_blog_stats(db(), o.account, c.author, 1); - while (itr != idx.end() && itr->following == o.account) { + const auto& feed_idx = db().get_index().indices().get(); + const auto& comment_idx = db().get_index().indices().get(); + const auto& idx = db().get_index().indices().get(); + + for (auto itr = idx.find(o.account); itr != idx.end() && itr->following == o.account; ++itr) { if (itr->what & (1 << blog)) { uint32_t next_id = 0; @@ -163,7 +175,7 @@ namespace golos { auto feed_itr = comment_idx.find(boost::make_tuple(c.id, itr->follower)); if (feed_itr == comment_idx.end()) { - db().create([&](feed_object &f) { + db().create([&](feed_object& f) { f.account = itr->follower; f.reblogged_by.push_back(o.account); f.first_reblogged_by = o.account; @@ -173,13 +185,13 @@ namespace golos { f.account_feed_id = next_id; }); } else { - db().modify(*feed_itr, [&](feed_object &f) { + db().modify(*feed_itr, [&](feed_object& f) { f.reblogged_by.push_back(o.account); f.reblogs++; }); } - const auto &old_feed_idx = db().get_index().indices().get(); + const auto& old_feed_idx = db().get_index().indices().get(); auto old_feed = old_feed_idx.lower_bound(itr->follower); while (old_feed->account == itr->follower && next_id - old_feed->account_feed_id > _plugin->max_feed_size()) { @@ -187,12 +199,59 @@ namespace golos { old_feed = old_feed_idx.lower_bound(itr->follower); }; } + } + } FC_CAPTURE_AND_RETHROW((o)) + } - ++itr; + void delete_reblog_evaluator::do_apply(const delete_reblog_operation& o) { + try { + const auto& c = db().get_comment(o.author, o.permlink); + + // Deleting blog object + + const auto& blog_comment_idx = db().get_index().indices().get(); + + auto blog_itr = blog_comment_idx.find(boost::make_tuple(c.id, o.account)); + + GOLOS_CHECK_LOGIC(blog_itr != blog_comment_idx.end(), + logic_errors::account_has_not_reblogged_this_post, + "Account has not reblogged this post"); + + db().remove(*blog_itr); + + // Fixing blog statistics + + save_blog_stats(db(), o.account, c.author, 0); + + // Removing info about reblog from feed_objects for followers of reblogger + + const auto& comment_idx = db().get_index().indices().get(); + const auto& idx = db().get_index().indices().get(); + + for (auto itr = idx.find(o.account); itr != idx.end() && itr->following == o.account; ++itr) { + + if (itr->what & (1 << blog)) { + + auto feed_itr = comment_idx.find(boost::make_tuple(c.id, itr->follower)); + + if (feed_itr != comment_idx.end()) { + if (feed_itr->reblogs <= 1) { + db().remove(*feed_itr); + } else { + db().modify(*feed_itr, [&](feed_object& f) { + f.reblogged_by.erase(std::remove(f.reblogged_by.begin(), f.reblogged_by.end(), + o.account)); + f.reblogs--; + }); + } + } + } } + + } FC_CAPTURE_AND_RETHROW((o)) } } } -} // golos::follow \ No newline at end of file +} // golos::follow diff --git a/plugins/follow/follow_operations.cpp b/plugins/follow/follow_operations.cpp index 5eed0eb923..e0895975a4 100644 --- a/plugins/follow/follow_operations.cpp +++ b/plugins/follow/follow_operations.cpp @@ -5,16 +5,36 @@ namespace golos { namespace plugins { namespace follow { + /// TODO: after the hardfork, we can rename this method validate_permlink because it is strictily less restrictive than before + /// Issue #56 contains the justificiation for allowing any UTF-8 string to serve as a permlink, content will be grouped by tags + /// going forward. + inline void validate_permlink(const string &permlink) { + GOLOS_CHECK_VALUE(permlink.size() < STEEMIT_MAX_PERMLINK_LENGTH, "permlink is too long"); + GOLOS_CHECK_VALUE(fc::is_utf8(permlink), "permlink not formatted in UTF8"); + } + void follow_operation::validate() const { - FC_ASSERT(follower != following, "You cannot follow yourself"); + GOLOS_CHECK_LOGIC(follower != following, + logic_errors::cannot_follow_yourself, + "You cannot follow yourself"); } void reblog_operation::validate() const { - FC_ASSERT(account != author, "You cannot reblog your own content"); + GOLOS_CHECK_LOGIC(account != author, + logic_errors::cannot_reblog_own_content, + "You cannot reblog your own content"); + GOLOS_CHECK_PARAM(permlink, validate_permlink(permlink)); + } + + void delete_reblog_operation::validate() const { + GOLOS_CHECK_LOGIC(account != author, + logic_errors::cannot_delete_reblog_of_own_content, + "You cannot delete reblog of your own content"); + GOLOS_CHECK_PARAM(permlink, validate_permlink(permlink)); } } } } //golos::follow -DEFINE_OPERATION_TYPE(golos::plugins::follow::follow_plugin_operation) \ No newline at end of file +DEFINE_OPERATION_TYPE(golos::plugins::follow::follow_plugin_operation) diff --git a/plugins/follow/include/golos/plugins/follow/follow_api_object.hpp b/plugins/follow/include/golos/plugins/follow/follow_api_object.hpp index f5b5a53de7..ce53a5bfd0 100644 --- a/plugins/follow/include/golos/plugins/follow/follow_api_object.hpp +++ b/plugins/follow/include/golos/plugins/follow/follow_api_object.hpp @@ -67,7 +67,7 @@ namespace golos { limit(lim) { } - follow_count_api_obj(const follow_count_api_obj &o) : + follow_count_api_obj(const follow_count_api_obj& o) : account(o.account), follower_count(o.follower_count), following_count(o.following_count), diff --git a/plugins/follow/include/golos/plugins/follow/follow_evaluators.hpp b/plugins/follow/include/golos/plugins/follow/follow_evaluators.hpp index cc00314153..0ec41396b5 100644 --- a/plugins/follow/include/golos/plugins/follow/follow_evaluators.hpp +++ b/plugins/follow/include/golos/plugins/follow/follow_evaluators.hpp @@ -15,24 +15,36 @@ namespace golos { public: typedef follow_operation operation_type; - follow_evaluator(database &db, plugin *plugin) : golos::chain::evaluator_impl(db), _plugin(plugin) { + follow_evaluator(database& db, plugin* plugin) : golos::chain::evaluator_impl(db), _plugin(plugin) { } - void do_apply(const follow_operation &o); + void do_apply(const follow_operation& o); - plugin *_plugin; + plugin* _plugin; }; class reblog_evaluator : public golos::chain::evaluator_impl { public: typedef reblog_operation operation_type; - reblog_evaluator(database &db, plugin *plugin) : golos::chain::evaluator_impl(db), _plugin(plugin) { + reblog_evaluator(database& db, plugin* plugin) : golos::chain::evaluator_impl(db), _plugin(plugin) { } - void do_apply(const reblog_operation &o); + void do_apply(const reblog_operation& o); - plugin *_plugin; + plugin* _plugin; + }; + + class delete_reblog_evaluator : public golos::chain::evaluator_impl { + public: + typedef delete_reblog_operation operation_type; + + delete_reblog_evaluator(database& db, plugin* plugin) : golos::chain::evaluator_impl(db), _plugin(plugin) { + } + + void do_apply(const delete_reblog_operation& o); + + plugin* _plugin; }; } } diff --git a/plugins/follow/include/golos/plugins/follow/follow_objects.hpp b/plugins/follow/include/golos/plugins/follow/follow_objects.hpp index 0bc3f7c24c..3bda8d6105 100644 --- a/plugins/follow/include/golos/plugins/follow/follow_objects.hpp +++ b/plugins/follow/include/golos/plugins/follow/follow_objects.hpp @@ -10,7 +10,7 @@ namespace golos { using protocol::share_type; using chainbase::object; using chainbase::object_id; - using chainbase::allocator ; + using chainbase::allocator; using chainbase::shared_vector; using golos::chain::comment_object; using golos::chain::by_id; diff --git a/plugins/follow/include/golos/plugins/follow/follow_operations.hpp b/plugins/follow/include/golos/plugins/follow/follow_operations.hpp index d6006c1bf9..8686704bda 100644 --- a/plugins/follow/include/golos/plugins/follow/follow_operations.hpp +++ b/plugins/follow/include/golos/plugins/follow/follow_operations.hpp @@ -20,7 +20,7 @@ namespace golos { } }; - struct reblog_operation : base_operation{ + struct reblog_operation : base_operation { protocol::account_name_type account; protocol::account_name_type author; std::string permlink; @@ -31,7 +31,18 @@ namespace golos { } }; - using follow_plugin_operation = fc::static_variant; + struct delete_reblog_operation : base_operation { + protocol::account_name_type account; + protocol::account_name_type author; + std::string permlink; + + void validate() const; + void get_required_posting_authorities(flat_set& a) const { + a.insert(account); + } + }; + + using follow_plugin_operation = fc::static_variant; } } @@ -39,6 +50,7 @@ namespace golos { FC_REFLECT((golos::plugins::follow::follow_operation), (follower)(following)(what)); FC_REFLECT((golos::plugins::follow::reblog_operation), (account)(author)(permlink)); +FC_REFLECT((golos::plugins::follow::delete_reblog_operation), (account)(author)(permlink)); FC_REFLECT_TYPENAME((golos::plugins::follow::follow_plugin_operation)); DECLARE_OPERATION_TYPE(golos::plugins::follow::follow_plugin_operation) diff --git a/plugins/follow/include/golos/plugins/follow/plugin.hpp b/plugins/follow/include/golos/plugins/follow/plugin.hpp index 44a0bbb813..65983437d9 100644 --- a/plugins/follow/include/golos/plugins/follow/plugin.hpp +++ b/plugins/follow/include/golos/plugins/follow/plugin.hpp @@ -3,11 +3,24 @@ #include #include #include +#include #include "follow_api_object.hpp" namespace golos { namespace plugins { namespace follow { using json_rpc::msg_pack; + struct logic_errors { + enum error_type { + cannot_follow_yourself, + cannot_reblog_own_content, + cannot_delete_reblog_of_own_content, + cannot_follow_and_ignore_simultaneously, + only_top_level_posts_reblogged, + account_already_reblogged_this_post, + account_has_not_reblogged_this_post, + }; + }; + void fill_account_reputation( const golos::chain::database& db, const account_name_type& account, @@ -30,14 +43,14 @@ namespace golos { namespace plugins { namespace follow { class plugin final : public appbase::plugin { public: - constexpr static const char *plugin_name = "follow"; + constexpr static const char* plugin_name = "follow"; APPBASE_PLUGIN_REQUIRES( (chain::plugin) (json_rpc::plugin) ) - static const std::string &name() { + static const std::string& name() { static std::string name = plugin_name; return name; } @@ -59,10 +72,10 @@ namespace golos { namespace plugins { namespace follow { plugin(); - void set_program_options(boost::program_options::options_description &cli, - boost::program_options::options_description &cfg) override; + void set_program_options(boost::program_options::options_description& cli, + boost::program_options::options_description& cfg) override; - void plugin_initialize(const boost::program_options::variables_map &options) override; + void plugin_initialize(const boost::program_options::variables_map& options) override; uint32_t max_feed_size(); @@ -78,3 +91,13 @@ namespace golos { namespace plugins { namespace follow { }; } } } // golos::plugins::follow + +FC_REFLECT_ENUM(golos::plugins::follow::logic_errors::error_type, + (cannot_follow_yourself) + (cannot_reblog_own_content) + (cannot_delete_reblog_of_own_content) + (cannot_follow_and_ignore_simultaneously) + (only_top_level_posts_reblogged) + (account_already_reblogged_this_post) + (account_has_not_reblogged_this_post) +); diff --git a/plugins/follow/plugin.cpp b/plugins/follow/plugin.cpp index 4fccc3379d..161eff7cd1 100644 --- a/plugins/follow/plugin.cpp +++ b/plugins/follow/plugin.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -9,10 +10,18 @@ #include #include #include +#include #include +#include -#define CHECK_ARG_SIZE(s) \ - FC_ASSERT( args.args->size() == s, "Expected #s argument(s), was ${n}", ("n", args.args->size()) ); +namespace golos { + +template<> +std::string get_logic_error_namespace() { + return golos::plugins::follow::plugin::name(); +} + +} // namespace golos namespace golos { namespace plugins { @@ -24,6 +33,7 @@ namespace golos { using golos::chain::to_string; using golos::chain::account_index; using golos::chain::by_name; + using golos::api::discussion_helper; void fill_account_reputation( const golos::chain::database& db, @@ -34,7 +44,7 @@ namespace golos { return; } - auto &rep_idx = db.get_index().indices().get(); + auto& rep_idx = db.get_index().indices().get(); auto itr = rep_idx.find(account); if (rep_idx.end() != itr) { reputation = itr->reputation; @@ -44,47 +54,47 @@ namespace golos { } struct pre_operation_visitor { - plugin &_plugin; - golos::chain::database &db; + plugin& _plugin; + golos::chain::database& db; - pre_operation_visitor(plugin &plugin, golos::chain::database &db) : _plugin(plugin), db(db) { + pre_operation_visitor(plugin& plugin, golos::chain::database& db) : _plugin(plugin), db(db) { } typedef void result_type; template - void operator()(const T &) const { + void operator()(const T&) const { } - void operator()(const vote_operation &op) const { + void operator()(const vote_operation& op) const { try { - const auto &c = db.get_comment(op.author, op.permlink); + const auto& c = db.get_comment(op.author, op.permlink); if (db.calculate_discussion_payout_time(c) == fc::time_point_sec::maximum()) { return; } - const auto &cv_idx = db.get_index().indices().get(); + const auto& cv_idx = db.get_index().indices().get(); auto cv = cv_idx.find(std::make_tuple(c.id, db.get_account(op.voter).id)); if (cv != cv_idx.end()) { - const auto &rep_idx = db.get_index().indices().get(); + const auto& rep_idx = db.get_index().indices().get(); auto rep = rep_idx.find(op.author); if (rep != rep_idx.end()) { - db.modify(*rep, [&](reputation_object &r) { + db.modify(*rep, [&](reputation_object& r) { r.reputation -= (cv->rshares >> 6); // Shift away precision from vests. It is noise }); } } - } catch (const fc::exception &e) { + } catch (const fc::exception& e) { } } - void operator()(const delete_comment_operation &op) const { + void operator()(const delete_comment_operation& op) const { try { - const auto *comment = db.find_comment(op.author, op.permlink); + const auto* comment = db.find_comment(op.author, op.permlink); if (comment == nullptr) { return; @@ -93,20 +103,20 @@ namespace golos { return; } - const auto &feed_idx = db.get_index().indices().get(); + const auto& feed_idx = db.get_index().indices().get(); auto itr = feed_idx.lower_bound(comment->id); while (itr != feed_idx.end() && itr->comment == comment->id) { - const auto &old_feed = *itr; + const auto& old_feed = *itr; ++itr; db.remove(old_feed); } - const auto &blog_idx = db.get_index().indices().get(); + const auto& blog_idx = db.get_index().indices().get(); auto blog_itr = blog_idx.lower_bound(comment->id); while (blog_itr != blog_idx.end() && blog_itr->comment == comment->id) { - const auto &old_blog = *blog_itr; + const auto& old_blog = *blog_itr; ++blog_itr; db.remove(old_blog); } @@ -115,19 +125,19 @@ namespace golos { }; struct post_operation_visitor { - plugin &_plugin; - database &db; + plugin& _plugin; + database& db; - post_operation_visitor(plugin &plugin, database &db) : _plugin(plugin), db(db) { + post_operation_visitor(plugin& plugin, database& db) : _plugin(plugin), db(db) { } typedef void result_type; template - void operator()(const T &) const { + void operator()(const T&) const { } - void operator()(const custom_json_operation &op) const { + void operator()(const custom_json_operation& op) const { try { if (op.id == plugin::plugin_name) { custom_json_operation new_cop; @@ -139,7 +149,7 @@ namespace golos { try { fop = fc::json::from_string(op.json).as(); - } catch (const fc::exception &) { + } catch (const fc::exception&) { return; } @@ -151,23 +161,23 @@ namespace golos { } FC_CAPTURE_AND_RETHROW() } - void operator()(const comment_operation &op) const { + void operator()(const comment_operation& op) const { try { if (op.parent_author.size() > 0) { return; } - const auto &c = db.get_comment(op.author, op.permlink); + const auto& c = db.get_comment(op.author, op.permlink); if (c.created != db.head_block_time()) { return; } - const auto &idx = db.get_index().indices().get(); - const auto &comment_idx = db.get_index().indices().get(); + const auto& idx = db.get_index().indices().get(); + const auto& comment_idx = db.get_index().indices().get(); auto itr = idx.find(op.author); - const auto &feed_idx = db.get_index().indices().get(); + const auto& feed_idx = db.get_index().indices().get(); while (itr != idx.end() && itr->following == op.author) { if (itr->what & (1 << blog)) { @@ -179,13 +189,13 @@ namespace golos { } if (comment_idx.find(boost::make_tuple(c.id, itr->follower)) == comment_idx.end()) { - db.create([&](feed_object &f) { + db.create([&](feed_object& f) { f.account = itr->follower; f.comment = c.id; f.account_feed_id = next_id; }); - const auto &old_feed_idx = db.get_index().indices().get(); + const auto& old_feed_idx = db.get_index().indices().get(); auto old_feed = old_feed_idx.lower_bound(itr->follower); while (old_feed->account == itr->follower && @@ -199,8 +209,8 @@ namespace golos { ++itr; } - const auto &blog_idx = db.get_index().indices().get(); - const auto &comment_blog_idx = db.get_index().indices().get(); + const auto& blog_idx = db.get_index().indices().get(); + const auto& comment_blog_idx = db.get_index().indices().get(); auto last_blog = blog_idx.lower_bound(op.author); uint32_t next_id = 0; @@ -209,13 +219,13 @@ namespace golos { } if (comment_blog_idx.find(boost::make_tuple(c.id, op.author)) == comment_blog_idx.end()) { - db.create([&](blog_object &b) { + db.create([&](blog_object& b) { b.account = op.author; b.comment = c.id; b.blog_feed_id = next_id; }); - const auto &old_blog_idx = db.get_index().indices().get(); + const auto& old_blog_idx = db.get_index().indices().get(); auto old_blog = old_blog_idx.lower_bound(op.author); while (old_blog->account == op.author && @@ -227,18 +237,18 @@ namespace golos { } FC_LOG_AND_RETHROW() } - void operator()(const vote_operation &op) const { + void operator()(const vote_operation& op) const { try { - const auto &comment = db.get_comment(op.author, op.permlink); + const auto& comment = db.get_comment(op.author, op.permlink); if (db.calculate_discussion_payout_time(comment) == fc::time_point_sec::maximum()) { return; } - const auto &cv_idx = db.get_index().indices().get(); + const auto& cv_idx = db.get_index().indices().get(); auto cv = cv_idx.find(boost::make_tuple(comment.id, db.get_account(op.voter).id)); - const auto &rep_idx = db.get_index().indices().get(); + const auto& rep_idx = db.get_index().indices().get(); auto voter_rep = rep_idx.find(op.voter); auto author_rep = rep_idx.find(op.author); @@ -255,7 +265,7 @@ namespace golos { return; } - db.create([&](reputation_object &r) { + db.create([&](reputation_object& r) { r.account = op.author; r.reputation = (cv->rshares >> 6); // Shift away precision from vests. It is noise }); @@ -266,7 +276,7 @@ namespace golos { return; } - db.modify(*author_rep, [&](reputation_object &r) { + db.modify(*author_rep, [&](reputation_object& r) { r.reputation += (cv->rshares >> 6); // Shift away precision from vests. It is noise }); } @@ -274,7 +284,7 @@ namespace golos { } }; - inline void set_what(std::vector &what, uint16_t bitmask) { + inline void set_what(std::vector& what, uint16_t bitmask) { if (bitmask & 1 << blog) { what.push_back(blog); } @@ -286,12 +296,18 @@ namespace golos { struct plugin::impl final { public: impl() : database_(appbase::app().get_plugin().db()) { + helper = std::make_unique( + database_, + follow::fill_account_reputation, + nullptr, + golos::plugins::social_network::fill_comment_info + ); } ~impl() { }; - void plugin_initialize(plugin &self) { + void plugin_initialize(plugin& self) { // Each plugin needs its own evaluator registry. _custom_operation_interpreter = std::make_shared< generic_custom_operation_interpreter>(database()); @@ -299,27 +315,28 @@ namespace golos { // Add each operation evaluator to the registry _custom_operation_interpreter->register_evaluator(&self); _custom_operation_interpreter->register_evaluator(&self); + _custom_operation_interpreter->register_evaluator(&self); // Add the registry to the database so the database can delegate custom ops to the plugin database().set_custom_operation_interpreter(plugin_name, _custom_operation_interpreter); } - golos::chain::database &database() { + golos::chain::database& database() { return database_; } - void pre_operation(const operation_notification &op_obj, plugin &self) { + void pre_operation(const operation_notification& op_obj, plugin& self) { try { op_obj.op.visit(pre_operation_visitor(self, database())); - } catch (const fc::assert_exception &) { + } catch (const fc::assert_exception&) { if (database().is_producing()) { throw; } } } - void post_operation(const operation_notification &op_obj, plugin &self) { + void post_operation(const operation_notification& op_obj, plugin& self) { try { op_obj.op.visit(post_operation_visitor(self, database())); } catch (fc::assert_exception) { @@ -372,37 +389,39 @@ namespace golos { blog_authors_r get_blog_authors(account_name_type ); - golos::chain::database &database_; + golos::chain::database& database_; uint32_t max_feed_size_ = 500; std::shared_ptr> _custom_operation_interpreter; + + std::unique_ptr helper; }; plugin::plugin() { } - void plugin::set_program_options(boost::program_options::options_description &cli, - boost::program_options::options_description &cfg) { + void plugin::set_program_options(boost::program_options::options_description& cli, + boost::program_options::options_description& cfg) { cli.add_options() ("follow-max-feed-size", boost::program_options::value()->default_value(500), "Set the maximum size of cached feed for an account"); cfg.add(cli); } - void plugin::plugin_initialize(const boost::program_options::variables_map &options) { + void plugin::plugin_initialize(const boost::program_options::variables_map& options) { try { ilog("Intializing follow plugin"); pimpl.reset(new impl()); - auto &db = pimpl->database(); + auto& db = pimpl->database(); pimpl->plugin_initialize(*this); - db.pre_apply_operation.connect([&](operation_notification &o) { + db.pre_apply_operation.connect([&](operation_notification& o) { pimpl->pre_operation(o, *this); }); - db.post_apply_operation.connect([&](const operation_notification &o) { + db.post_apply_operation.connect([&](const operation_notification& o) { pimpl->post_operation(o, *this); }); golos::chain::add_plugin_index(db); @@ -439,11 +458,11 @@ namespace golos { follow_type type, uint32_t limit) { - FC_ASSERT(limit <= 1000); + GOLOS_CHECK_LIMIT_PARAM(limit, 1000); std::vector result; result.reserve(limit); - const auto &idx = database().get_index().indices().get(); + const auto& idx = database().get_index().indices().get(); auto itr = idx.lower_bound(std::make_tuple(account, start)); while (itr != idx.end() && result.size() < limit && itr->following == account) { if (type == undefined || itr->what & (1 << type)) { @@ -465,9 +484,10 @@ namespace golos { account_name_type start, follow_type type, uint32_t limit) { - FC_ASSERT(limit <= 100); + + GOLOS_CHECK_LIMIT_PARAM(limit, 100); std::vector result; - const auto &idx = database().get_index().indices().get(); + const auto& idx = database().get_index().indices().get(); auto itr = idx.lower_bound(std::make_tuple(account, start)); while (itr != idx.end() && result.size() < limit && itr->follower == account) { if (type == undefined || itr->what & (1 << type)) { @@ -501,7 +521,7 @@ namespace golos { account_name_type account, uint32_t entry_id, uint32_t limit) { - FC_ASSERT(limit <= 500, "Cannot retrieve more than 500 feed entries at a time."); + GOLOS_CHECK_LIMIT_PARAM(limit, 500); if (entry_id == 0) { entry_id = ~0; @@ -510,19 +530,19 @@ namespace golos { std::vector result; result.reserve(limit); - const auto &db = database(); - const auto &feed_idx = db.get_index().indices().get(); + const auto& db = database(); + const auto& feed_idx = db.get_index().indices().get(); auto itr = feed_idx.lower_bound(boost::make_tuple(account, entry_id)); while (itr != feed_idx.end() && itr->account == account && result.size() < limit) { - const auto &comment = db.get(itr->comment); + const auto& comment = db.get(itr->comment); feed_entry entry; entry.author = comment.author; entry.permlink = to_string(comment.permlink); entry.entry_id = itr->account_feed_id; if (itr->first_reblogged_by != account_name_type()) { entry.reblog_by.reserve(itr->reblogged_by.size()); - for (const auto &a : itr->reblogged_by) { + for (const auto& a : itr->reblogged_by) { entry.reblog_by.push_back(a); } //entry.reblog_by = itr->first_reblogged_by; @@ -540,7 +560,7 @@ namespace golos { account_name_type account, uint32_t entry_id, uint32_t limit) { - FC_ASSERT(limit <= 500, "Cannot retrieve more than 500 feed entries at a time."); + GOLOS_CHECK_LIMIT_PARAM(limit, 500); if (entry_id == 0) { entry_id = ~0; @@ -549,19 +569,19 @@ namespace golos { std::vector result; result.reserve(limit); - const auto &db = database(); - const auto &feed_idx = db.get_index().indices().get(); + const auto& db = database(); + const auto& feed_idx = db.get_index().indices().get(); auto itr = feed_idx.lower_bound(boost::make_tuple(account, entry_id)); while (itr != feed_idx.end() && itr->account == account && result.size() < limit) { - const auto &comment = db.get(itr->comment); + const auto& comment = db.get(itr->comment); comment_feed_entry entry; - entry.comment = comment_api_object(comment, db); + entry.comment = helper->create_comment_api_object(comment); entry.entry_id = itr->account_feed_id; if (itr->first_reblogged_by != account_name_type()) { //entry.reblog_by = itr->first_reblogged_by; entry.reblog_by.reserve(itr->reblogged_by.size()); - for (const auto &a : itr->reblogged_by) { + for (const auto& a : itr->reblogged_by) { entry.reblog_by.push_back(a); } entry.reblog_on = itr->first_reblogged_on; @@ -578,7 +598,7 @@ namespace golos { account_name_type account, uint32_t entry_id, uint32_t limit) { - FC_ASSERT(limit <= 500, "Cannot retrieve more than 500 blog entries at a time."); + GOLOS_CHECK_LIMIT_PARAM(limit, 500); if (entry_id == 0) { entry_id = ~0; @@ -587,12 +607,12 @@ namespace golos { std::vector result; result.reserve(limit); - const auto &db = database(); - const auto &blog_idx = db.get_index().indices().get(); + const auto& db = database(); + const auto& blog_idx = db.get_index().indices().get(); auto itr = blog_idx.lower_bound(boost::make_tuple(account, entry_id)); while (itr != blog_idx.end() && itr->account == account && result.size() < limit) { - const auto &comment = db.get(itr->comment); + const auto& comment = db.get(itr->comment); blog_entry entry; entry.author = comment.author; entry.permlink = to_string(comment.permlink); @@ -612,7 +632,7 @@ namespace golos { account_name_type account, uint32_t entry_id, uint32_t limit) { - FC_ASSERT(limit <= 500, "Cannot retrieve more than 500 blog entries at a time."); + GOLOS_CHECK_LIMIT_PARAM(limit, 500); if (entry_id == 0) { entry_id = ~0; @@ -621,14 +641,14 @@ namespace golos { std::vector result; result.reserve(limit); - const auto &db = database(); - const auto &blog_idx = db.get_index().indices().get(); + const auto& db = database(); + const auto& blog_idx = db.get_index().indices().get(); auto itr = blog_idx.lower_bound(boost::make_tuple(account, entry_id)); while (itr != blog_idx.end() && itr->account == account && result.size() < limit) { - const auto &comment = db.get(itr->comment); + const auto& comment = db.get(itr->comment); comment_blog_entry entry; - entry.comment = comment_api_object(comment, db); + entry.comment = helper->create_comment_api_object(comment); entry.blog = account; entry.reblog_on = itr->reblogged_on; entry.entry_id = itr->blog_feed_id; @@ -645,9 +665,10 @@ namespace golos { std::vector < account_name_type > accounts ) { - FC_ASSERT(accounts.size() <= 100, "Cannot retrieve more than 100 account reputations at a time."); + GOLOS_CHECK_PARAM(accounts, + GOLOS_CHECK_VALUE(accounts.size() <= 100, "Cannot retrieve more than 100 account reputations at a time.")); - const auto &idx = database().get_index().indices().get(); + const auto& idx = database().get_index().indices().get(); size_t acc_count = accounts.size(); @@ -677,10 +698,10 @@ namespace golos { account_name_type author, std::string permlink ) { - auto &db = database(); + auto& db = database(); std::vector result; - const auto &post = db.get_comment(author, permlink); - const auto &blog_idx = db.get_index(); + const auto& post = db.get_comment(author, permlink); + const auto& blog_idx = db.get_index(); auto itr = blog_idx.lower_bound(post.id); while (itr != blog_idx.end() && itr->comment == post.id && result.size() < 2000) { result.push_back(itr->account); @@ -690,9 +711,9 @@ namespace golos { } blog_authors_r plugin::impl::get_blog_authors(account_name_type blog_account) { - auto &db = database(); + auto& db = database(); blog_authors_r result; - const auto &stats_idx = db.get_index(); + const auto& stats_idx = db.get_index(); auto itr = stats_idx.lower_bound(boost::make_tuple(blog_account)); while (itr != stats_idx.end() && itr->blogger == blog_account && result.size()) { result.emplace_back(itr->guest, itr->count); @@ -702,95 +723,107 @@ namespace golos { } DEFINE_API(plugin, get_followers) { - CHECK_ARG_SIZE(4) - auto following = args.args->at(0).as(); - auto start_follower = args.args->at(1).as(); - auto type = args.args->at(2).as(); - auto limit = args.args->at(3).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, following) + (account_name_type, start_follower) + (follow_type, type) + (uint32_t, limit) + ) return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_followers(following, start_follower, type, limit); }); } DEFINE_API(plugin, get_following) { - CHECK_ARG_SIZE(4) - auto follower = args.args->at(0).as(); - auto start_following = args.args->at(1).as(); - auto type = args.args->at(2).as(); - auto limit = args.args->at(3).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, follower) + (account_name_type, start_following) + (follow_type, type) + (uint32_t, limit) + ) return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_following(follower, start_following, type, limit); }); } DEFINE_API(plugin, get_follow_count) { - auto tmp = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, account) + ) return pimpl->database().with_weak_read_lock([&]() { - return pimpl->get_follow_count(tmp); + return pimpl->get_follow_count(account); }); } DEFINE_API(plugin, get_feed_entries){ - CHECK_ARG_SIZE(3) - auto account = args.args->at(0).as(); - auto entry_id = args.args->at(1).as(); - auto limit = args.args->at(2).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, account) + (uint32_t, entry_id) + (uint32_t, limit) + ) return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_feed_entries(account, entry_id, limit); }); } DEFINE_API(plugin, get_feed) { - CHECK_ARG_SIZE(3) - auto account = args.args->at(0).as(); - auto entry_id = args.args->at(1).as(); - auto limit = args.args->at(2).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, account) + (uint32_t, entry_id) + (uint32_t, limit) + ) return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_feed(account, entry_id, limit); }); } DEFINE_API(plugin, get_blog_entries) { - CHECK_ARG_SIZE(3) - auto account = args.args->at(0).as(); - auto entry_id = args.args->at(1).as(); - auto limit = args.args->at(2).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, account) + (uint32_t, entry_id) + (uint32_t, limit) + ) return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_blog_entries(account, entry_id, limit); }); } DEFINE_API(plugin, get_blog) { - CHECK_ARG_SIZE(3) - auto account = args.args->at(0).as(); - auto entry_id = args.args->at(1).as(); - auto limit = args.args->at(2).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, account) + (uint32_t, entry_id) + (uint32_t, limit) + ) return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_blog(account, entry_id, limit); }); } DEFINE_API(plugin, get_account_reputations) { - CHECK_ARG_SIZE(1) - auto accounts = args.args->at(0).as< std::vector < account_name_type > >(); + PLUGIN_API_VALIDATE_ARGS( + (std::vector, accounts) + ) return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_account_reputations( accounts ); }); } DEFINE_API(plugin, get_reblogged_by) { - CHECK_ARG_SIZE(2) - auto author = args.args->at(0).as(); - auto permlink = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, author) + (std::string, permlink) + ) return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_reblogged_by(author, permlink); }); } DEFINE_API(plugin, get_blog_authors) { - auto tmp = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, account) + ) return pimpl->database().with_weak_read_lock([&]() { - return pimpl->get_blog_authors(tmp); + return pimpl->get_blog_authors(account); }); } } diff --git a/plugins/json_rpc/CMakeLists.txt b/plugins/json_rpc/CMakeLists.txt index 99e491fd15..9f9560e2d9 100644 --- a/plugins/json_rpc/CMakeLists.txt +++ b/plugins/json_rpc/CMakeLists.txt @@ -23,7 +23,7 @@ endif() add_library(golos::${CURRENT_TARGET} ALIAS golos_${CURRENT_TARGET}) set_property(TARGET golos_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGET}) -target_link_libraries(golos_${CURRENT_TARGET} appbase fc) +target_link_libraries(golos_${CURRENT_TARGET} golos_protocol appbase fc) target_include_directories(golos_${CURRENT_TARGET} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../../") diff --git a/plugins/debug_node/include/golos/plugins/debug_node/api_helper.hpp b/plugins/json_rpc/include/golos/plugins/json_rpc/api_helper.hpp similarity index 78% rename from plugins/debug_node/include/golos/plugins/debug_node/api_helper.hpp rename to plugins/json_rpc/include/golos/plugins/json_rpc/api_helper.hpp index b2041a7de2..e9e275226b 100644 --- a/plugins/debug_node/include/golos/plugins/debug_node/api_helper.hpp +++ b/plugins/json_rpc/include/golos/plugins/json_rpc/api_helper.hpp @@ -9,10 +9,9 @@ // ) //Expands to: // std::string json_filename; -// FC_ASSERT(args.args.valid(), "Invalid parameters"); // auto n_args = args.args->size(); -// FC_ASSERT(n_args == 1, "Expected 1 parameter, received ${n}", ("n", n_args)); -// json_filename = args.args->at(0).as(); +// GOLOS_ASSERT(n_args == 1, golos::invalid_arguments_count, "Expected 1 parameter, received ${n}", ("n", n_args)("required",1)); +// json_filename = GOLOS_CONVERT_PARAM(json_filename,args.args->at(0),std::string); // //And this: // PLUGIN_API_VALIDATE_ARGS( @@ -24,13 +23,12 @@ // std::string json_filename; // uint32_t count; // uint32_t skip_flags = golos::chain::database::skip_nothing; -// FC_ASSERT(args.args.valid(), "Invalid parameters"); // auto n_args = args.args->size(); -// FC_ASSERT(n_args >= 2 && n_args <= 3, "Expected at least 2 and up to 3 parameters, received ${n}", ("n", n_args)); -// json_filename = args.args->at(0).as(); -// count = args.args->at(1).as(); +// GOLOS_ASSERT(n_args >= 2 && n_args <= 3, golos::invalid_arguments_count, "Expected at least 2 and up to 3 parameters, received ${n}", ("n", n_args)("min",2)("max",3)); +// json_filename = GOLOS_CONVERT_PARAM(json_filename,args.args->at(0),std::string); +// count = GOLOS_CONVERT_PARAM(count,args.args->at(1),uint32_t); // if (n_args > 2) { -// skip_flags = args.args->at(2).as(); +// skip_flags = GOLOS_CONVERT_PARAM(skip_flags,args.args->at(2),uint32_t); // } #include @@ -56,7 +54,7 @@ // main #define PLUGIN_API_VALIDATE_ARGS_II(ARGS, N_REQ, N_ARGS) \ PLUGIN_API_DECLARE_ARG_VARS(ARGS) \ - FC_ASSERT(args.args.valid(), "Invalid parameters"); /* is it possible to get invalid?*/ \ + /*FC_ASSERT(args.args.valid(), "Invalid parameters"); *//* is it possible to get invalid?*/ \ auto n_args = args.args->size(); \ PLUGIN_API_ARGS_NUM_ASSERT(N_REQ, N_ARGS) \ PLUGIN_API_LOAD_AND_CHECK_ARGS(ARGS, N_REQ) @@ -87,20 +85,21 @@ #define PLUGIN_API_ARGS_NUM_ASSERT(req, all) PLUGIN_API_ARGS_NUM_ASSERT_I(BOOST_PP_LESS(req, all), req, all) #define PLUGIN_API_ARGS_NUM_ASSERT_I(less, req, all) \ - PLUGIN_API_ARGS_NUM_ASSERT_II(AN_ASSERT_COND(less, req, all), AN_ASSERT_MSG(less, req, all)) -#define PLUGIN_API_ARGS_NUM_ASSERT_II(cond, msg) \ - FC_ASSERT(cond, msg, ("n", n_args)); + PLUGIN_API_ARGS_NUM_ASSERT_II(AN_ASSERT_COND(less, req, all), AN_ASSERT_MSG(less, req, all), AN_ASSERT_VARS(less, req, all)) +#define PLUGIN_API_ARGS_NUM_ASSERT_II(cond, msg, vars) \ + GOLOS_ASSERT(cond, golos::invalid_arguments_count, msg, ("n", n_args) vars); #define AN_ASSERT_COND(less, req, all) BOOST_PP_IF(less, n_args >= req && n_args <= all, n_args == all) #define AN_ASSERT_MSG(LESS, REQ, ALL) \ "Expected " BOOST_PP_STRINGIZE( BOOST_PP_IF(LESS, at least REQ and up to ALL, REQ) ) \ " parameter" PLURAL_FINAL(ALL) ", received ${n}" +#define AN_ASSERT_VARS(less, req, all) BOOST_PP_IF(less, ("min",req)("max",all), ("required",all)) #define PLURAL_FINAL(n) BOOST_PP_IF(BOOST_PP_LESS(n, 2), "", "s") #define PLUGIN_API_LOAD_AND_CHECK_ARGS(ARGS, n_req) BOOST_PP_SEQ_FOR_EACH_I(LOAD_AND_CHECK_ARG, n_req, ARGS) #define LOAD_AND_CHECK_ARG(r, n_req, i, arg) LOAD_AND_CHECK_ARG_I(arg, i, BOOST_PP_LESS(i, n_req)) #define LOAD_AND_CHECK_ARG_I(arg, i, req) BOOST_PP_IF(req, LOAD_ARG, LOAD_OPTIONAL_ARG)(arg, i) -#define LOAD_ARG(arg, i) ARG_NAME(arg) = args.args->at(i).as(); +#define LOAD_ARG(arg, i) ARG_NAME(arg) = GOLOS_CONVERT_PARAM(ARG_NAME(arg), args.args->at(i), ARG_TYPE(arg)); #define LOAD_OPTIONAL_ARG(arg, i) \ if (n_args > i) { \ LOAD_ARG(arg, i) \ diff --git a/plugins/json_rpc/include/golos/plugins/json_rpc/plugin.hpp b/plugins/json_rpc/include/golos/plugins/json_rpc/plugin.hpp index 6c371cb485..994be50c5c 100644 --- a/plugins/json_rpc/include/golos/plugins/json_rpc/plugin.hpp +++ b/plugins/json_rpc/include/golos/plugins/json_rpc/plugin.hpp @@ -48,10 +48,14 @@ #define JSON_RPC_METHOD_NOT_FOUND (-32601) #define JSON_RPC_INVALID_PARAMS (-32602) #define JSON_RPC_INTERNAL_ERROR (-32603) -#define JSON_RPC_SERVER_ERROR (-32000) -#define JSON_RPC_NO_PARAMS (-32001) -#define JSON_RPC_PARSE_PARAMS_ERROR (-32002) -#define JSON_RPC_ERROR_DURING_CALL (-32003) + +#define SERVER_INTERNAL_ERROR (-32000) +#define SERVER_UNSUPPORTED_OPERATION (-32001) // unsupported_operation +#define SERVER_INVALID_PARAMETER (-32002) // parameter_exception (invalid_arguments_count, missing_object, object_already_exist) +#define SERVER_BUSINESS_LOGIC_ERROR (-32003) // business_exception (bandwidth_exception, insufficient_funds, logic_exception) +#define SERVER_MISSING_AUTHORITY (-32004) // tx_missing_authority +#define SERVER_INVALID_OPERATION (-32005) // tx_invalid_operation (client must check inner exception) +#define SERVER_INVALID_TRANSACTION (-32006) // transaction_exception namespace golos { namespace plugins { @@ -143,4 +147,4 @@ namespace golos { } } // steem::plugins::json_rpc -FC_REFLECT((golos::plugins::json_rpc::api_method_signature), (args)(ret)) \ No newline at end of file +FC_REFLECT((golos::plugins::json_rpc::api_method_signature), (args)(ret)) diff --git a/plugins/json_rpc/include/golos/plugins/json_rpc/utility.hpp b/plugins/json_rpc/include/golos/plugins/json_rpc/utility.hpp index f65e9d4f2a..080052a9a3 100644 --- a/plugins/json_rpc/include/golos/plugins/json_rpc/utility.hpp +++ b/plugins/json_rpc/include/golos/plugins/json_rpc/utility.hpp @@ -40,9 +40,8 @@ BOOST_PP_CAT( method, _return ) method( BOOST_PP_CAT( method, _args )& ); #define DEFINE_API(class, api_name) \ api_name ## _return class :: api_name ( api_name ## _args& args ) -namespace golos { - namespace plugins { - namespace json_rpc { +namespace golos { namespace plugins { namespace json_rpc { + class msg_pack final { public: fc::variant id; @@ -80,12 +79,8 @@ namespace golos { // Pass error to remote connection void error(int32_t code, std::string message, fc::optional data = fc::optional()); - void error(std::string message, fc::optional data = fc::optional()); - void error(int32_t code, const fc::exception &); - void error(const fc::exception &); - fc::optional error() const; private: @@ -128,8 +123,6 @@ namespace golos { struct void_type { }; - } - } -} // golos::plugins::json_rpc +} } } // golos::plugins::json_rpc FC_REFLECT((golos::plugins::json_rpc::void_type),) diff --git a/plugins/json_rpc/plugin.cpp b/plugins/json_rpc/plugin.cpp index 591522fde5..5f94414940 100644 --- a/plugins/json_rpc/plugin.cpp +++ b/plugins/json_rpc/plugin.cpp @@ -1,6 +1,8 @@ #include #include +#include + #include #include @@ -123,13 +125,6 @@ namespace golos { } } - void msg_pack::error(std::string message, fc::optional data) { - error(JSON_RPC_SERVER_ERROR, std::move(message), std::move(data)); - } - - void msg_pack::error(const fc::exception &e) { - error(JSON_RPC_SERVER_ERROR, e); - } void msg_pack::error(int32_t code, const fc::exception &e) { error(code, e.to_string(), fc::variant(*(e.dynamic_copy_exception()))); @@ -166,92 +161,114 @@ namespace golos { _methods.push_back(canonical_name.str()); } - api_method *find_api_method(std::string api, std::string method) { + api_method *find_api_method(std::string api, std::string method, msg_pack& msg) { auto api_itr = _registered_apis.find(api); - FC_ASSERT(api_itr != _registered_apis.end(), "Could not find API ${api}", ("api", api)); + if (api_itr == _registered_apis.end()) { + msg.error(JSON_RPC_METHOD_NOT_FOUND, "Could not find API ${api}", + fc::mutable_variant_object()("api", api)); + return nullptr; + } auto method_itr = api_itr->second.find(method); - FC_ASSERT(method_itr != api_itr->second.end(), "Could not find method ${method}", - ("method", method)); + if (method_itr == api_itr->second.end()) { + msg.error(JSON_RPC_METHOD_NOT_FOUND, "Could not find method ${method}", + fc::mutable_variant_object()("method", method)); + return nullptr; + } return &(method_itr->second); } - api_method *process_params(string method, const fc::variant_object &request, msg_pack &func_args) { + api_method *process_params(const fc::variant_object &request, msg_pack &func_args) { api_method *ret = nullptr; - if (method == "call") { - FC_ASSERT(request.contains("params")); + if (!request.contains("params")) { + func_args.error(JSON_RPC_INVALID_REQUEST, "A member \"params\" does not exist"); + return nullptr; + } - std::vector v; + std::vector v; - if (request["params"].is_array()) { - v = request["params"].as >(); - } + if (request["params"].is_array()) { + v = request["params"].as >(); + } - FC_ASSERT(v.size() == 2 || v.size() == 3, "params should be {\"api\", \"method\", \"args\""); + if (v.size() < 2 || v.size() > 3) { + func_args.error(JSON_RPC_INVALID_REQUEST, "A member \"params\" should be [\"api\", \"method\", \"args\"]"); + return nullptr; + } - ret = find_api_method(v[0].as_string(), v[1].as_string()); - func_args.plugin = v[0].as_string(); - func_args.method = v[1].as_string(); - fc::variant tmp = (v.size() == 3) ? v[2] : fc::json::from_string("{}"); - func_args.args = tmp.as>(); - } else { - vector v; - boost::split(v, method, boost::is_any_of(".")); + if (nullptr == (ret = find_api_method(v[0].as_string(), v[1].as_string(), func_args))) { + return nullptr; + } - FC_ASSERT(v.size() == 2, "method specification invalid. Should be api.method"); + func_args.plugin = v[0].as_string(); + func_args.method = v[1].as_string(); + fc::variant tmp = (v.size() == 3) ? v[2] : fc::json::from_string("[]"); - ret = find_api_method(v[0], v[1]); - func_args.plugin = v[0]; - func_args.method = v[1]; - fc::variant tmp = (v.size() == 3) ? v[2] : fc::json::from_string("{}"); + try { func_args.args = tmp.as>(); + } catch (const fc::bad_cast_exception& e) { + func_args.error(JSON_RPC_INVALID_REQUEST, "A member \"args\" should be array", static_cast(e)); + return nullptr; } return ret; } - void rpc_jsonrpc(const fc::variant_object &request, msg_pack &msg) { - // TODO: id is optional value or not? - if (request.contains("id")) { - msg.rpc_id(request["id"]); - } + void rpc_jsonrpc(const fc::variant &data, msg_pack &msg) { + fc::variant_object request; - if (!request.contains("jsonrpc") || request["jsonrpc"].as_string() != "2.0") { - return msg.error(JSON_RPC_INVALID_REQUEST, "jsonrpc value is not \"2.0\""); - } else if (!request.contains("method")) { - return msg.error(JSON_RPC_INVALID_REQUEST, "A member \"method\" does not exist"); - } + try { + request = data.get_object(); - string method; + // TODO: id is optional value or not? + if (request.contains("id")) { + msg.rpc_id(request["id"]); + } - try { - method = request["method"].as_string(); - } catch (const fc::assert_exception &e) { - return msg.error(JSON_RPC_METHOD_NOT_FOUND, e); + if (!request.contains("jsonrpc") || request["jsonrpc"].as_string() != "2.0") { + return msg.error(JSON_RPC_INVALID_REQUEST, "jsonrpc value is not \"2.0\""); + } + + if (!request.contains("method") || request["method"].as_string() != "call") { + return msg.error(JSON_RPC_INVALID_REQUEST, "A member \"method\" is not \"call\""); + } + } catch (const fc::bad_cast_exception& e) { + return msg.error(JSON_RPC_INVALID_REQUEST, "Invalid request structure", static_cast(e)); } - // This is to maintain backwards compatibility with existing call structure. - if ((method == "call" && request.contains("params")) || method != "call") { - api_method *call = nullptr; - try { - call = process_params(method, request, msg); - } catch (const fc::assert_exception &e) { - return msg.error(JSON_RPC_PARSE_PARAMS_ERROR, e); - } + api_method *call = process_params(request, msg); + if (call == nullptr) { + return; + } - try { - auto result = (*call)(msg); - if (msg.valid()) { - msg.result(std::move(result)); - } - } catch (const fc::assert_exception &e) { - return msg.error(JSON_RPC_ERROR_DURING_CALL, e); + try { + auto result = (*call)(msg); + if (msg.valid()) { + msg.result(std::move(result)); } - } else { - return msg.error(JSON_RPC_NO_PARAMS, "A member \"params\" does not exist"); + } catch (const golos::unsupported_operation& e) { + msg.error(SERVER_UNSUPPORTED_OPERATION, e); + + } catch (const golos::parameter_exception& e) { + msg.error(SERVER_INVALID_PARAMETER, e); + + } catch (const golos::business_exception& e) { + msg.error(SERVER_BUSINESS_LOGIC_ERROR, e); + + } catch (const golos::protocol::tx_missing_authority& e) { + msg.error(SERVER_MISSING_AUTHORITY, e); + + } catch (const golos::protocol::tx_invalid_operation& e) { + msg.error(SERVER_INVALID_OPERATION, e); + + } catch (const golos::protocol::transaction_exception& e) { + msg.error(SERVER_INVALID_TRANSACTION, e); + + } catch (const golos::golos_exception& e) { + msg.error(SERVER_INTERNAL_ERROR, e); } } @@ -291,21 +308,16 @@ namespace golos { dump_rpc_time dump(data); try { - rpc_jsonrpc(data.get_object(), msg); - } catch (const fc::parse_error_exception& e) { - msg.error(JSON_RPC_INVALID_PARAMS, e); - dump.error("invalid params"); - } catch (const fc::bad_cast_exception& e) { - msg.error(JSON_RPC_INVALID_PARAMS, e); - dump.error("invalid types"); + rpc_jsonrpc(data, msg); + } catch (const fc::exception& e) { - msg.error(e); + msg.error(JSON_RPC_INTERNAL_ERROR, std::string("Internal error: ") + e.to_string(), e); dump.error("invalid request"); } catch (const std::exception& e) { - msg.error(e.what()); + msg.error(JSON_RPC_INTERNAL_ERROR, std::string("Internal error: ") + e.what()); dump.error(e.what()); } catch (...) { - msg.error("Unknown error - parsing rpc message failed"); + msg.error(JSON_RPC_INTERNAL_ERROR, "Unknown error - parsing rpc message failed"); dump.error("unknown"); } } @@ -335,6 +347,41 @@ namespace golos { next_handler(); } + void call(const string &message, response_handler_type response_handler) { + auto send_error = [response_handler](int32_t code, const std::string& msg, fc::optional d = fc::optional()) { + json_rpc_response response; + response.error = json_rpc_error(code, msg, d); + response_handler(fc::json::to_string(response)); + }; + + try { + fc::variant v; + + try { + v = fc::json::from_string(message); + } catch (const fc::exception& e) { + return send_error(JSON_RPC_PARSE_ERROR, "Invalid JSON-structure", e); + } + + if (v.is_array()) { + vector messages = v.as>(); + + if(messages.size() == 0) { + return send_error(JSON_RPC_INVALID_REQUEST, "Array of requests must be non-empty"); + } + rpc(messages, response_handler); + } else { + msg_pack msg([response_handler](json_rpc_response &response){ + response_handler(fc::json::to_string(response)); + }); + + rpc(v, msg); + } + } catch (const fc::exception &e) { + return send_error(JSON_RPC_INTERNAL_ERROR, e.to_string(), e); + } + } + void initialize() { } @@ -400,26 +447,7 @@ namespace golos { } void plugin::call(const string &message, response_handler_type response_handler) { - try { - fc::variant v = fc::json::from_string(message); - - if (v.is_array()) { - vector messages = v.as>(); - - FC_ASSERT(messages.size(), "Array is invalid"); - pimpl->rpc(messages, response_handler); - } else { - msg_pack msg([response_handler](json_rpc_response &response){ - response_handler(fc::json::to_string(response)); - }); - - pimpl->rpc(v, msg); - } - } catch (const fc::exception &e) { - json_rpc_response response; - response.error = json_rpc_error(JSON_RPC_SERVER_ERROR, e.to_string(), fc::variant(*(e.dynamic_copy_exception()))); - response_handler(fc::json::to_string(response)); - } + pimpl->call(message, response_handler); } } } diff --git a/plugins/market_history/market_history_plugin.cpp b/plugins/market_history/market_history_plugin.cpp index fc3e60447d..c1183dfdba 100644 --- a/plugins/market_history/market_history_plugin.cpp +++ b/plugins/market_history/market_history_plugin.cpp @@ -1,14 +1,12 @@ #include +#include #include #include #include #include - - -#define CHECK_ARG_SIZE(s) \ - FC_ASSERT( args.args->size() == s, "Expected #s argument(s), was ${n}", ("n", args.args->size()) ); +#include namespace golos { @@ -223,8 +221,6 @@ namespace golos { } order_book market_history_plugin::market_history_plugin_impl::get_order_book(uint32_t limit) const { - FC_ASSERT(limit <= 500); - const auto &order_idx = database().get_index().indices().get(); auto itr = order_idx.lower_bound(price::max(SBD_SYMBOL, STEEM_SYMBOL)); @@ -258,7 +254,6 @@ namespace golos { } order_book_extended market_history_plugin::market_history_plugin_impl::get_order_book_extended(uint32_t limit) const { - FC_ASSERT(limit <= 1000); order_book_extended result; auto max_sell = price::max(SBD_SYMBOL, STEEM_SYMBOL); @@ -301,7 +296,6 @@ namespace golos { vector market_history_plugin::market_history_plugin_impl::get_trade_history( time_point_sec start, time_point_sec end, uint32_t limit) const { - FC_ASSERT(limit <= 1000); const auto &bucket_idx = database().get_index().indices().get(); auto itr = bucket_idx.lower_bound(start); @@ -321,7 +315,6 @@ namespace golos { } vector market_history_plugin::market_history_plugin_impl::get_recent_trades(uint32_t limit) const { - FC_ASSERT(limit <= 1000); const auto &order_idx = database().get_index().indices().get(); auto itr = order_idx.rbegin(); @@ -450,6 +443,7 @@ namespace golos { // Api Defines DEFINE_API(market_history_plugin, get_ticker) { + PLUGIN_API_VALIDATE_ARGS(); auto &db = _my->database(); return db.with_weak_read_lock([&]() { return _my->get_ticker(); @@ -457,6 +451,7 @@ namespace golos { } DEFINE_API(market_history_plugin, get_volume) { + PLUGIN_API_VALIDATE_ARGS(); auto &db = _my->database(); return db.with_weak_read_lock([&]() { return _my->get_volume(); @@ -464,8 +459,11 @@ namespace golos { } DEFINE_API(market_history_plugin, get_order_book) { - CHECK_ARG_SIZE(1) - auto limit = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, limit) + ); + GOLOS_CHECK_LIMIT_PARAM(limit, 500); + auto &db = _my->database(); return db.with_weak_read_lock([&]() { return _my->get_order_book(limit); @@ -473,8 +471,11 @@ namespace golos { } DEFINE_API(market_history_plugin, get_order_book_extended) { - CHECK_ARG_SIZE(1) - auto limit = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, limit) + ); + GOLOS_CHECK_LIMIT_PARAM(limit, 1000); + auto &db = _my->database(); return db.with_weak_read_lock([&]() { return _my->get_order_book_extended(limit); @@ -483,10 +484,13 @@ namespace golos { DEFINE_API(market_history_plugin, get_trade_history) { - CHECK_ARG_SIZE(3) - auto start = args.args->at(0).as(); - auto end = args.args->at(1).as(); - auto limit = args.args->at(2).as(); + PLUGIN_API_VALIDATE_ARGS( + (time_point_sec, start) + (time_point_sec, end) + (uint32_t, limit) + ); + GOLOS_CHECK_LIMIT_PARAM(limit, 1000); + auto &db = _my->database(); return db.with_weak_read_lock([&]() { return _my->get_trade_history(start, end, limit); @@ -494,8 +498,11 @@ namespace golos { } DEFINE_API(market_history_plugin, get_recent_trades) { - CHECK_ARG_SIZE(1) - auto limit = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, limit) + ); + GOLOS_CHECK_LIMIT_PARAM(limit, 1000); + auto &db = _my->database(); return db.with_weak_read_lock([&]() { return _my->get_recent_trades(limit); @@ -503,10 +510,11 @@ namespace golos { } DEFINE_API(market_history_plugin, get_market_history) { - CHECK_ARG_SIZE(3) - auto bucket_seconds = args.args->at(0).as(); - auto start = args.args->at(1).as(); - auto end = args.args->at(2).as(); + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, bucket_seconds) + (time_point_sec, start) + (time_point_sec, end) + ); auto &db = _my->database(); return db.with_weak_read_lock([&]() { return _my->get_market_history(bucket_seconds, start, end); @@ -514,6 +522,7 @@ namespace golos { } DEFINE_API(market_history_plugin, get_market_history_buckets) { + PLUGIN_API_VALIDATE_ARGS(); auto &db = _my->database(); return db.with_weak_read_lock([&]() { return _my->get_market_history_buckets(); @@ -521,10 +530,12 @@ namespace golos { } DEFINE_API(market_history_plugin, get_open_orders) { - auto tmp = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, account) + ); auto &db = _my->database(); return db.with_weak_read_lock([&]() { - return _my->get_open_orders(tmp); + return _my->get_open_orders(account); }); } diff --git a/plugins/mongo_db/CMakeLists.txt b/plugins/mongo_db/CMakeLists.txt index a74b1af60e..85a2e820e4 100644 --- a/plugins/mongo_db/CMakeLists.txt +++ b/plugins/mongo_db/CMakeLists.txt @@ -60,6 +60,7 @@ if(ENABLE_MONGO_PLUGIN) golos_chain golos::chain_plugin golos::follow + golos::social_network appbase fc ${LIBBSONCXX_LIBRARIES} diff --git a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_operations.hpp b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_operations.hpp index cb91c15420..c24d9c5af2 100644 --- a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_operations.hpp +++ b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_operations.hpp @@ -74,6 +74,7 @@ namespace mongo_db { result_type operator()(const shutdown_witness_operation& op); result_type operator()(const fill_transfer_from_savings_operation& op); result_type operator()(const hardfork_operation& op); + result_type operator()(const producer_reward_operation& op); result_type operator()(const comment_payout_update_operation& op); result_type operator()(const comment_benefactor_reward_operation& op); // diff --git a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_state.hpp b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_state.hpp index 32228e6ba3..987814cd1b 100644 --- a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_state.hpp +++ b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_state.hpp @@ -78,16 +78,15 @@ namespace mongo_db { result_type operator()(const shutdown_witness_operation& op); result_type operator()(const fill_transfer_from_savings_operation& op); result_type operator()(const hardfork_operation& op); + result_type operator()(const producer_reward_operation& op); result_type operator()(const comment_payout_update_operation& op); result_type operator()(const comment_benefactor_reward_operation& op); result_type operator()(const return_vesting_delegation_operation& op); result_type operator()(const chain_properties_update_operation& op); - void write_global_property_object(const dynamic_global_property_object& dgpo, - const signed_block& current_block, bool history); + void write_global_property_object(const dynamic_global_property_object& dgpo, bool history); - void write_witness_schedule_object(const witness_schedule_object& wso, - const signed_block& current_block, bool history); + void write_witness_schedule_object(const witness_schedule_object& wso, bool history); private: database &db_; @@ -128,6 +127,11 @@ namespace mongo_db { void format_required_approval(const required_approval_object& reqapp, const account_name_type& proposal_author, const std::string& proposal_title); + void format_liquidity_reward_balance(const liquidity_reward_balance_object& lrbo, + const account_name_type& owner); + + void format_liquidity_reward_balance(const account_name_type& owner); + named_document create_document(const std::string& name, const std::string& key, const std::string& keyval); diff --git a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_types.hpp b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_types.hpp index 4ffc39451b..ba1d6f10ce 100644 --- a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_types.hpp +++ b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_types.hpp @@ -74,8 +74,6 @@ namespace mongo_db { void bmi_insert_or_replace(db_map& bmi, named_document doc); - using named_document_ptr = std::unique_ptr; - inline std::string hex_md5(const std::string& input) { uint8_t digest[16]; @@ -92,7 +90,7 @@ namespace mongo_db { } digest_str[sizeof digest_str - 1] = '\0'; - return std::string(bson_strdup (digest_str)); + return std::string(digest_str); } inline std::string hash_oid(const std::string& value) { @@ -136,13 +134,25 @@ namespace mongo_db { } inline void format_value(document& doc, const std::string& name, const fc::time_point_sec& value) { - doc << name << value.to_iso_string(); + doc << name + << bsoncxx::types::b_date{ + std::chrono::milliseconds( + std::chrono::seconds(value.sec_since_epoch()))}; } inline void format_value(document& doc, const std::string& name, const shared_string& value) { doc << name << to_string(value); } + inline void format_json(document& doc, const std::string& name, const shared_string& value) { + auto str_value = to_string(value); + try { + doc << name << bsoncxx::from_json(str_value); + } catch (...) { + doc << name << str_value; + } + } + template inline void format_value(document& doc, const std::string& name, const fc::fixed_string& value) { doc << name << static_cast(value); diff --git a/plugins/mongo_db/mongo_db_operations.cpp b/plugins/mongo_db/mongo_db_operations.cpp index 4e4bd6a324..f65ace3285 100644 --- a/plugins/mongo_db/mongo_db_operations.cpp +++ b/plugins/mongo_db/mongo_db_operations.cpp @@ -694,6 +694,11 @@ namespace mongo_db { return body; } + auto operation_writer::operator()(const producer_reward_operation& op) -> result_type { + result_type body; + return body; + } + auto operation_writer::operator()(const comment_payout_update_operation& op) -> result_type { result_type body; diff --git a/plugins/mongo_db/mongo_db_plugin.cpp b/plugins/mongo_db/mongo_db_plugin.cpp index 3aca0827ed..80bed5b068 100644 --- a/plugins/mongo_db/mongo_db_plugin.cpp +++ b/plugins/mongo_db/mongo_db_plugin.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -61,16 +60,16 @@ namespace mongo_db { boost::program_options::value()->default_value("mongodb://127.0.0.1:27017/Golos"), "Mongo DB connection string") ("mongodb-write-raw-blocks", - boost::program_options::value()->default_value(true), + boost::program_options::value()->default_value(false), "Write raw blocks into mongo or not") ("mongodb-write-operations", boost::program_options::value>()->multitoken()->zero_tokens()->composing(), "List of operations to write into mongo") ("mongodb-store-dgp-history", - boost::program_options::value()->default_value(10), + boost::program_options::value()->default_value(100), "Mode of storing global_property_object history for each N block") ("mongodb-store-wso-history", - boost::program_options::value()->default_value(10), + boost::program_options::value()->default_value(100), "Mode of storing witness_schedule_object history for each N block"); cfg.add(cli); } diff --git a/plugins/mongo_db/mongo_db_state.cpp b/plugins/mongo_db/mongo_db_state.cpp index 7578bb5242..d6312acb56 100644 --- a/plugins/mongo_db/mongo_db_state.cpp +++ b/plugins/mongo_db/mongo_db_state.cpp @@ -1,11 +1,10 @@ #include -#include -#include #include #include #include #include #include +#include #include #include @@ -22,7 +21,6 @@ namespace mongo_db { using bsoncxx::builder::stream::document; using bsoncxx::builder::stream::open_document; using bsoncxx::builder::stream::close_document; - using namespace golos::plugins::follow; using golos::chain::by_account; using golos::chain::decline_voting_rights_request_index; @@ -68,25 +66,22 @@ namespace mongo_db { format_value(body, "removed", false); + format_value(body, "block_num", state_block.block_num()); + format_value(body, "author", auth); format_value(body, "permlink", perm); format_value(body, "abs_rshares", comment.abs_rshares); - format_value(body, "active", comment.active); format_value(body, "allow_curation_rewards", comment.allow_curation_rewards); format_value(body, "allow_replies", comment.allow_replies); format_value(body, "allow_votes", comment.allow_votes); - format_value(body, "author_rewards", comment.author_rewards); - format_value(body, "beneficiary_payout", comment.beneficiary_payout_value); format_value(body, "cashout_time", comment.cashout_time); format_value(body, "children", comment.children); format_value(body, "children_abs_rshares", comment.children_abs_rshares); format_value(body, "children_rshares2", comment.children_rshares2); format_value(body, "created", comment.created); - format_value(body, "curator_payout", comment.curator_payout_value); format_value(body, "depth", comment.depth); format_value(body, "last_payout", comment.last_payout); - format_value(body, "last_update", comment.last_update); format_value(body, "max_accepted_payout", comment.max_accepted_payout); format_value(body, "max_cashout_time", comment.max_cashout_time); format_value(body, "net_rshares", comment.net_rshares); @@ -95,7 +90,6 @@ namespace mongo_db { format_value(body, "parent_permlink", comment.parent_permlink); format_value(body, "percent_steem_dollars", comment.percent_steem_dollars); format_value(body, "reward_weight", comment.reward_weight); - format_value(body, "total_payout", comment.total_payout_value); format_value(body, "total_vote_weight", comment.total_vote_weight); format_value(body, "vote_rshares", comment.vote_rshares); @@ -128,11 +122,50 @@ namespace mongo_db { format_value(body, "mode", comment_mode); - auto& content = db_.get_comment_content(comment_id_type(comment.id)); + if (db_.has_index()) { + const auto& con_idx = db_.get_index().indices().get(); + auto con_itr = con_idx.find(comment.id); + if (con_itr != con_idx.end()) { + format_value(body, "title", con_itr->title); + format_value(body, "body", con_itr->body); + format_json(body, "json_metadata", con_itr->json_metadata); + } + } - format_value(body, "title", content.title); - format_value(body, "body", content.body); - format_value(body, "json_metadata", content.json_metadata); + if (db_.has_index()) { + const auto& clu_idx = db_.get_index().indices().get(); + auto clu_itr = clu_idx.find(comment.id); + if (clu_itr != clu_idx.end()) { + format_value(body, "active", clu_itr->active); + format_value(body, "last_update", clu_itr->last_update); + } + } + + if (db_.has_index()) { + const auto& cr_idx = db_.get_index().indices().get(); + auto cr_itr = cr_idx.find(comment.id); + if (cr_itr != cr_idx.end()) { + format_value(body, "author_rewards", cr_itr->author_rewards); + format_value(body, "author_gbg_payout", cr_itr->author_gbg_payout_value); + format_value(body, "author_golos_payout", cr_itr->author_golos_payout_value); + format_value(body, "author_gests_payout", cr_itr->author_gests_payout_value); + format_value(body, "beneficiary_payout", cr_itr->beneficiary_payout_value); + format_value(body, "beneficiary_gests_payout", cr_itr->beneficiary_gests_payout_value); + format_value(body, "curator_payout", cr_itr->curator_payout_value); + format_value(body, "curator_gests_payout", cr_itr->curator_gests_payout_value); + format_value(body, "total_payout", cr_itr->total_payout_value); + } else { + format_value(body, "author_rewards", 0); + format_value(body, "author_gbg_payout", asset(0, SBD_SYMBOL)); + format_value(body, "author_golos_payout", asset(0, STEEM_SYMBOL)); + format_value(body, "author_gests_payout", asset(0, VESTS_SYMBOL)); + format_value(body, "beneficiary_payout", asset(0, SBD_SYMBOL)); + format_value(body, "beneficiary_gests_payout", asset(0, VESTS_SYMBOL)); + format_value(body, "curator_payout", asset(0, SBD_SYMBOL)); + format_value(body, "curator_gests_payout", asset(0, VESTS_SYMBOL)); + format_value(body, "total_payout", asset(0, SBD_SYMBOL)); + } + } std::string category, root_oid; if (comment.parent_author == STEEMIT_ROOT_POST_PARENT) { @@ -239,11 +272,10 @@ namespace mongo_db { format_value(body, "last_post", account.last_post); -#ifndef IS_LOW_MEM - auto& account_metadata = db_.get(account.name); - - format_value(body, "json_metadata", account_metadata.json_metadata); -#endif + auto account_metadata = db_.find(account.name); + if (account_metadata != nullptr) { + format_json(body, "json_metadata", account_metadata->json_metadata); + } body << close_document; @@ -329,15 +361,15 @@ namespace mongo_db { std::string band_type; switch (type) { - case post: - band_type = "post"; - break; - case forum: - band_type = "forum"; - break; - case market: - band_type = "market"; - break; + case post: + band_type = "post"; + break; + case forum: + band_type = "forum"; + break; + case market: + band_type = "market"; + break; } auto oid = std::string(band->account).append("/").append(band_type); @@ -443,6 +475,7 @@ namespace mongo_db { format_value(body, "delegatee", delegation.delegatee); format_value(body, "vesting_shares", delegation.vesting_shares); format_value(body, "min_delegation_time", delegation.min_delegation_time); + format_value(body, "timestamp", state_block.timestamp); body << close_document; @@ -505,6 +538,7 @@ namespace mongo_db { format_value(body, "to_approved", escrow.to_approved); format_value(body, "agent_approved", escrow.agent_approved); format_value(body, "disputed", escrow.disputed); + format_value(body, "timestamp", state_block.timestamp); body << close_document; @@ -551,6 +585,7 @@ namespace mongo_db { format_value(body, "title", proposal.title); format_value(body, "memo", proposal.memo); + format_value(body, "timestamp", state_block.timestamp); if (proposal.review_period_time.valid()) { format_value(body, "review_period_time", *(proposal.review_period_time)); @@ -558,8 +593,9 @@ namespace mongo_db { if (!proposal.proposed_operations.empty()) { array ops_array; - for (auto& op: proposal.proposed_operations) { - ops_array << op; + auto operations = proposal.operations(); + for (auto& op: operations) { + ops_array << fc::json::to_string(op); } body << "proposed_operations" << ops_array; } @@ -637,6 +673,50 @@ namespace mongo_db { } } + void state_writer::format_liquidity_reward_balance(const liquidity_reward_balance_object& lrbo, + const account_name_type& owner) { + try { + auto oid = std::string(owner); + auto oid_hash = hash_oid(oid); + + auto doc = create_document("liquidity_reward_balance_object", "_id", oid_hash); + + // We don't need any 'owner_id' because oid is owner + + auto& body = doc.doc; + + body << "$set" << open_document; + + format_oid(body, oid); + + format_value(body, "owner", owner); + format_value(body, "steem_volume", lrbo.steem_volume); + format_value(body, "sbd_volume", lrbo.sbd_volume); + //format_value(body, "weight", lrbo.weight); // weight not exported now + format_value(body, "last_update", lrbo.last_update); + + body << close_document; + + bmi_insert_or_replace(all_docs, std::move(doc)); + } catch (...) { + // + } + } + + void state_writer::format_liquidity_reward_balance(const account_name_type& owner) { + try { + auto &obj_owner = db_.get_account(owner); + const auto &ridx = db_.get_index().indices().get(); + auto itr = ridx.find(obj_owner.id); + if (ridx.end() != itr) { + format_liquidity_reward_balance(*itr, owner); + } + } + catch (...) { + // + } + } + auto state_writer::operator()(const vote_operation& op) -> result_type { format_comment(op.author, op.permlink); @@ -697,13 +777,13 @@ namespace mongo_db { auto state_writer::operator()(const delete_comment_operation& op) -> result_type { - std::string author = op.author; + std::string author = op.author; auto comment_oid = std::string(op.author).append("/").append(op.permlink); auto comment_oid_hash = hash_oid(comment_oid); // Will be updated with the following fields. If no one - created with these fields. - auto comment = create_document("comment_object", "_id", comment_oid_hash); + auto comment = create_document("comment_object", "_id", comment_oid_hash); auto& body = comment.doc; @@ -721,7 +801,7 @@ namespace mongo_db { bmi_insert_or_replace(all_docs, std::move(comment)); // Will be updated with removed = true. If no one - nothing to do. - auto comment_vote = create_removal_document("comment_vote_object", "comment", comment_oid_hash); + auto comment_vote = create_removal_document("comment_vote_object", "comment", comment_oid_hash); bmi_insert_or_replace(all_docs, std::move(comment_vote)); } @@ -734,6 +814,7 @@ namespace mongo_db { format_value(body, "to", op.to); format_value(body, "amount", op.amount); format_value(body, "memo", op.memo); + format_value(body, "timestamp", state_block.timestamp); std::vector part; auto path = op.memo; @@ -757,10 +838,53 @@ namespace mongo_db { auto state_writer::operator()(const transfer_to_vesting_operation& op) -> result_type { format_account(op.from); format_account(op.to); + try { + const auto &dgp = db_.get_dynamic_global_properties(); + + auto doc = create_document("transfer_to_vesting", "", ""); + auto& body = doc.doc; + + document from_index; + from_index << "from_id" << 1; + doc.indexes_to_create.push_back(std::move(from_index)); + + document to_index; + to_index << "to_id" << 1; + doc.indexes_to_create.push_back(std::move(to_index)); + + format_value(body, "from", op.from); + format_oid(body, "from_id", op.from); + format_value(body, "to", op.to); + format_oid(body, "to_id", op.to); + format_value(body, "amount", op.amount); + format_value(body, "timestamp", state_block.timestamp); + + asset amount_gests; + amount_gests.amount = op.amount.amount / + (dgp.total_reward_fund_steem.amount / dgp.total_vesting_shares.amount); + amount_gests.symbol = VESTS_SYMBOL; + + format_value(body, "amount_gests", amount_gests); + + all_docs.push_back(std::move(doc)); + } + catch (...) { + // + } } auto state_writer::operator()(const withdraw_vesting_operation& op) -> result_type { + auto doc = create_document("withdraw_vesting", "", ""); + + auto& body = doc.doc; + + format_value(body, "account", op.account); + format_value(body, "vesting_shares", op.vesting_shares); + format_value(body, "timestamp", state_block.timestamp); + format_account(op.account); + + all_docs.push_back(std::move(doc)); } auto state_writer::operator()(const limit_order_create_operation& op) -> result_type { @@ -784,6 +908,7 @@ namespace mongo_db { format_value(body, "orderid", loo.orderid); format_value(body, "for_sale", loo.for_sale); format_value(body, "sell_price", loo.sell_price); + format_value(body, "timestamp", state_block.timestamp); body << close_document; @@ -820,6 +945,7 @@ namespace mongo_db { format_oid(body, oid); + format_value(body, "filled", false); format_value(body, "owner", std::string(op.owner)); format_value(body, "requestid", op.requestid); format_value(body, "amount", op.amount); @@ -969,10 +1095,12 @@ namespace mongo_db { format_oid(body, oid); + format_value(body, "filled", false); format_value(body, "from_account", std::string(from_account.name)); format_value(body, "to_account", std::string(to_account.name)); format_value(body, "percent", wvro.percent); format_value(body, "auto_vest", wvro.auto_vest); + format_value(body, "timestamp", state_block.timestamp); body << close_document; @@ -1005,6 +1133,7 @@ namespace mongo_db { format_value(body, "orderid", loo.orderid); format_value(body, "for_sale", loo.for_sale); format_value(body, "sell_price", loo.sell_price); + format_value(body, "timestamp", state_block.timestamp); body << close_document; @@ -1147,6 +1276,28 @@ namespace mongo_db { auto state_writer::operator()(const transfer_to_savings_operation& op) -> result_type { format_account(op.from); format_account(op.to); + + auto doc = create_document("transfer_to_savings", "", ""); + + document from_index; + from_index << "from_id" << 1; + doc.indexes_to_create.push_back(std::move(from_index)); + + document to_index; + to_index << "to_id" << 1; + doc.indexes_to_create.push_back(std::move(to_index)); + + auto& body = doc.doc; + + format_value(body, "from", op.from); + format_oid(body, "from_id", op.from); + format_value(body, "to", op.to); + format_oid(body, "to_id", op.to); + format_value(body, "amount", op.amount); + format_value(body, "memo", op.memo); + format_value(body, "timestamp", state_block.timestamp); + + all_docs.push_back(std::move(doc)); } auto state_writer::operator()(const transfer_from_savings_operation& op) -> result_type { @@ -1169,12 +1320,13 @@ namespace mongo_db { format_value(body, "removed", false); format_value(body, "from", op.from); format_value(body, "to", op.to); -#ifndef IS_LOW_MEM - format_value(body, "memo", op.memo); -#endif + if (db_.store_memo_in_savings_withdraws()) { + format_value(body, "memo", op.memo); + } format_value(body, "request_id", swo.request_id); format_value(body, "amount", op.amount); format_value(body, "complete", swo.complete); + format_value(body, "timestamp", state_block.timestamp); body << close_document; @@ -1313,23 +1465,126 @@ namespace mongo_db { } auto state_writer::operator()(const fill_convert_request_operation& op) -> result_type { - + try { + format_account(op.owner); + + auto oid = std::string(op.owner).append("/").append(std::to_string(op.requestid)); + auto oid_hash = hash_oid(oid); + + auto cro = create_document("convert_request_object", "_id", oid_hash); + + auto& body = cro.doc; + + body << "$set" << open_document; + + format_oid(body, oid); + + format_value(body, "filled", true); + format_value(body, "amount_out", op.amount_out); + format_value(body, "timestamp", state_block.timestamp); + + body << close_document; + + bmi_insert_or_replace(all_docs, std::move(cro)); + } + catch (...) { + // + } } auto state_writer::operator()(const liquidity_reward_operation& op) -> result_type { - + try { + auto &owner = db_.get_account(op.owner); + const auto &ridx = db_.get_index().indices().get(); + auto itr = ridx.find(owner.id); + if (ridx.end() != itr) { + format_liquidity_reward_balance(*itr, op.owner); + } + } + catch (...) { + // + } } auto state_writer::operator()(const interest_operation& op) -> result_type { - + try { + auto doc = create_document("interest", "", ""); + auto& body = doc.doc; + + document owner_index; + owner_index << "owner_id" << 1; + doc.indexes_to_create.push_back(std::move(owner_index)); + + format_oid(body, "owner_id", op.owner); + format_value(body, "owner", op.owner); + format_value(body, "interest", op.interest); + format_value(body, "timestamp", state_block.timestamp); + + all_docs.push_back(std::move(doc)); + } + catch (...) { + // + } } auto state_writer::operator()(const fill_vesting_withdraw_operation& op) -> result_type { - + try { + format_account(op.from_account); + format_account(op.to_account); + auto oid = op.from_account + "/" + op.to_account; + auto oid_hash = hash_oid(oid); + + auto doc = create_document("withdraw_vesting_route_object", "_id", oid_hash); + + auto &body = doc.doc; + + body << "$set" << open_document; + + format_oid(body, oid); + + format_value(body, "filled", true); + format_value(body, "withdrawn", op.withdrawn); + format_value(body, "deposited", op.deposited); + format_value(body, "timestamp", state_block.timestamp); + + body << close_document; + + bmi_insert_or_replace(all_docs, std::move(doc)); + } + catch (...) { + // + } } auto state_writer::operator()(const fill_order_operation& op) -> result_type { - + format_liquidity_reward_balance(op.current_owner); + format_liquidity_reward_balance(op.open_owner); + try { + auto doc = create_document("fill_order_id", "", ""); + auto& body = doc.doc; + + document current_owner_index; + current_owner_index << "current_owner" << 1; + doc.indexes_to_create.push_back(std::move(current_owner_index)); + + document open_owner_index; + open_owner_index << "open_owner_id" << 1; + doc.indexes_to_create.push_back(std::move(open_owner_index)); + + format_value(body, "current_owner", op.current_owner); + format_oid(body, "current_owner_id", op.current_owner); + format_value(body, "current_orderid", op.current_orderid); + format_value(body, "current_pays", op.current_pays); + format_value(body, "open_owner", op.open_owner); + format_oid(body, "open_owner_id", op.open_owner); + format_value(body, "open_orderid", op.open_orderid); + format_value(body, "open_pays", op.open_pays); + + all_docs.push_back(std::move(doc)); + } + catch (...) { + // + } } auto state_writer::operator()(const shutdown_witness_operation& op) -> result_type { @@ -1337,13 +1592,28 @@ namespace mongo_db { } auto state_writer::operator()(const fill_transfer_from_savings_operation& op) -> result_type { - + try { + format_account(op.from); + format_account(op.to); + db_.get_savings_withdraw(op.from, op.request_id); + } catch (...) { + auto oid = std::string(op.from).append("/").append(std::to_string(op.request_id)); + auto oid_hash = hash_oid(oid); + + auto doc = create_removal_document("savings_withdraw_object", "_id", oid_hash); + + bmi_insert_or_replace(all_docs, std::move(doc)); + } } auto state_writer::operator()(const hardfork_operation& op) -> result_type { } + auto state_writer::operator()(const producer_reward_operation& op) -> result_type { + + } + auto state_writer::operator()(const comment_payout_update_operation& op) -> result_type { format_comment(op.author, op.permlink); } @@ -1481,12 +1751,11 @@ namespace mongo_db { } } - void state_writer::write_global_property_object(const dynamic_global_property_object& dgpo, - const signed_block& current_block, bool history) { + void state_writer::write_global_property_object(const dynamic_global_property_object& dgpo, bool history) { try { std::string oid; if (history) { - oid = current_block.timestamp; + oid = state_block.timestamp; } else { oid = MONGO_ID_SINGLE; } @@ -1503,7 +1772,7 @@ namespace mongo_db { format_oid(body, oid); if (history) { - format_value(body, "timestamp", current_block.timestamp); + format_value(body, "timestamp", state_block.timestamp); } format_value(body, "head_block_number", dgpo.head_block_number); format_value(body, "head_block_id", dgpo.head_block_id.str()); @@ -1556,12 +1825,11 @@ namespace mongo_db { } } - void state_writer::write_witness_schedule_object(const witness_schedule_object& wso, - const signed_block& current_block, bool history) { + void state_writer::write_witness_schedule_object(const witness_schedule_object& wso, bool history) { try { std::string oid; if (history) { - oid = current_block.timestamp; + oid = state_block.timestamp; } else { oid = MONGO_ID_SINGLE; } @@ -1578,7 +1846,7 @@ namespace mongo_db { format_oid(body, oid); if (history) { - format_value(body, "timestamp", current_block.timestamp); + format_value(body, "timestamp", state_block.timestamp); } format_value(body, "current_virtual_time", wso.current_virtual_time); format_value(body, "next_shuffle_block_num", wso.next_shuffle_block_num); diff --git a/plugins/mongo_db/mongo_db_types.cpp b/plugins/mongo_db/mongo_db_types.cpp index 1d08a1c49f..9750f5ef7d 100644 --- a/plugins/mongo_db/mongo_db_types.cpp +++ b/plugins/mongo_db/mongo_db_types.cpp @@ -5,12 +5,14 @@ namespace plugins { namespace mongo_db { void bmi_insert_or_replace(db_map& bmi, named_document doc) { - auto it = bmi.get().find(std::make_tuple( + auto& idx = bmi.get(); + auto it = idx.find(std::make_tuple( doc.collection_name, doc.key, doc.keyval, doc.is_removal)); - if (it != bmi.get().end()) - bmi.get().erase(it); - bmi.push_back(std::move(doc)); + if (it != idx.end()) { + idx.erase(it); + } + idx.emplace(std::move(doc)); } } } diff --git a/plugins/mongo_db/mongo_db_writer.cpp b/plugins/mongo_db/mongo_db_writer.cpp index 89bb67d9be..51824fbd98 100644 --- a/plugins/mongo_db/mongo_db_writer.cpp +++ b/plugins/mongo_db/mongo_db_writer.cpp @@ -93,17 +93,17 @@ namespace mongo_db { write_raw_block(head_iter->second, virtual_ops[head_iter->first]); } - state_writer st_writer(all_docs, block); + state_writer st_writer(all_docs, head_iter->second); if (store_history_mode_dgp != 0 && (head_iter->second.block_num() % store_history_mode_dgp == 0)) { - st_writer.write_global_property_object(dgp_s[head_iter->first], head_iter->second, true); + st_writer.write_global_property_object(dgp_s[head_iter->first], true); } - st_writer.write_global_property_object(dgp_s[head_iter->first], head_iter->second, false); + st_writer.write_global_property_object(dgp_s[head_iter->first], false); if (store_history_mode_wso != 0 && (head_iter->second.block_num() % store_history_mode_wso == 0)) { - st_writer.write_witness_schedule_object(wso_s[head_iter->first], head_iter->second, true); + st_writer.write_witness_schedule_object(wso_s[head_iter->first], true); } - st_writer.write_witness_schedule_object(wso_s[head_iter->first], head_iter->second, false); + st_writer.write_witness_schedule_object(wso_s[head_iter->first], false); // Parsing all transactions. st_writer writes all results to all_docs @@ -239,8 +239,7 @@ namespace mongo_db { filter << "_id" << bsoncxx::oid(named_doc.keyval); - mongocxx::model::update_one msg{filter.view(), - view}; + mongocxx::model::update_one msg{filter.view(), view}; msg.upsert(true); formatted_blocks[named_doc.collection_name]->append(msg); } diff --git a/plugins/network_broadcast_api/network_broadcast_api.cpp b/plugins/network_broadcast_api/network_broadcast_api.cpp index 3d458a076c..188a513b93 100644 --- a/plugins/network_broadcast_api/network_broadcast_api.cpp +++ b/plugins/network_broadcast_api/network_broadcast_api.cpp @@ -1,11 +1,14 @@ #include #include +#include #include #include #include +#include + namespace golos { namespace plugins { namespace network_broadcast_api { @@ -39,12 +42,12 @@ namespace golos { } DEFINE_API(network_broadcast_api_plugin, broadcast_transaction) { - auto n_args = args.args->size(); - FC_ASSERT(n_args == 1, "Expected at least 1 argument, got 0"); - auto trx = args.args->at(0).as(); - if (n_args > 1) { - const auto max_block_age = args.args->at(1).as(); - FC_ASSERT(!check_max_block_age(max_block_age)); + PLUGIN_API_VALIDATE_ARGS( + (signed_transaction, trx) + (uint32_t, max_block_age, 0) + ); + if (n_args >= 2) { + GOLOS_CHECK_PARAM(max_block_age, GOLOS_CHECK_VALUE(!check_max_block_age(max_block_age), "Invalid value")); } pimpl->_chain.accept_transaction(trx); pimpl->_p2p.broadcast_transaction(trx); @@ -53,12 +56,12 @@ namespace golos { } DEFINE_API(network_broadcast_api_plugin, broadcast_transaction_synchronous) { - const auto n_args = args.args->size(); - FC_ASSERT(n_args >= 1, "Expected at least 1 argument, got 0"); - auto trx = args.args->at(0).as(); - if (n_args > 1) { - const auto max_block_age = args.args->at(1).as(); - FC_ASSERT(!check_max_block_age(max_block_age)); + PLUGIN_API_VALIDATE_ARGS( + (signed_transaction, trx) + (uint32_t, max_block_age, 0) + ); + if (n_args >= 2) { + GOLOS_CHECK_PARAM(max_block_age, GOLOS_CHECK_VALUE(!check_max_block_age(max_block_age), "Invalid value")); } // Delegate connection handlers to callback @@ -81,9 +84,9 @@ namespace golos { } DEFINE_API(network_broadcast_api_plugin, broadcast_block) { - const auto n_args = args.args->size(); - FC_ASSERT(n_args == 1, "Expected 1 argument, got 0"); - auto block = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (signed_block, block) + ); pimpl->_chain.accept_block(block); pimpl->_p2p.broadcast_block(block); return broadcast_block_return(); @@ -92,12 +95,12 @@ namespace golos { DEFINE_API(network_broadcast_api_plugin,broadcast_transaction_with_callback){ // TODO: implement commit semantic for delegating connection handlers - const auto n_args = args.args->size(); - FC_ASSERT(n_args >= 2, "Expected at least 1 argument, got 0"); - auto trx = args.args->at(1).as(); - if (n_args > 2) { - const auto max_block_age = args.args->at(2).as(); - FC_ASSERT(!check_max_block_age(max_block_age)); + PLUGIN_API_VALIDATE_ARGS( + (signed_transaction, trx) + (uint32_t, max_block_age, 0) + ); + if (n_args >= 2) { + GOLOS_CHECK_PARAM(max_block_age, GOLOS_CHECK_VALUE(!check_max_block_age(max_block_age), "Invalid value")); } trx.validate(); diff --git a/plugins/operation_history/include/golos/plugins/operation_history/history_object.hpp b/plugins/operation_history/include/golos/plugins/operation_history/history_object.hpp index 4cdf867455..c23d2858b2 100644 --- a/plugins/operation_history/include/golos/plugins/operation_history/history_object.hpp +++ b/plugins/operation_history/include/golos/plugins/operation_history/history_object.hpp @@ -29,7 +29,7 @@ namespace golos { namespace plugins { namespace operation_history { - enum account_object_types { + enum operation_object_types { operation_object_type = (OPERATION_HISTORY_SPACE_ID << 8), }; diff --git a/plugins/operation_history/include/golos/plugins/operation_history/plugin.hpp b/plugins/operation_history/include/golos/plugins/operation_history/plugin.hpp index e56c001bce..662f90fa99 100644 --- a/plugins/operation_history/include/golos/plugins/operation_history/plugin.hpp +++ b/plugins/operation_history/include/golos/plugins/operation_history/plugin.hpp @@ -77,9 +77,8 @@ namespace golos { namespace plugins { namespace operation_history { * @return sequence of operations included/generated within the block */ (get_ops_in_block) - - (get_transaction) + (get_transaction) ) private: struct plugin_impl; diff --git a/plugins/operation_history/plugin.cpp b/plugins/operation_history/plugin.cpp index 5138b9bbfc..e8effa0755 100644 --- a/plugins/operation_history/plugin.cpp +++ b/plugins/operation_history/plugin.cpp @@ -1,14 +1,15 @@ #include #include +#include +#include #include #include #define STEEM_NAMESPACE_PREFIX "golos::protocol::" +#define OPERATION_POSTFIX "_operation" -#define CHECK_ARG_SIZE(s) \ - FC_ASSERT( args.args->size() == s, "Expected #s argument(s), was ${n}", ("n", args.args->size()) ); namespace golos { namespace plugins { namespace operation_history { @@ -20,35 +21,40 @@ namespace golos { namespace plugins { namespace operation_history { struct operation_visitor { operation_visitor( golos::chain::database& db, - golos::chain::operation_notification& op_note) + golos::chain::operation_notification& op_note, + uint32_t start_block) : database(db), - note(op_note) { + note(op_note), + start_block(start_block) { } using result_type = void; golos::chain::database& database; golos::chain::operation_notification& note; + uint32_t start_block; template void operator()(Op&&) const { - note.stored_in_db = true; - - database.create([&](operation_object& obj) { - note.db_id = obj.id._id; - - obj.trx_id = note.trx_id; - obj.block = note.block; - obj.trx_in_block = note.trx_in_block; - obj.op_in_trx = note.op_in_trx; - obj.virtual_op = note.virtual_op; - obj.timestamp = database.head_block_time(); - - const auto size = fc::raw::pack_size(note.op); - obj.serialized_op.resize(size); - fc::datastream ds(obj.serialized_op.data(), size); - fc::raw::pack(ds, note.op); - }); + if (start_block <= database.head_block_num()) { + note.stored_in_db = true; + + database.create([&](operation_object& obj) { + note.db_id = obj.id._id; + + obj.trx_id = note.trx_id; + obj.block = note.block; + obj.trx_in_block = note.trx_in_block; + obj.op_in_trx = note.op_in_trx; + obj.virtual_op = note.virtual_op; + obj.timestamp = database.head_block_time(); + + const auto size = fc::raw::pack_size(note.op); + obj.serialized_op.resize(size); + fc::datastream ds(obj.serialized_op.data(), size); + fc::raw::pack(ds, note.op); + }); + } } }; @@ -60,7 +66,7 @@ namespace golos { namespace plugins { namespace operation_history { const fc::flat_set& ops_list, bool is_blacklist, uint32_t block) - : operation_visitor(db, note), + : operation_visitor(db, note, block), filter(ops_list), blacklist(is_blacklist), start_block(block) { @@ -72,9 +78,6 @@ namespace golos { namespace plugins { namespace operation_history { template void operator()(const T& op) const { - if (database.head_block_num() < start_block) { - return; - } if (filter.find(fc::get_typename::name()) != filter.end()) { if (!blacklist) { operation_visitor::operator()(op); @@ -94,11 +97,26 @@ namespace golos { namespace plugins { namespace operation_history { ~plugin_impl() = default; + void erase_old_blocks() { + uint32_t head_block = database.head_block_num(); + if (history_blocks <= head_block) { + uint32_t need_block = head_block - history_blocks; + const auto& idx = database.get_index().indices().get(); + auto it = idx.begin(); + while (it != idx.end() && it->block <= need_block) { + auto next_it = it; + ++next_it; + database.remove(*it); + it = next_it; + } + } + } + void on_operation(golos::chain::operation_notification& note) { if (filter_content) { note.op.visit(operation_visitor_filter(database, note, ops_list, blacklist, start_block)); } else { - note.op.visit(operation_visitor(database, note)); + note.op.visit(operation_visitor(database, note, start_block)); } } @@ -130,28 +148,31 @@ namespace golos { namespace plugins { namespace operation_history { result.transaction_num = itr->trx_in_block; return result; } - FC_ASSERT(false, "Unknown Transaction ${t}", ("t", id)); + GOLOS_THROW_MISSING_OBJECT("transaction", id); } bool filter_content = false; uint32_t start_block = 0; - bool blacklist = false; + uint32_t history_blocks = UINT32_MAX; + bool blacklist = true; fc::flat_set ops_list; golos::chain::database& database; }; DEFINE_API(plugin, get_ops_in_block) { - CHECK_ARG_SIZE(2) - auto block_num = args.args->at(0).as(); - auto only_virtual = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, block_num) + (bool, only_virtual) + ); return pimpl->database.with_weak_read_lock([&](){ return pimpl->get_ops_in_block(block_num, only_virtual); }); } DEFINE_API(plugin, get_transaction) { - CHECK_ARG_SIZE(1) - auto id = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (transaction_id_type, id) + ); return pimpl->database.with_weak_read_lock([&](){ return pimpl->get_transaction(id); }); @@ -171,11 +192,13 @@ namespace golos { namespace plugins { namespace operation_history { "Defines a list of operations which will be explicitly ignored." ) ( "history-start-block", - boost::program_options::value()->composing(), + boost::program_options::value(), "Defines starting block from which recording stats." + ) ( + "history-blocks", + boost::program_options::value(), + "Defines depth of history for recording stats." ); - - cfg.add(cli); } void plugin::plugin_initialize(const boost::program_options::variables_map& options) { @@ -196,15 +219,19 @@ namespace golos { namespace plugins { namespace operation_history { for (const auto& op : ops) { if (op.size()) { - pimpl->ops_list.insert(STEEM_NAMESPACE_PREFIX + op); + std::size_t pos = op.find(OPERATION_POSTFIX); + if (pos not_eq std::string::npos and (pos + strlen(OPERATION_POSTFIX)) == op.size()) { + pimpl->ops_list.insert(STEEM_NAMESPACE_PREFIX + op); + } else { + pimpl->ops_list.insert(STEEM_NAMESPACE_PREFIX + op + OPERATION_POSTFIX); + } } } } }; if (options.count("history-whitelist-ops")) { - FC_ASSERT( - !options.count("history-blacklist-ops"), + GOLOS_CHECK_OPTION(!options.count("history-blacklist-ops"), "history-blacklist-ops and history-whitelist-ops can't be specified together"); pimpl->filter_content = true; @@ -225,6 +252,18 @@ namespace golos { namespace plugins { namespace operation_history { pimpl->start_block = 0; } ilog("operation_history: start_block ${s}", ("s", pimpl->start_block)); + + if (options.count("history-blocks")) { + uint32_t history_blocks = options.at("history-blocks").as(); + pimpl->history_blocks = history_blocks; + pimpl->database.applied_block.connect([&](const signed_block& block){ + pimpl->erase_old_blocks(); + }); + } else { + pimpl->history_blocks = UINT32_MAX; + } + ilog("operation_history: history-blocks ${s}", ("s", pimpl->history_blocks)); + JSON_RPC_REGISTER_API(name()); ilog("operation_history plugin: plugin_initialize() end"); } diff --git a/plugins/private_message/CMakeLists.txt b/plugins/private_message/CMakeLists.txt index fd5daa2af8..8d81dfd05d 100644 --- a/plugins/private_message/CMakeLists.txt +++ b/plugins/private_message/CMakeLists.txt @@ -2,13 +2,17 @@ set(CURRENT_TARGET private_message) list(APPEND CURRENT_TARGET_HEADERS include/golos/plugins/private_message/private_message_plugin.hpp + include/golos/plugins/private_message/private_message_api_objects.hpp include/golos/plugins/private_message/private_message_objects.hpp + include/golos/plugins/private_message/private_message_operations.hpp include/golos/plugins/private_message/private_message_evaluators.hpp + include/golos/plugins/private_message/private_message_exceptions.hpp ) list(APPEND CURRENT_TARGET_SOURCES private_message_plugin.cpp - private_message_objects.cpp + private_message_api_objects.cpp + private_message_operations.cpp ) if(BUILD_SHARED_LIBRARIES) @@ -29,9 +33,9 @@ set_property(TARGET golos_${CURRENT_TARGET} PROPERTY EXPORT_NAME ${CURRENT_TARGE target_link_libraries( golos_${CURRENT_TARGET} golos::chain_plugin - golos::p2p golos::protocol golos::network + golos::json_rpc graphene_utilities graphene_time appbase diff --git a/plugins/private_message/include/golos/plugins/private_message/private_message_api_objects.hpp b/plugins/private_message/include/golos/plugins/private_message/private_message_api_objects.hpp new file mode 100644 index 0000000000..b48db7f0e6 --- /dev/null +++ b/plugins/private_message/include/golos/plugins/private_message/private_message_api_objects.hpp @@ -0,0 +1,219 @@ +#pragma once + +#include + +#include + +#define PRIVATE_DEFAULT_LIMIT 100 + +namespace golos { namespace plugins { namespace private_message { + + using namespace golos::protocol; + + class message_object; + + struct message_api_object { + message_api_object(const message_object& o); + message_api_object(); + + account_name_type from; + account_name_type to; + uint64_t nonce = 0; + public_key_type from_memo_key; + public_key_type to_memo_key; + uint32_t checksum = 0; + std::vector encrypted_message; + + time_point_sec create_date; + time_point_sec receive_date; + time_point_sec read_date; + time_point_sec remove_date; + }; + + class settings_object; + + struct settings_api_object final { + settings_api_object(const settings_object& o); + settings_api_object(); + + bool ignore_messages_from_unknown_contact = false; + }; + + struct contact_size_info { + uint32_t total_outbox_messages = 0; + uint32_t unread_outbox_messages = 0; + uint32_t total_inbox_messages = 0; + uint32_t unread_inbox_messages = 0; + + bool empty() const { + return !total_outbox_messages && !total_inbox_messages; + } + + contact_size_info& operator-=(const contact_size_info& s) { + total_outbox_messages -= s.total_outbox_messages; + unread_outbox_messages -= s.unread_outbox_messages; + total_inbox_messages -= s.total_inbox_messages; + unread_inbox_messages -= s.unread_inbox_messages; + return *this; + } + + contact_size_info& operator+=(const contact_size_info& s) { + total_outbox_messages += s.total_outbox_messages; + unread_outbox_messages += s.unread_outbox_messages; + total_inbox_messages += s.total_inbox_messages; + unread_inbox_messages += s.unread_inbox_messages; + return *this; + } + + bool operator==(const contact_size_info& s) const { + return + total_outbox_messages == s.total_outbox_messages && + unread_outbox_messages == s.unread_outbox_messages && + total_inbox_messages == s.total_inbox_messages && + unread_inbox_messages == s.unread_inbox_messages; + } + + bool operator!=(const contact_size_info& s) const { + return !(this->operator==(s)); + } + }; + + struct contacts_size_info final: public contact_size_info { + uint32_t total_contacts = 0; + }; + + /** + * Contact item + */ + class contact_object; + + struct contact_api_object final { + contact_api_object(const contact_object& o); + contact_api_object(); + + account_name_type owner; + account_name_type contact; + std::string json_metadata; + private_contact_type local_type = unknown; + private_contact_type remote_type = unknown; + contact_size_info size; + }; + + /** + * Counters for account contact lists + */ + struct contact_size_object; + + struct contacts_size_api_object { + fc::flat_map size; + }; + + /** + * Query for inbox/outbox messages + */ + struct message_box_query final { + fc::flat_set select_accounts; + fc::flat_set filter_accounts; + time_point_sec newest_date = time_point_sec::min(); + bool unread_only = false; + uint16_t limit = PRIVATE_DEFAULT_LIMIT; + uint32_t offset = 0; + }; + + /** + * Query for thread messages + */ + struct message_thread_query final { + time_point_sec newest_date = time_point_sec::min(); + bool unread_only = false; + uint16_t limit = PRIVATE_DEFAULT_LIMIT; + uint32_t offset = 0; + }; + + /** + * Events for callbacks + */ + enum class callback_event_type: uint8_t { + message, + mark, + remove_inbox, + remove_outbox, + contact, + }; + + /** + * Query for callback + */ + struct callback_query final { + fc::flat_set select_accounts; + fc::flat_set filter_accounts; + fc::flat_set select_events; + fc::flat_set filter_events; + }; + + /** + * Callback event about message + */ + struct callback_message_event final { + callback_event_type type; + message_api_object message; + }; + + /** + * Callback event about contact + */ + struct callback_contact_event final { + callback_event_type type; + contact_api_object contact; + }; + +} } } // golos::plugins::private_message + +FC_REFLECT( + (golos::plugins::private_message::message_api_object), + (from)(to)(from_memo_key)(to_memo_key)(nonce)(checksum)(encrypted_message) + (create_date)(receive_date)(read_date)(remove_date)) + +FC_REFLECT( + (golos::plugins::private_message::settings_api_object), + (ignore_messages_from_unknown_contact)) + +FC_REFLECT( + (golos::plugins::private_message::contact_size_info), + (total_outbox_messages)(unread_outbox_messages)(total_inbox_messages)(unread_inbox_messages)) + +FC_REFLECT_DERIVED( + (golos::plugins::private_message::contacts_size_info), ((golos::plugins::private_message::contact_size_info)), + (total_contacts)) + +FC_REFLECT( + (golos::plugins::private_message::contact_api_object), + (contact)(json_metadata)(local_type)(remote_type)(size)) + +FC_REFLECT( + (golos::plugins::private_message::contacts_size_api_object), + (size)) + +FC_REFLECT( + (golos::plugins::private_message::message_box_query), + (select_accounts)(filter_accounts)(newest_date)(unread_only)(limit)(offset)) + +FC_REFLECT( + (golos::plugins::private_message::message_thread_query), + (newest_date)(unread_only)(limit)(offset)) + +FC_REFLECT_ENUM( + golos::plugins::private_message::callback_event_type, + (message)(mark)(remove_inbox)(remove_outbox)(contact)) + +FC_REFLECT( + (golos::plugins::private_message::callback_query), + (select_accounts)(filter_accounts)(select_events)(filter_events)) + +FC_REFLECT( + (golos::plugins::private_message::callback_message_event), + (type)(message)) + +FC_REFLECT( + (golos::plugins::private_message::callback_contact_event), + (type)(contact)) diff --git a/plugins/private_message/include/golos/plugins/private_message/private_message_evaluators.hpp b/plugins/private_message/include/golos/plugins/private_message/private_message_evaluators.hpp index c3aa1e4962..a276e5eb30 100644 --- a/plugins/private_message/include/golos/plugins/private_message/private_message_evaluators.hpp +++ b/plugins/private_message/include/golos/plugins/private_message/private_message_evaluators.hpp @@ -1,29 +1,102 @@ #pragma once -#include -#include -#include +#include #include -namespace golos { - namespace plugins { - namespace private_message { +namespace golos { namespace chain { + class database; +} } // golos::chain - class private_message_evaluator : public golos::chain::evaluator_impl - { - public: - typedef private_message_operation operation_type; +namespace golos { namespace plugins { namespace private_message { - private_message_evaluator(database& db, private_message_plugin* plugin) - : golos::chain::evaluator_impl( db ) - , _plugin( plugin ) - {} + class private_message_plugin; - void do_apply( const private_message_operation& o ); + using golos::chain::evaluator_impl; + using golos::chain::database; - private_message_plugin* _plugin; - }; + template + class private_message_evaluator final: + public evaluator_impl, private_message_plugin_operation> + { + public: + using operation_type = private_message_operation; + private_message_evaluator(database& db, Impl* impl) + : evaluator_impl, private_message_plugin_operation>(db), + impl_(impl) { } - } -} + + void do_apply(const private_message_operation& o); + + Impl* impl_; + }; + + template + class private_delete_message_evaluator final: + public evaluator_impl, private_message_plugin_operation> + { + public: + using operation_type = private_delete_message_operation; + + private_delete_message_evaluator(database& db, Impl* impl) + : evaluator_impl, private_message_plugin_operation>(db), + impl_(impl) { + } + + void do_apply(const private_delete_message_operation& o); + + Impl* impl_; + }; + + template + class private_mark_message_evaluator final: + public evaluator_impl, private_message_plugin_operation> + { + public: + using operation_type = private_mark_message_operation; + + private_mark_message_evaluator(database& db, Impl* impl) + : evaluator_impl, private_message_plugin_operation>(db), + impl_(impl) { + } + + void do_apply(const private_mark_message_operation& o); + + Impl* impl_; + }; + + template + class private_settings_evaluator final: + public evaluator_impl, private_message_plugin_operation> + { + public: + using operation_type = private_settings_operation; + + private_settings_evaluator(database& db, Impl* impl) + : evaluator_impl, private_message_plugin_operation>(db), + impl_(impl) { + } + + void do_apply(const private_settings_operation& o); + + Impl* impl_; + }; + + template + class private_contact_evaluator final: + public evaluator_impl, private_message_plugin_operation> + { + public: + using operation_type = private_contact_operation; + + private_contact_evaluator(database& db, Impl* impl) + : evaluator_impl, private_message_plugin_operation>(db), + impl_(impl) { + } + + void do_apply(const private_contact_operation& o); + + Impl* impl_; + }; + +} } } diff --git a/plugins/private_message/include/golos/plugins/private_message/private_message_exceptions.hpp b/plugins/private_message/include/golos/plugins/private_message/private_message_exceptions.hpp new file mode 100644 index 0000000000..4e9a04a7c3 --- /dev/null +++ b/plugins/private_message/include/golos/plugins/private_message/private_message_exceptions.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +namespace golos { namespace plugins { namespace private_message { + + struct logic_errors { + enum types { + cannot_send_to_yourself, + from_and_to_memo_keys_must_be_different, + cannot_add_contact_to_yourself, + sender_in_ignore_list, + recepient_ignores_messages_from_unknown_contact, + add_unknown_contact, + contact_has_not_changed, + no_unread_messages, + }; + }; + +} } } // golos::plugins::private_message + +namespace golos { + template<> + inline std::string get_logic_error_namespace() { + return golos::plugins::private_message::private_message_plugin::name(); + } +} + +FC_REFLECT_ENUM(golos::plugins::private_message::logic_errors::types, + (cannot_send_to_yourself) + (from_and_to_memo_keys_must_be_different) + (cannot_add_contact_to_yourself) + (sender_in_ignore_list) + (recepient_ignores_messages_from_unknown_contact) + (add_unknown_contact) + (contact_has_not_changed) + (no_unread_messages) +); \ No newline at end of file diff --git a/plugins/private_message/include/golos/plugins/private_message/private_message_objects.hpp b/plugins/private_message/include/golos/plugins/private_message/private_message_objects.hpp index 119976e56b..2bd5a4926c 100644 --- a/plugins/private_message/include/golos/plugins/private_message/private_message_objects.hpp +++ b/plugins/private_message/include/golos/plugins/private_message/private_message_objects.hpp @@ -4,160 +4,256 @@ #include #include #include -#include -#include -#include -#include +#include -namespace golos { - namespace plugins { - namespace private_message { +namespace golos { namespace plugins { namespace private_message { - using namespace golos::protocol; - using namespace chainbase; - using namespace golos::chain; + using namespace golos::protocol; + using namespace chainbase; + using namespace golos::chain; + using namespace boost::multi_index; #ifndef PRIVATE_MESSAGE_SPACE_ID #define PRIVATE_MESSAGE_SPACE_ID 6 #endif -#define STEEMIT_PRIVATE_MESSAGE_COP_ID 777 - - enum private_message_object_type { - message_object_type = (PRIVATE_MESSAGE_SPACE_ID << 8) - }; - - - struct message_body { - fc::time_point thread_start; /// the sent_time of the original message, if any - std::string subject; - std::string body; - std::string json_meta; - flat_set cc; - }; - - - class message_object - : public object { - public: - template - message_object(Constructor &&c, allocator a) : - encrypted_message(a) { - c(*this); - } - - id_type id; - - account_name_type from; - account_name_type to; - public_key_type from_memo_key; - public_key_type to_memo_key; - uint64_t sent_time = 0; /// used as seed to secret generation - time_point_sec receive_time; /// time received by blockchain - uint32_t checksum = 0; - buffer_type encrypted_message; - }; - - typedef message_object::id_type message_id_type; - - struct message_api_obj { - message_api_obj(const message_object &o) : - id(o.id), - from(o.from), - to(o.to), - from_memo_key(o.from_memo_key), - to_memo_key(o.to_memo_key), - sent_time(o.sent_time), - receive_time(o.receive_time), - checksum(o.checksum), - encrypted_message(o.encrypted_message.begin(), o.encrypted_message.end()) { - } - - message_api_obj() { - } - - message_id_type id; - account_name_type from; - account_name_type to; - public_key_type from_memo_key; - public_key_type to_memo_key; - uint64_t sent_time; - time_point_sec receive_time; - uint32_t checksum; - vector encrypted_message; - }; - - struct extended_message_object : public message_api_obj { - extended_message_object() { - } - - extended_message_object(const message_api_obj &o) - : message_api_obj(o) { - } - - message_body message; - }; - - struct by_to_date; - struct by_from_date; - - using namespace boost::multi_index; - - using message_index = multi_index_container< - message_object, - indexed_by< - ordered_unique< - tag, - member - >, - ordered_unique< - tag, - composite_key< - message_object, - member, - member, - member - >, - composite_key_compare, std::greater, std::less> - >, - ordered_unique< - tag, - composite_key< - message_object, - member, - member, - member - >, - composite_key_compare, std::greater, std::less> - > - >, - allocator - >; - - struct private_message_operation: public base_operation { - account_name_type from; - account_name_type to; - public_key_type from_memo_key; - public_key_type to_memo_key; - uint64_t sent_time = 0; /// used as seed to secret generation - uint32_t checksum = 0; - std::vector encrypted_message; - - void validate() const; - void get_required_posting_authorities(flat_set& a) const { - a.insert(from); - } - }; - - using private_message_plugin_operation = fc::static_variant; + enum private_message_object_type { + message_object_type = (PRIVATE_MESSAGE_SPACE_ID << 8), + settings_object_type = (PRIVATE_MESSAGE_SPACE_ID << 8) + 1, + contact_object_type = (PRIVATE_MESSAGE_SPACE_ID << 8) + 2, + contact_size_object_type = (PRIVATE_MESSAGE_SPACE_ID << 8) + 3, + }; + /** + * Message + */ + class message_object: public object { + public: + template + message_object(Constructor&& c, allocator a) + : encrypted_message(a) { + c(*this); } - } -} -FC_REFLECT((golos::plugins::private_message::private_message_operation), - (from)(to)(from_memo_key)(to_memo_key)(sent_time)(checksum)(encrypted_message)) + id_type id; -FC_REFLECT_TYPENAME((golos::plugins::private_message::private_message_plugin_operation)) -DECLARE_OPERATION_TYPE(golos::plugins::private_message::private_message_plugin_operation) + account_name_type from; + account_name_type to; + uint64_t nonce; + public_key_type from_memo_key; + public_key_type to_memo_key; + uint32_t checksum = 0; + buffer_type encrypted_message; + + time_point_sec inbox_create_date; // == time_point_sec::min() means removed message + time_point_sec outbox_create_date; // == time_point_sec::min() means removed message + time_point_sec receive_date; + time_point_sec read_date; + time_point_sec remove_date; + }; + + using message_id_type = message_object::id_type; + + struct by_inbox; + struct by_inbox_account; + struct by_outbox; + struct by_outbox_account; + struct by_nonce; + + struct by_owner; + struct by_contact; + + using message_index = multi_index_container< + message_object, + indexed_by< + ordered_unique< + tag, + member>, + ordered_unique< + tag, + composite_key< + message_object, + member, + member, + member>, + composite_key_compare< + string_less, + std::greater, + std::greater>>, + ordered_unique< + tag, + composite_key< + message_object, + member, + member, + member, + member>, + composite_key_compare< + string_less, + string_less, + std::greater, + std::greater>>, + ordered_unique< + tag, + composite_key< + message_object, + member, + member, + member>, + composite_key_compare< + string_less, + std::greater, + std::greater>>, + ordered_unique< + tag, + composite_key< + message_object, + member, + member, + member, + member>, + composite_key_compare< + string_less, + string_less, + std::greater, + std::greater>>, + ordered_unique< + tag, + composite_key< + message_object, + member, + member, + member>, + composite_key_compare< + string_less, + string_less, + std::less>>>, + allocator>; + + /** + * Settings + */ + class settings_object: public object { + public: + template + settings_object(Constructor&& c, allocator a) { + c(*this); + } + + id_type id; + + account_name_type owner; + bool ignore_messages_from_unknown_contact = false; + }; + + using settings_id_type = settings_object::id_type; + + using settings_index = multi_index_container< + settings_object, + indexed_by< + ordered_unique< + tag, + member>, + ordered_unique< + tag, + member>>, + allocator>; + + /** + * Contact item + */ + class contact_object: public object { + public: + template + contact_object(Constructor&& c, allocator a): json_metadata(a) { + c(*this); + } + + id_type id; + + account_name_type owner; + account_name_type contact; + private_contact_type type; + shared_string json_metadata; + contact_size_info size; + }; + + using contact_id_type = contact_object::id_type; + + using contact_index = multi_index_container< + contact_object, + indexed_by< + ordered_unique< + tag, + member>, + ordered_unique< + tag, + composite_key< + contact_object, + member, + member, + member>, + composite_key_compare< + string_less, + std::greater, + string_less>>, + ordered_unique< + tag, + composite_key< + contact_object, + member, + member>, + composite_key_compare< + string_less, + string_less>>>, + allocator>; + + /** + * Counters for account contact lists + */ + struct contact_size_object: public object { + template + contact_size_object(Constructor&& c, allocator a) { + c(*this); + } + + id_type id; + + account_name_type owner; + private_contact_type type; + contacts_size_info size; + }; + + using contact_size_id_type = contact_size_object::id_type; + + using contact_size_index = multi_index_container< + contact_size_object, + indexed_by< + ordered_unique< + tag, + member>, + ordered_unique< + tag, + composite_key< + contact_size_object, + member, + member>, + composite_key_compare< + string_less, + std::less>>>, + allocator>; + +} } } // golos::plugins::private_message + +CHAINBASE_SET_INDEX_TYPE( + golos::plugins::private_message::message_object, golos::plugins::private_message::message_index) + +CHAINBASE_SET_INDEX_TYPE( + golos::plugins::private_message::settings_object, golos::plugins::private_message::settings_index) + +CHAINBASE_SET_INDEX_TYPE( + golos::plugins::private_message::contact_object, golos::plugins::private_message::contact_index) + +CHAINBASE_SET_INDEX_TYPE( + golos::plugins::private_message::contact_size_object, golos::plugins::private_message::contact_size_index) \ No newline at end of file diff --git a/plugins/private_message/include/golos/plugins/private_message/private_message_operations.hpp b/plugins/private_message/include/golos/plugins/private_message/private_message_operations.hpp new file mode 100644 index 0000000000..58bffcc754 --- /dev/null +++ b/plugins/private_message/include/golos/plugins/private_message/private_message_operations.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +namespace golos { namespace plugins { namespace private_message { + using namespace golos::protocol; + + struct private_message_operation: public base_operation { + account_name_type from; + account_name_type to; + uint64_t nonce; + public_key_type from_memo_key; + public_key_type to_memo_key; + uint32_t checksum; + bool update = false; + std::vector encrypted_message; + + void validate() const; + void get_required_posting_authorities(flat_set& a) const; + }; + + struct private_delete_message_operation: public base_operation { + account_name_type requester; + account_name_type from; + account_name_type to; + uint64_t nonce = 0; + time_point_sec start_date; + time_point_sec stop_date; + + void validate() const; + void get_required_posting_authorities(flat_set& a) const; + }; + + struct private_mark_message_operation: public base_operation { + account_name_type from; + account_name_type to; + uint64_t nonce = 0; + time_point_sec start_date; + time_point_sec stop_date; + + void validate() const; + void get_required_posting_authorities(flat_set& a) const; + }; + + struct private_settings_operation: public base_operation { + account_name_type owner; + bool ignore_messages_from_unknown_contact = false; + + void validate() const; + void get_required_posting_authorities(flat_set& a) const; + }; + + /** + * Types of contacts + */ + enum private_contact_type: uint8_t { + unknown = 1, + pinned = 2, + ignored = 3, + }; + + constexpr auto private_contact_type_size = static_cast(ignored + 1); + + struct private_contact_operation: public base_operation { + account_name_type owner; + account_name_type contact; + private_contact_type type = pinned; + std::string json_metadata; + + void validate() const; + void get_required_posting_authorities(flat_set& a) const; + }; + + using private_message_plugin_operation = fc::static_variant< + private_message_operation, + private_delete_message_operation, + private_mark_message_operation, + private_settings_operation, + private_contact_operation>; + +} } } // golos::plugins::private_message + +FC_REFLECT( + (golos::plugins::private_message::private_message_operation), + (from)(to)(nonce)(from_memo_key)(to_memo_key)(checksum)(update)(encrypted_message)) + +FC_REFLECT( + (golos::plugins::private_message::private_delete_message_operation), + (requester)(from)(to)(nonce)(start_date)(stop_date)) + +FC_REFLECT( + (golos::plugins::private_message::private_mark_message_operation), + (from)(to)(nonce)(start_date)(stop_date)) + +FC_REFLECT( + (golos::plugins::private_message::private_settings_operation), + (owner)(ignore_messages_from_unknown_contact)) + +FC_REFLECT_ENUM( + golos::plugins::private_message::private_contact_type, + (unknown)(pinned)(ignored)) + +FC_REFLECT( + (golos::plugins::private_message::private_contact_operation), + (owner)(contact)(type)(json_metadata)) + +FC_REFLECT_TYPENAME((golos::plugins::private_message::private_message_plugin_operation)) + +DECLARE_OPERATION_TYPE(golos::plugins::private_message::private_message_plugin_operation) \ No newline at end of file diff --git a/plugins/private_message/include/golos/plugins/private_message/private_message_plugin.hpp b/plugins/private_message/include/golos/plugins/private_message/private_message_plugin.hpp index f969a59ac4..16173a6907 100644 --- a/plugins/private_message/include/golos/plugins/private_message/private_message_plugin.hpp +++ b/plugins/private_message/include/golos/plugins/private_message/private_message_plugin.hpp @@ -1,6 +1,6 @@ #pragma once -#include - +#include +#include #include #include @@ -13,73 +13,59 @@ #include #include -namespace golos { - namespace plugins { - namespace private_message { - using namespace golos::chain; - // struct inbox_r { - // vector inbox; - // }; - - // struct outbox_r { - // vector outbox; - // }; - - DEFINE_API_ARGS(get_inbox, json_rpc::msg_pack, vector ) - DEFINE_API_ARGS(get_outbox, json_rpc::msg_pack, vector ) - - /** - * This plugin scans the blockchain for custom operations containing a valid message and authorized - * by the posting key. - * - */ - class private_message_plugin final : public appbase::plugin { - public: - - APPBASE_PLUGIN_REQUIRES((json_rpc::plugin)) - - private_message_plugin(); - - ~private_message_plugin(); - - void set_program_options( - boost::program_options::options_description &cli, - boost::program_options::options_description &cfg) override; - - void plugin_initialize(const boost::program_options::variables_map &options) override; - - void plugin_startup() override; +namespace golos { namespace plugins { namespace private_message { + using namespace golos::chain; - void plugin_shutdown() override; + DEFINE_API_ARGS(get_inbox, json_rpc::msg_pack, std::vector) + DEFINE_API_ARGS(get_outbox, json_rpc::msg_pack, std::vector) + DEFINE_API_ARGS(get_thread, json_rpc::msg_pack, std::vector) + DEFINE_API_ARGS(get_settings , json_rpc::msg_pack, settings_api_object) + DEFINE_API_ARGS(get_contact_info, json_rpc::msg_pack, contact_api_object) + DEFINE_API_ARGS(get_contacts_size, json_rpc::msg_pack, contacts_size_api_object) + DEFINE_API_ARGS(get_contacts, json_rpc::msg_pack, std::vector) + DEFINE_API_ARGS(set_callback, json_rpc::msg_pack, json_rpc::void_type) - flat_map tracked_accounts() const; /// map start_range to end_range + /** + * This plugin scans the blockchain for custom operations containing a valid message and authorized + * by the posting key. + * + */ + class private_message_plugin final : public appbase::plugin { + public: + APPBASE_PLUGIN_REQUIRES((json_rpc::plugin)) - friend class private_message_plugin_impl; + private_message_plugin(); - constexpr const static char *plugin_name = "private_message"; + ~private_message_plugin(); - static const std::string &name() { - static std::string name = plugin_name; - return name; - } + void set_program_options( + boost::program_options::options_description& cli, + boost::program_options::options_description& cfg) override; - DECLARE_API((get_inbox)(get_outbox)) + void plugin_initialize(const boost::program_options::variables_map& options) override; + void plugin_startup() override; - private: - class private_message_plugin_impl; + void plugin_shutdown() override; - std::unique_ptr my; - }; - } - } -} //golos::plugins::private_message + static const std::string& name(); -FC_REFLECT((golos::plugins::private_message::message_body), (thread_start)(subject)(body)(json_meta)(cc)); + DECLARE_API( + (get_inbox) + (get_outbox) + (get_thread) + (get_settings) + (get_contact_info) + (get_contacts_size) + (get_contacts) + (set_callback) + ) -FC_REFLECT((golos::plugins::private_message::message_object), (id)(from)(to)(from_memo_key)(to_memo_key)(sent_time)(receive_time)(checksum)(encrypted_message)); -CHAINBASE_SET_INDEX_TYPE(golos::plugins::private_message::message_object, golos::plugins::private_message::message_index); + private: + class private_message_plugin_impl; + friend class private_message_plugin_impl; -FC_REFLECT((golos::plugins::private_message::message_api_obj), (id)(from)(to)(from_memo_key)(to_memo_key)(sent_time)(receive_time)(checksum)(encrypted_message)); + std::unique_ptr my; + }; -FC_REFLECT_DERIVED((golos::plugins::private_message::extended_message_object), ((golos::plugins::private_message::message_api_obj)), (message)); +} } } //golos::plugins::private_message diff --git a/plugins/private_message/private_message_api_objects.cpp b/plugins/private_message/private_message_api_objects.cpp new file mode 100644 index 0000000000..22e5fdf37b --- /dev/null +++ b/plugins/private_message/private_message_api_objects.cpp @@ -0,0 +1,42 @@ +#include +#include + +namespace golos { namespace plugins { namespace private_message { + + message_api_object::message_api_object(const message_object& o) + : from(o.from), + to(o.to), + nonce(o.nonce), + from_memo_key(o.from_memo_key), + to_memo_key(o.to_memo_key), + checksum(o.checksum), + encrypted_message(o.encrypted_message.begin(), o.encrypted_message.end()), + create_date(std::max(o.inbox_create_date, o.outbox_create_date)), + receive_date(o.receive_date), + read_date(o.read_date), + remove_date(o.remove_date) { + } + + message_api_object::message_api_object() = default; + + + settings_api_object::settings_api_object(const settings_object& o) + : ignore_messages_from_unknown_contact(o.ignore_messages_from_unknown_contact) { + } + + settings_api_object::settings_api_object() = default; + + + contact_api_object::contact_api_object(const contact_object& o) + : owner(o.owner), + contact(o.contact), + json_metadata(o.json_metadata.begin(), o.json_metadata.end()), + local_type(o.type), + size(o.size) { + } + + contact_api_object::contact_api_object() = default; + +} } } // golos::plugins::private_message + + diff --git a/plugins/private_message/private_message_objects.cpp b/plugins/private_message/private_message_objects.cpp deleted file mode 100644 index 95775dab53..0000000000 --- a/plugins/private_message/private_message_objects.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -namespace golos { - namespace plugins { - namespace private_message { - - void private_message_operation::validate() const { - FC_ASSERT(from != to, "You cannot write to yourself"); - } - } - } -} - -DEFINE_OPERATION_TYPE(golos::plugins::private_message::private_message_plugin_operation); diff --git a/plugins/private_message/private_message_operations.cpp b/plugins/private_message/private_message_operations.cpp new file mode 100644 index 0000000000..a226e8828b --- /dev/null +++ b/plugins/private_message/private_message_operations.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include + +namespace golos { namespace plugins { namespace private_message { + + static inline void validate_account_name(const string& name) { + GOLOS_CHECK_VALUE(is_valid_account_name(name), "Account name ${name} is invalid", ("name", name)); + } + + static inline bool is_valid_contact_type(private_contact_type type) { + switch(type) { + case unknown: + case pinned: + case ignored: + return true; + + default: + break; + } + return false; + } + + + void private_settings_operation::validate() const { + GOLOS_CHECK_PARAM_ACCOUNT(owner); + } + void private_settings_operation::get_required_posting_authorities(flat_set& a) const { + a.insert(owner); + } + + + void private_message_operation::validate() const { + GOLOS_CHECK_PARAM_ACCOUNT(to); + GOLOS_CHECK_PARAM_ACCOUNT(from); + + GOLOS_CHECK_LOGIC(from != to, + logic_errors::cannot_send_to_yourself, + "You cannot write to yourself"); + + GOLOS_CHECK_PARAM(to_memo_key, { + GOLOS_CHECK_VALUE(to_memo_key != public_key_type(), "`to_key` can't be empty"); + }); + + GOLOS_CHECK_PARAM(from_memo_key, { + GOLOS_CHECK_VALUE(from_memo_key != public_key_type(), "`from_key` can't be empty"); + }); + + GOLOS_CHECK_LOGIC( + from_memo_key != to_memo_key, + logic_errors::from_and_to_memo_keys_must_be_different, + "`from_key` can't be equal to `to_key`"); + + GOLOS_CHECK_PARAM(nonce, { + GOLOS_CHECK_VALUE(nonce != 0, "Nonce can't be zero"); + }); + + GOLOS_CHECK_PARAM(encrypted_message, { + GOLOS_CHECK_VALUE(encrypted_message.size() >= 16, "Encrypted message is too small"); + }); + } + + void private_message_operation::get_required_posting_authorities(flat_set& a) const { + a.insert(from); + } + + + void private_delete_message_operation::validate() const { + GOLOS_CHECK_PARAM(from, { + if (from.size()) { + validate_account_name(from); + } + }); + + GOLOS_CHECK_PARAM(to, { + if (to.size()) { + validate_account_name(to); + GOLOS_CHECK_VALUE(to != from, "You cannot delete messages to yourself"); + } + }); + + GOLOS_CHECK_PARAM(requester, { + validate_account_name(requester); + if (to.size() || from.size()) { + GOLOS_CHECK_VALUE(requester == to || requester == from, + "`requester` can delete messages only from his inbox/outbox"); + } + }); + + GOLOS_CHECK_PARAM(start_date, { + GOLOS_CHECK_VALUE(start_date <= stop_date, "`start_date` can't be greater then to_time"); + }); + + GOLOS_CHECK_PARAM(nonce, { + if (nonce != 0) { + GOLOS_CHECK_VALUE(to.size(), "to and nonce should be set both"); + GOLOS_CHECK_VALUE(start_date == time_point_sec::min(), "nonce and start_date can't be used together"); + GOLOS_CHECK_VALUE(stop_date == time_point_sec::min(), "nonce and stop_date can't be used together"); + } + }); + } + + void private_delete_message_operation::get_required_posting_authorities(flat_set& a) const { + a.insert(requester); + } + + + void private_mark_message_operation::validate() const { + GOLOS_CHECK_PARAM_ACCOUNT(to); + + GOLOS_CHECK_PARAM(from, { + if (from.size()) { + validate_account_name(from); + GOLOS_CHECK_VALUE(to != from, "You cannot mark messages to yourself"); + } + }); + + GOLOS_CHECK_PARAM(start_date, { + GOLOS_CHECK_VALUE(start_date <= stop_date, "`start_date` can't be greater then `stop_date`"); + }); + + GOLOS_CHECK_PARAM(nonce, { + if (nonce != 0) { + GOLOS_CHECK_VALUE(to.size(), "Non-zero 'nonce' requires 'to' to be set too"); + GOLOS_CHECK_VALUE(start_date == time_point_sec::min(), "Non-zero `nonce` can't be used with `start_date`"); + GOLOS_CHECK_VALUE(stop_date == time_point_sec::min(), "Non-zero `nonce` can't be used with `stop_date`"); + } + }); + } + + void private_mark_message_operation::get_required_posting_authorities(flat_set& a) const { + a.insert(to); + } + + + void private_contact_operation::validate() const { + GOLOS_CHECK_PARAM_ACCOUNT(contact); + GOLOS_CHECK_PARAM_ACCOUNT(owner); + + GOLOS_CHECK_LOGIC(contact != owner, + logic_errors::cannot_add_contact_to_yourself, + "You add contact to yourself"); + + GOLOS_CHECK_PARAM(type, { + GOLOS_CHECK_VALUE(is_valid_contact_type(type), "Unknown contact type"); + }); + + if (json_metadata.size() > 0) { + GOLOS_CHECK_PARAM(json_metadata, { + GOLOS_CHECK_VALUE(fc::json::is_valid(json_metadata), "JSON Metadata not valid JSON"); + GOLOS_CHECK_VALUE(type != unknown, "JSON Metadata can't be set for undefined contact"); + }); + } + } + + void private_contact_operation::get_required_posting_authorities(flat_set& a) const { + a.insert(owner); + } + +} } } // golos::plugins::private_message + +DEFINE_OPERATION_TYPE(golos::plugins::private_message::private_message_plugin_operation); \ No newline at end of file diff --git a/plugins/private_message/private_message_plugin.cpp b/plugins/private_message/private_message_plugin.cpp index 3aa2e7ed68..55fdd2621e 100644 --- a/plugins/private_message/private_message_plugin.cpp +++ b/plugins/private_message/private_message_plugin.cpp @@ -1,5 +1,8 @@ #include #include +#include +#include +#include #include #include @@ -9,6 +12,7 @@ #include +#include // template @@ -16,7 +20,6 @@ T dejsonify(const std::string &s) { return fc::json::from_string(s).as(); } -#define DEFAULT_VALUE_VECTOR(value) default_value({fc::json::to_string(value)}, fc::json::to_string(value)) #define LOAD_VALUE_SET(options, name, container, type) \ if( options.count(name) ) { \ const std::vector& ops = options[name].as>(); \ @@ -24,181 +27,994 @@ if( options.count(name) ) { \ } // -namespace golos { - namespace plugins { - namespace private_message { +namespace golos { namespace plugins { namespace private_message { - class private_message_plugin::private_message_plugin_impl final { - public: - private_message_plugin_impl(private_message_plugin &_plugin) - : _self(_plugin) , - _db(appbase::app().get_plugin().db()){ - _custom_operation_interpreter = std::make_shared - < generic_custom_operation_interpreter > (_db); + struct callback_info final { + callback_query query; + std::shared_ptr msg; - _custom_operation_interpreter->register_evaluator(&_self); + callback_info() = default; + callback_info(callback_query&& q, std::shared_ptr m) + : query(q), + msg(m) { - _db.set_custom_operation_interpreter(_self.name(), _custom_operation_interpreter); - return; + } + }; + + class private_message_plugin::private_message_plugin_impl final { + public: + private_message_plugin_impl(private_message_plugin& plugin) + : db_(appbase::app().get_plugin().db()) { + + custom_operation_interpreter_ = std::make_shared + >(db_); + + auto coi = custom_operation_interpreter_.get(); + + using impl = private_message_plugin_impl; + + coi->register_evaluator>(this); + coi->register_evaluator>(this); + coi->register_evaluator>(this); + coi->register_evaluator>(this); + coi->register_evaluator>(this); + + db_.set_custom_operation_interpreter(plugin.name(), custom_operation_interpreter_); + } + + template + std::vector get_message_box( + const std::string& account, const message_box_query&, Filter&&) const; + + std::vector get_thread( + const std::string& from, const std::string& to, const message_thread_query&) const; + + settings_api_object get_settings(const std::string& owner) const; + + contact_api_object get_contact_item(const contact_object& o) const; + + contact_api_object get_contact_info(const std::string& owner, const std::string& contact) const; + + contacts_size_api_object get_contacts_size(const std::string& owner) const; + + std::vector get_contacts( + const std::string& owner, const private_contact_type, uint16_t limit, uint32_t offset) const; + + void call_callbacks( + const callback_event_type, const account_name_type& from, const account_name_type& to, fc::variant); + + bool can_call_callbacks() const; + + ~private_message_plugin_impl() = default; + + bool is_tracked_account(account_name_type) const; + + std::shared_ptr> custom_operation_interpreter_; + flat_map tracked_account_ranges_; + flat_set tracked_account_list_; + + golos::chain::database& db_; + + std::mutex callbacks_mutex_; + std::list callbacks_; + }; + + static inline time_point_sec min_create_date() { + return time_point_sec(1); + } + + template + std::vector private_message_plugin::private_message_plugin_impl::get_message_box( + const std::string& to, const message_box_query& query, GetAccount&& get_account + ) const { + std::vector result; + const auto& idx = db_.get_index().indices().get(); + auto newest_date = query.newest_date; + + if (newest_date == time_point_sec::min()) { + newest_date = db_.head_block_time(); + } + + auto itr = idx.lower_bound(std::make_tuple(to, newest_date)); + auto etr = idx.upper_bound(std::make_tuple(to, min_create_date())); + auto offset = query.offset; + + auto filter = [&](const message_object& o) -> bool { + auto& account = get_account(o); + return + (query.select_accounts.empty() || query.select_accounts.count(account)) && + (query.filter_accounts.empty() || !query.filter_accounts.count(account)) && + (!query.unread_only || o.read_date == time_point_sec::min()); + }; + + for (; itr != etr && offset; ++itr) { + if (filter(*itr)){ + --offset; + } + } + + auto limit = query.limit; + + if (!limit) { + limit = PRIVATE_DEFAULT_LIMIT; + } + + result.reserve(limit); + for (; itr != etr && result.size() < limit; ++itr) { + if (filter(*itr)) { + result.emplace_back(*itr); + } + } + + return result; + } + + std::vector private_message_plugin::private_message_plugin_impl::get_thread( + const std::string& from, const std::string& to, const message_thread_query& query + ) const { + + std::vector result; + const auto& outbox_idx = db_.get_index().indices().get(); + const auto& inbox_idx = db_.get_index().indices().get(); + + auto outbox_itr = outbox_idx.lower_bound(std::make_tuple(from, to, query.newest_date)); + auto outbox_etr = outbox_idx.upper_bound(std::make_tuple(from, to, min_create_date())); + auto inbox_itr = inbox_idx.lower_bound(std::make_tuple(from, to, query.newest_date)); + auto inbox_etr = inbox_idx.upper_bound(std::make_tuple(from, to, min_create_date())); + auto offset = query.offset; + + auto filter = [&](const message_object& o) { + return (!query.unread_only || o.read_date == time_point_sec::min()); + }; + + auto itr_to_message = [&](auto& itr) -> const message_object& { + const message_object& result = *itr; + ++itr; + return result; + }; + + auto select_message = [&]() -> const message_object& { + if (outbox_itr != outbox_etr) { + if (inbox_itr == inbox_etr || outbox_itr->id > inbox_itr->id) { + return itr_to_message(outbox_itr); } + } + return itr_to_message(inbox_itr); + }; - vector get_inbox(const std::string& to, time_point newest, uint16_t limit, std::uint64_t offset) const; + auto is_not_done = [&]() -> bool { + return outbox_itr != outbox_etr || inbox_itr != inbox_etr; + }; - vector get_outbox(const std::string& from, time_point newest, uint16_t limit, std::uint64_t offset) const; + while (is_not_done() && offset) { + auto& message = select_message(); + if (filter(message)){ + --offset; + } + } + result.reserve(query.limit); + while (is_not_done() && result.size() < query.limit) { + auto& message = select_message(); + if (filter(message)) { + result.emplace_back(message); + } + } - ~private_message_plugin_impl() {}; + return result; + } - private_message_plugin &_self; - std::shared_ptr > _custom_operation_interpreter; - flat_map _tracked_accounts; + settings_api_object private_message_plugin::private_message_plugin_impl::get_settings( + const std::string& owner + ) const { + const auto& idx = db_.get_index().indices().get(); + auto itr = idx.find(owner); + if (itr != idx.end()) { + return settings_api_object(*itr); + } - golos::chain::database &_db; - }; + return settings_api_object(); + } - vector private_message_plugin::private_message_plugin_impl::get_inbox( - const std::string& to, time_point newest, uint16_t limit, std::uint64_t offset) const { - FC_ASSERT(limit <= 100); + contact_api_object private_message_plugin::private_message_plugin_impl::get_contact_item( + const contact_object& o + ) const { + contact_api_object result(o); - vector result; - const auto &idx = _db.get_index().indices().get(); - auto itr = idx.lower_bound(std::make_tuple(to, newest)); + const auto& idx = db_.get_index().indices().get(); + auto itr = idx.find(std::make_tuple(o.contact, o.owner)); - if (idx.size() > offset) { - while (itr != idx.end() && offset && itr->to == to) { - ++itr; - --offset; - } + if (idx.end() != itr) { + result.remote_type = itr->type; + } + + return result; + } + + contact_api_object private_message_plugin::private_message_plugin_impl::get_contact_info( + const std::string& owner, const std::string& contact + ) const { + const auto& idx = db_.get_index().indices().get(); + auto itr = idx.find(std::make_tuple(owner, contact)); + + if (itr != idx.end()) { + return get_contact_item(*itr); + } + return contact_api_object(); + } + + contacts_size_api_object private_message_plugin::private_message_plugin_impl::get_contacts_size( + const std::string& owner + ) const { + contacts_size_api_object result; + + const auto& idx = db_.get_index().indices().get(); + auto itr = idx.lower_bound(std::make_tuple(owner, unknown)); + auto etr = idx.upper_bound(std::make_tuple(owner, private_contact_type_size)); + + for (; etr != itr; ++itr) { + result.size[itr->type] = itr->size; + } + + for (uint8_t i = unknown; i < private_contact_type_size; ++i) { + auto t = static_cast(i); + if (!result.size.count(t)) { + result.size[t] = contacts_size_info(); + } + } + + return result; + } + + std::vector private_message_plugin::private_message_plugin_impl::get_contacts( + const std::string& owner, const private_contact_type type, uint16_t limit, uint32_t offset + ) const { + std::vector result; + + result.reserve(limit); + + const auto& idx = db_.get_index().indices().get(); + auto itr = idx.lower_bound(std::make_tuple(owner, type)); + auto etr = idx.upper_bound(std::make_tuple(owner, type)); + + for (; itr != etr && offset; ++itr, --offset); + + for (; itr != etr; ++itr) { + result.push_back(get_contact_item(*itr)); + } + return result; + } + + bool private_message_plugin::private_message_plugin_impl::can_call_callbacks() const { + return !db_.is_producing() && !callbacks_.empty(); + } + + void private_message_plugin::private_message_plugin_impl::call_callbacks( + const callback_event_type event, const account_name_type& from, const account_name_type& to, fc::variant r + ) { + std::lock_guard lock(callbacks_mutex_); + for (auto itr = callbacks_.begin(); callbacks_.end() != itr; ) { + auto& info = *itr; + + if (info.query.filter_events.count(event) || + (!info.query.select_events.empty() && !info.query.select_events.count(event)) || + info.query.filter_accounts.count(from) || + info.query.filter_accounts.count(to) || + (to.size() && !info.query.select_accounts.empty() && !info.query.select_accounts.count(to)) || + (from.size() && !info.query.select_accounts.empty() && !info.query.select_accounts.count(from)) + ) { + ++itr; + continue; + } + + try { + info.msg->unsafe_result(r); + ++itr; + } catch (...) { + callbacks_.erase(itr++); + } + } + } + + template + void private_message_evaluator::do_apply(const private_message_operation& pm) { + if (!impl_->is_tracked_account(pm.from) && !impl_->is_tracked_account(pm.to)) { + return; + } + + database& d = impl_->db_; + auto& contact_idx = d.get_index().indices().get(); + auto contact_itr = contact_idx.find(std::make_tuple(pm.to, pm.from)); + + auto& cfg_idx = d.get_index().indices().get(); + auto cfg_itr = cfg_idx.find(pm.to); + + d.get_account(pm.to); + + GOLOS_CHECK_LOGIC(contact_itr == contact_idx.end() || contact_itr->type != ignored, + logic_errors::sender_in_ignore_list, + "Sender is in the ignore list of recipient"); + + GOLOS_CHECK_LOGIC( + (cfg_itr == cfg_idx.end() || !cfg_itr->ignore_messages_from_unknown_contact) || + (contact_itr != contact_idx.end() && contact_itr->type == pinned), + logic_errors::recepient_ignores_messages_from_unknown_contact, + "Recipient accepts messages only from his contact list"); + + auto& id_idx = d.get_index().indices().get(); + auto id_itr = id_idx.find(std::make_tuple(pm.from, pm.to, pm.nonce)); + + if (pm.update && id_itr == id_idx.end()) { + GOLOS_THROW_MISSING_OBJECT("private_message", + fc::mutable_variant_object()("from", pm.from)("to", pm.to)("nonce", pm.nonce)); + } else if (!pm.update && id_itr != id_idx.end()){ + GOLOS_THROW_OBJECT_ALREADY_EXIST("private_message", + fc::mutable_variant_object()("from", pm.from)("to", pm.to)("nonce", pm.nonce)); + } + + auto now = d.head_block_time(); + + auto set_message = [&](message_object& pmo) { + pmo.from_memo_key = pm.from_memo_key; + pmo.to_memo_key = pm.to_memo_key; + pmo.checksum = pm.checksum; + pmo.read_date = time_point_sec::min(); + pmo.receive_date = now; + pmo.encrypted_message.resize(pm.encrypted_message.size()); + std::copy( + pm.encrypted_message.begin(), pm.encrypted_message.end(), + pmo.encrypted_message.begin()); + }; + + if (id_itr == id_idx.end()) { + d.create([&](message_object& pmo) { + pmo.from = pm.from; + pmo.to = pm.to; + pmo.nonce = pm.nonce; + pmo.inbox_create_date = now; + pmo.outbox_create_date = now; + pmo.remove_date = time_point_sec::min(); + set_message(pmo); + }); + id_itr = id_idx.find(std::make_tuple(pm.from, pm.to, pm.nonce)); + } else { + d.modify(*id_itr, set_message); + } + + if (this->impl_->can_call_callbacks()) { + this->impl_->call_callbacks( + callback_event_type::message, pm.from, pm.to, + fc::variant(callback_message_event({callback_event_type::message, message_api_object(*id_itr)}))); + } + + // Ok, now update contact lists and counters in them + auto& size_idx = d.get_index().indices().get(); + + // Increment counters depends on side of communication + auto inc_counters = [&](auto& o, const bool is_send) { + if (is_send) { + o.total_outbox_messages++; + o.unread_outbox_messages++; + } else { + o.total_inbox_messages++; + o.unread_inbox_messages++; + } + }; + + // Update global counters by type of contact + auto modify_size = [&](auto& owner, auto type, const bool is_new_contact, const bool is_send) { + auto modify_counters = [&](auto& pcso) { + inc_counters(pcso.size, is_send); + if (is_new_contact) { + pcso.size.total_contacts++; } + }; - while (itr != idx.end() && limit && itr->to == to) { - result.push_back(*itr); - ++itr; - --limit; + auto size_itr = size_idx.find(std::make_tuple(owner, type)); + if (size_idx.end() == size_itr) { + d.create([&](auto& pcso){ + pcso.owner = owner; + pcso.type = type; + modify_counters(pcso); + }); + } else { + d.modify(*size_itr, modify_counters); + } + }; + + // Add contact list if it doesn't exist or update it if it exits + auto modify_contact = [&](auto& owner, auto& contact, auto type, const bool is_send) { + bool is_new_contact; + auto contact_itr = contact_idx.find(std::make_tuple(owner, contact)); + if (contact_idx.end() != contact_itr) { + d.modify(*contact_itr, [&](auto& pco) { + inc_counters(pco.size, is_send); + }); + is_new_contact = false; + type = contact_itr->type; + } else { + d.create([&](auto& pco) { + pco.owner = owner; + pco.contact = contact; + pco.type = type; + inc_counters(pco.size, is_send); + }); + is_new_contact = true; + + if (this->impl_->can_call_callbacks()) { + contact_itr = contact_idx.find(std::make_tuple(owner, contact)); + this->impl_->call_callbacks( + callback_event_type::contact, owner, contact, + fc::variant(callback_contact_event( + {callback_event_type::contact, contact_api_object(*contact_itr)}))); } + } + modify_size(owner, type, is_new_contact, is_send); + }; - return result; + modify_contact(pm.from, pm.to, pinned, true); + modify_contact(pm.to, pm.from, unknown, false); + } + + template + bool process_private_messages(database& db, const Operation& po, Action&& action, Args&&... args) { + auto start_date = std::max(po.start_date, min_create_date()); + auto stop_date = std::max(po.stop_date, min_create_date()); + + auto& idx = db.get_index().indices().get(); + auto itr = idx.lower_bound(std::make_tuple(std::forward(args)..., stop_date)); + auto etr = idx.lower_bound(std::make_tuple(std::forward(args)..., start_date)); + + if (itr == etr) { + return false; + } + + while (itr != etr) { + auto& message = (*itr); + ++itr; + if (!action(message)) { + break; + } + } + return true; + } + + template + void process_group_message_operation( + database& db, const Operation& po, const std::string& requester, + Map& map, ProcessAction&& process_action, ContactAction&& contact_action + ) { + if (po.nonce != 0) { + auto& idx = db.get_index().indices().get(); + auto itr = idx.find(std::make_tuple(po.from, po.to, po.nonce)); + + if (itr == idx.end()) { + GOLOS_THROW_MISSING_OBJECT("private_message", + fc::mutable_variant_object()("from", po.from)("to", po.to)("nonce", po.nonce)); } - vector private_message_plugin::private_message_plugin_impl::get_outbox( - const std::string& from, time_point newest, uint16_t limit, std::uint64_t offset) const { - FC_ASSERT(limit <= 100); + process_action(*itr); + } else if (po.from.size() && po.to.size()) { + if (!process_private_messages(db, po, process_action, po.from, po.to)) { + GOLOS_THROW_MISSING_OBJECT("private_message", + fc::mutable_variant_object()("from", po.from)("to", po.to) + ("start_date", po.start_date)("stop_date", po.stop_date)); + } + } else if (po.from.size()) { + if (!process_private_messages(db, po, process_action, po.from)) { + GOLOS_THROW_MISSING_OBJECT("private_message", + fc::mutable_variant_object()("from", po.from) + ("start_date", po.start_date)("stop_date", po.stop_date)); + } + } else if (po.to.size()) { + if (!process_private_messages(db, po, process_action, po.to)) { + GOLOS_THROW_MISSING_OBJECT("private_message", + fc::mutable_variant_object()("to", po.to) + ("start_date", po.start_date)("stop_date", po.stop_date)); + } + } else { + if (!process_private_messages(db, po, process_action, requester) && + !process_private_messages(db, po, process_action, requester) + ) { + GOLOS_THROW_MISSING_OBJECT("private_message", + fc::mutable_variant_object()("requester", requester) + ("start_date", po.start_date)("stop_date", po.stop_date)); + } + } - vector result; - const auto &idx = _db.get_index().indices().get(); + auto& contact_idx = db.get_index().indices().get(); + auto& size_idx = db.get_index().indices().get(); - auto itr = idx.lower_bound(std::make_tuple(from, newest)); + for (const auto& stat_info: map) { + const auto& owner = std::get<0>(stat_info.first); + const auto& size = stat_info.second; + auto contact_itr = contact_idx.find(stat_info.first); + auto size_itr = size_idx.find(owner); - if (idx.size() > offset) { - while (itr != idx.end() && offset && itr->from == from) { - ++itr; - --offset; + FC_ASSERT(contact_idx.end() != contact_itr && size_idx.end() != size_itr, "Invalid size"); + + if (!contact_action(*contact_itr, *size_itr, size)) { + db.modify(*contact_itr, [&](auto& pco) { + pco.size -= size; + }); + db.modify(*size_itr, [&](auto& pcso){ + pcso.size -= size; + }); + } + } + } + + template + void private_delete_message_evaluator::do_apply(const private_delete_message_operation& pdm) { + if (!impl_->is_tracked_account(pdm.from) && + !impl_->is_tracked_account(pdm.to) && + !impl_->is_tracked_account(pdm.requester) + ) { + return; + } + + database& d = impl_->db_; + auto now = d.head_block_time(); + fc::flat_map, contact_size_info> stat_map; + + process_group_message_operation( + d, pdm, pdm.requester, stat_map, + /* process_action */ + [&](const message_object& m) -> bool { + uint32_t unread_messages = 0; + + if (m.read_date == time_point_sec::min()) { + unread_messages = 1; + } + if (pdm.requester == pdm.to) { + // remove from inbox + if (m.inbox_create_date == time_point_sec::min()) { + return false; } + auto& inbox_stat = stat_map[std::make_tuple(m.to, m.from)]; + inbox_stat.unread_inbox_messages += unread_messages; + inbox_stat.total_inbox_messages++; + } else { + // remove from outbox + if (m.outbox_create_date == time_point_sec::min()) { + return false; + } + auto& outbox_stat = stat_map[std::make_tuple(m.from, m.to)]; + outbox_stat.unread_outbox_messages += unread_messages; + outbox_stat.total_outbox_messages++; } - while (itr != idx.end() && limit && itr->from == from) { - result.push_back(*itr); - ++itr; - --limit; + if (this->impl_->can_call_callbacks()) { + message_api_object ma(m); + ma.remove_date = now; + + if (pdm.requester == pdm.to) { + this->impl_->call_callbacks( + callback_event_type::remove_inbox, m.from, m.to, + fc::variant(callback_message_event( + {callback_event_type::remove_inbox, ma}))); + } else { + this->impl_->call_callbacks( + callback_event_type::remove_outbox, m.from, m.to, + fc::variant(callback_message_event( + {callback_event_type::remove_outbox, ma}))); + } } - return result; - } - - void private_message_evaluator::do_apply(const private_message_operation &pm) { - database &d = db(); - - const flat_map &tracked_accounts = _plugin->tracked_accounts(); - - auto to_itr = tracked_accounts.lower_bound(pm.to); - auto from_itr = tracked_accounts.lower_bound(pm.from); - - FC_ASSERT(pm.from != pm.to); - FC_ASSERT(pm.from_memo_key != pm.to_memo_key); - FC_ASSERT(pm.sent_time != 0); - FC_ASSERT(pm.encrypted_message.size() >= 32); - - if (!tracked_accounts.size() || - (to_itr != tracked_accounts.end() && pm.to >= to_itr->first && - pm.to <= to_itr->second) || - (from_itr != tracked_accounts.end() && - pm.from >= from_itr->first && pm.from <= from_itr->second)) { - d.create([&](message_object &pmo) { - pmo.from = pm.from; - pmo.to = pm.to; - pmo.from_memo_key = pm.from_memo_key; - pmo.to_memo_key = pm.to_memo_key; - pmo.checksum = pm.checksum; - pmo.sent_time = pm.sent_time; - pmo.receive_time = d.head_block_time(); - pmo.encrypted_message.resize(pm.encrypted_message.size()); - std::copy(pm.encrypted_message.begin(), pm.encrypted_message.end(), - pmo.encrypted_message.begin()); + + if (m.remove_date == time_point_sec::min()) { + d.modify(m, [&](auto& m) { + m.remove_date = now; + if (pdm.requester == pdm.to) { + m.inbox_create_date = time_point_sec::min(); // remove message from find requests + } else { + m.outbox_create_date = time_point_sec::min(); // remove message from find requests + } }); + } else { + d.remove(m); } + return true; + }, + /* contact_action */ + [&](const contact_object& co, const contact_size_object& so, const contact_size_info& size) -> bool { + if (co.size != size || co.type != unknown) { + return false; + } + d.remove(co); + if (so.size.total_contacts == 1) { + d.remove(so); + } else { + d.modify(so, [&](auto& pcso) { + pcso.size.total_contacts--; + pcso.size -= size; + }); + } + return true; } + ); + } - private_message_plugin::private_message_plugin(){ - } + template + void private_mark_message_evaluator::do_apply(const private_mark_message_operation& pmm) { + if (!impl_->is_tracked_account(pmm.from) && !impl_->is_tracked_account(pmm.to)) { + return; + } - private_message_plugin::~private_message_plugin() { - } + database& d = impl_->db_; - void private_message_plugin::set_program_options( - boost::program_options::options_description &cli, - boost::program_options::options_description &cfg) { - cli.add_options() - ("pm-account-range", - boost::program_options::value < std::vector < std::string >> ()->composing()->multitoken(), - "Defines a range of accounts to private messages to/from as a json pair [\"from\",\"to\"] [from,to)"); - cfg.add(cli); - } + uint32_t total_marked_messages = 0; + auto now = d.head_block_time(); + fc::flat_map, contact_size_info> stat_map; - void private_message_plugin::plugin_initialize(const boost::program_options::variables_map &options) { - ilog("Intializing private message plugin"); - my.reset(new private_message_plugin::private_message_plugin_impl(*this)); + process_group_message_operation( + d, pmm, "", stat_map, + /* process_action */ + [&](const message_object& m) -> bool { + if (m.read_date != time_point_sec::min()) { + return true; + } + // only recipient can mark messages + stat_map[std::make_tuple(m.to, m.from)].unread_inbox_messages++; + // if sender hasn't yet removed the message + if (m.remove_date == time_point_sec::min()) { + stat_map[std::make_tuple(m.from, m.to)].unread_outbox_messages++; + } + total_marked_messages++; - add_plugin_index(my->_db); + d.modify(m, [&](message_object& m){ + m.read_date = now; + }); - typedef pair pairstring; - LOAD_VALUE_SET(options, "pm-accounts", my->_tracked_accounts, pairstring); - JSON_RPC_REGISTER_API(name()) + if (this->impl_->can_call_callbacks()) { + this->impl_->call_callbacks( + callback_event_type::mark, m.from, m.to, + fc::variant(callback_message_event({callback_event_type::mark, message_api_object(m)}))); + } + return true; + }, + + /* contact_action */ + [&](const contact_object&, const contact_size_object&, const contact_size_info&) -> bool { + return false; } + ); + + GOLOS_CHECK_LOGIC(total_marked_messages > 0, + logic_errors::no_unread_messages, + "No unread messages in requested range"); + } + + template + void private_settings_evaluator::do_apply(const private_settings_operation& ps) { + if (!impl_->is_tracked_account(ps.owner)) { + return; + } + + database& d = impl_->db_; + + auto& idx = d.get_index().indices().get(); + auto itr = idx.find(ps.owner); + + auto set_settings = [&](settings_object& pso) { + pso.owner = ps.owner; + pso.ignore_messages_from_unknown_contact = ps.ignore_messages_from_unknown_contact; + }; + + if (idx.end() != itr) { + d.modify(*itr, set_settings); + } else { + d.create(set_settings); + } + } + + template + void private_contact_evaluator::do_apply(const private_contact_operation& pc) { + if (!impl_->is_tracked_account(pc.owner) && !impl_->is_tracked_account(pc.contact)) { + return; + } + + database& d = impl_->db_; + + auto& contact_idx = d.get_index().indices().get(); + auto contact_itr = contact_idx.find(std::make_tuple(pc.owner, pc.contact)); + + d.get_account(pc.contact); + + GOLOS_CHECK_LOGIC(contact_idx.end() != contact_itr || pc.type != unknown, + logic_errors::add_unknown_contact, + "Can't add unknown contact"); - void private_message_plugin::plugin_startup() { - ilog("Starting up private message plugin"); + std::string json_metadata(contact_itr->json_metadata.begin(), contact_itr->json_metadata.end()); + GOLOS_CHECK_LOGIC(contact_itr->type != pc.type || pc.json_metadata != json_metadata, + logic_errors::contact_has_not_changed, + "Contact hasn't changed"); + + auto& owner_idx = d.get_index().indices().get(); + auto dst_itr = owner_idx.find(std::make_tuple(pc.owner, pc.type)); + + if (contact_idx.end() != contact_itr) { + auto src_itr = owner_idx.find(std::make_tuple(pc.owner, contact_itr->type)); + if (contact_itr->type != pc.type) { + // last contact + if (src_itr->size.total_contacts == 1) { + d.remove(*src_itr); + } else { + d.modify(*src_itr, [&](auto& src) { + src.size.total_contacts--; + src.size -= contact_itr->size; + }); + } + + // has messages or type is not unknown + if (!contact_itr->size.empty() || pc.type != unknown) { + auto modify_counters = [&](auto& dst) { + dst.size.total_contacts++; + dst.size += contact_itr->size; + }; + + if (owner_idx.end() == dst_itr) { + d.create([&](auto& dst) { + dst.owner = pc.owner; + dst.type = pc.type; + modify_counters(dst); + }); + } else { + d.modify(*dst_itr, modify_counters); + } + } } - void private_message_plugin::plugin_shutdown() { - ilog("Shuting down private message plugin"); + // contact is unknown and no messages + if (pc.type == unknown && contact_itr->size.empty()) { + d.remove(*contact_itr); + } else { + d.modify(*contact_itr, [&](auto& plo) { + plo.type = pc.type; + from_string(plo.json_metadata, pc.json_metadata); + }); + } + } else if (pc.type != unknown) { + d.create([&](auto& plo){ + plo.owner = pc.owner; + plo.contact = pc.contact; + plo.type = pc.type; + from_string(plo.json_metadata, pc.json_metadata); + }); + + contact_itr = contact_idx.find(std::make_tuple(pc.owner, pc.contact)); + + if (owner_idx.end() == dst_itr) { + d.create([&](auto& pcso) { + pcso.owner = pc.owner; + pcso.type = pc.type; + pcso.size.total_contacts = 1; + }); + } else { + d.modify(*dst_itr, [&](auto& pcso) { + pcso.size.total_contacts++; + }); } + } + + if (this->impl_->can_call_callbacks()) { + this->impl_->call_callbacks( + callback_event_type::contact, pc.owner, pc.contact, + fc::variant(callback_contact_event( + {callback_event_type::contact, contact_api_object(*contact_itr)}))); + } + } + + private_message_plugin::private_message_plugin() = default; + + private_message_plugin::~private_message_plugin() = default; + + const std::string& private_message_plugin::name() { + static std::string name = "private_message"; + return name; + } + + void private_message_plugin::set_program_options( + boost::program_options::options_description& cli, + boost::program_options::options_description& cfg + ) { + cfg.add_options() + ("pm-account-range", + boost::program_options::value>()->composing()->multitoken(), + "Defines a range of accounts to private messages to/from as a json pair [\"from\",\"to\"] [from,to]") + ("pm-account-list", + boost::program_options::value>()->composing()->multitoken(), + "Defines a list of accounts to private messages to/from"); + } + + void private_message_plugin::plugin_initialize(const boost::program_options::variables_map &options) { + ilog("Intializing private message plugin"); + my = std::make_unique(*this); - flat_map private_message_plugin::tracked_accounts() const { - return my->_tracked_accounts; + add_plugin_index(my->db_); + add_plugin_index(my->db_); + add_plugin_index(my->db_); + add_plugin_index(my->db_); + + using pairstring = std::pair; + LOAD_VALUE_SET(options, "pm-account-range", my->tracked_account_ranges_, pairstring); + if (options.count("pm-account-list")) { + auto list = options["pm-account-list"].as>(); + my->tracked_account_list_.insert(list.begin(), list.end()); + } + JSON_RPC_REGISTER_API(name()) + } + + void private_message_plugin::plugin_startup() { + ilog("Starting up private message plugin"); + } + + void private_message_plugin::plugin_shutdown() { + ilog("Shuting down private message plugin"); + } + + bool private_message_plugin::private_message_plugin_impl::is_tracked_account(account_name_type name) const { + if (tracked_account_ranges_.empty() && tracked_account_list_.empty()) { + return true; + } + + auto list_itr = tracked_account_list_.find(name); + if (tracked_account_list_.end() != list_itr) { + return true; + } + + auto range_itr = tracked_account_ranges_.lower_bound(name); + return tracked_account_ranges_.end() != range_itr && name >= range_itr->first && name <= range_itr->second; + } + + // Api Defines + + DEFINE_API(private_message_plugin, get_inbox) { + PLUGIN_API_VALIDATE_ARGS( + (std::string, to) + (message_box_query, query) + ); + + GOLOS_CHECK_LIMIT_PARAM(query.limit, PRIVATE_DEFAULT_LIMIT); + + GOLOS_CHECK_PARAM(query.filter_accounts, { + for (auto& itr : query.filter_accounts) { + GOLOS_CHECK_VALUE(!query.select_accounts.count(itr), + "Can't filter and select accounts '${account}' at the same time", + ("account", itr)); } + }); - // Api Defines + return my->db_.with_weak_read_lock([&]() { + return my->get_message_box( + to, query, + [&](const message_object& o) -> const account_name_type& { + return o.from; + } + ); + }); + } - DEFINE_API(private_message_plugin, get_inbox) { - auto to = args.args->at(0).as(); - auto newest = args.args->at(1).as(); - auto limit = args.args->at(2).as(); - auto offset = args.args->at(3).as(); - auto &db = my->_db; - return db.with_weak_read_lock([&]() { - return my->get_inbox(to, newest, limit, offset); - }); + DEFINE_API(private_message_plugin, get_outbox) { + PLUGIN_API_VALIDATE_ARGS( + (std::string, from) + (message_box_query, query) + ); + + GOLOS_CHECK_LIMIT_PARAM(query.limit, PRIVATE_DEFAULT_LIMIT); + + GOLOS_CHECK_PARAM(query.filter_accounts, { + for (auto& itr : query.filter_accounts) { + GOLOS_CHECK_VALUE(!query.select_accounts.count(itr), + "Can't filter and select accounts '${account}' at the same time", + ("account", itr)); } + }); - DEFINE_API(private_message_plugin, get_outbox) { - auto from = args.args->at(0).as(); - auto newest = args.args->at(1).as(); - auto limit = args.args->at(2).as(); - auto offset = args.args->at(3).as(); - auto &db = my->_db; - return db.with_weak_read_lock([&]() { - return my->get_outbox(from, newest, limit, offset); + return my->db_.with_weak_read_lock([&]() { + return my->get_message_box( + from, query, + [&](const message_object& o) -> const account_name_type& { + return o.to; }); - } + }); + } + + DEFINE_API(private_message_plugin, get_thread) { + PLUGIN_API_VALIDATE_ARGS( + (std::string, from) + (std::string, to) + (message_thread_query, query) + ); + + GOLOS_CHECK_LIMIT_PARAM(query.limit, PRIVATE_DEFAULT_LIMIT); + + if (!query.limit) { + query.limit = PRIVATE_DEFAULT_LIMIT; + } + + if (query.newest_date == time_point_sec::min()) { + query.newest_date = my->db_.head_block_time(); } + + return my->db_.with_weak_read_lock([&]() { + return my->get_thread(from, to, query); + }); + } + + DEFINE_API(private_message_plugin, get_settings) { + PLUGIN_API_VALIDATE_ARGS( + (std::string, owner) + ); + + return my->db_.with_weak_read_lock([&](){ + return my->get_settings(owner); + }); + } + + DEFINE_API(private_message_plugin, get_contacts_size) { + PLUGIN_API_VALIDATE_ARGS( + (std::string, owner) + ); + + return my->db_.with_weak_read_lock([&](){ + return my->get_contacts_size(owner); + }); } -} // golos::plugins::private_message + + DEFINE_API(private_message_plugin, get_contact_info) { + PLUGIN_API_VALIDATE_ARGS( + (std::string, owner) + (std::string, contact) + ); + + return my->db_.with_weak_read_lock([&](){ + return my->get_contact_info(owner, contact); + }); + } + + DEFINE_API(private_message_plugin, get_contacts) { + PLUGIN_API_VALIDATE_ARGS( + (std::string, owner) + (private_contact_type, type) + (uint16_t, limit) + (uint32_t, offset) + ); + + GOLOS_CHECK_LIMIT_PARAM(limit, 100); + + return my->db_.with_weak_read_lock([&](){ + return my->get_contacts(owner, type, limit, offset); + }); + } + + DEFINE_API(private_message_plugin, set_callback) { + PLUGIN_API_VALIDATE_ARGS( + (callback_query, query) + ); + + GOLOS_CHECK_PARAM(query.filter_accounts, { + for (auto& itr : query.filter_accounts) { + GOLOS_CHECK_VALUE(!query.select_accounts.count(itr), + "Can't filter and select accounts '${account}' at the same time", + ("account", itr)); + } + }); + + GOLOS_CHECK_PARAM(query.filter_events, { + for (auto& itr : query.filter_events) { + GOLOS_CHECK_VALUE(!query.select_events.count(itr), + "Can't filter and select accounts '${event}' at the same time", + ("event", itr)); + } + }); + + json_rpc::msg_pack_transfer transfer(args); + { + std::lock_guard lock(my->callbacks_mutex_); + my->callbacks_.emplace_back(std::move(query), transfer.msg()); + }; + transfer.complete(); + return {}; + } + +} } } // golos::plugins::private_message diff --git a/plugins/raw_block/plugin.cpp b/plugins/raw_block/plugin.cpp index f0a35753b1..b167eb1786 100644 --- a/plugins/raw_block/plugin.cpp +++ b/plugins/raw_block/plugin.cpp @@ -1,8 +1,10 @@ #include #include #include +#include #include #include +#include namespace golos { namespace plugins { @@ -45,10 +47,12 @@ get_raw_block_r plugin::plugin_impl::get_raw_block(uint32_t block_num) { } DEFINE_API ( plugin, get_raw_block ) { - auto tmp = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (uint32_t, block_num) + ); auto &db = my->database(); return db.with_weak_read_lock([&]() { - return my->get_raw_block(tmp); + return my->get_raw_block(block_num); }); } diff --git a/plugins/social_network/include/golos/plugins/social_network/social_network.hpp b/plugins/social_network/include/golos/plugins/social_network/social_network.hpp index 5e81c944c7..f968d12353 100644 --- a/plugins/social_network/include/golos/plugins/social_network/social_network.hpp +++ b/plugins/social_network/include/golos/plugins/social_network/social_network.hpp @@ -6,14 +6,16 @@ #include #include #include +#include +#include namespace golos { namespace plugins { namespace social_network { using plugins::json_rpc::msg_pack; using golos::api::discussion; using golos::api::account_vote; using golos::api::vote_state; - using golos::api::comment_api_object; using namespace golos::chain; + using golos::api::comment_api_object; DEFINE_API_ARGS(get_content, msg_pack, discussion) DEFINE_API_ARGS(get_content_replies, msg_pack, std::vector) @@ -42,7 +44,7 @@ namespace golos { namespace plugins { namespace social_network { ~social_network(); void set_program_options( - boost::program_options::options_description&, + boost::program_options::options_description& cfg, boost::program_options::options_description& config_file_options ) override; @@ -53,8 +55,19 @@ namespace golos { namespace plugins { namespace social_network { void plugin_startup() override; void plugin_shutdown() override; + comment_api_object create_comment_api_object(const comment_object& o) const; + + const comment_content_object& get_comment_content(const comment_id_type& comment) const ; + const comment_content_object* find_comment_content(const comment_id_type& comment) const ; + + private: struct impl; std::unique_ptr pimpl; }; -} } } // golos::plugins::social_network \ No newline at end of file + +// Callback which is needed for correct work of discussion_helper + void fill_comment_info(const golos::chain::database& db, const comment_object& co, comment_api_object& cao); + std::string get_json_metadata(const golos::chain::database& db, const comment_object&); + +} } } // golos::plugins::social_network diff --git a/plugins/social_network/include/golos/plugins/social_network/social_network_types.hpp b/plugins/social_network/include/golos/plugins/social_network/social_network_types.hpp new file mode 100644 index 0000000000..a5dce1ae91 --- /dev/null +++ b/plugins/social_network/include/golos/plugins/social_network/social_network_types.hpp @@ -0,0 +1,150 @@ +#pragma once + +namespace golos { namespace plugins { namespace social_network { + using namespace golos::chain; + + #ifndef SOCIAL_NETWORK_SPACE_ID + #define SOCIAL_NETWORK_SPACE_ID 10 + #endif + + enum social_network_types { + comment_content_object_type = (SOCIAL_NETWORK_SPACE_ID << 8), + comment_last_update_object_type = (SOCIAL_NETWORK_SPACE_ID << 8) + 1, + comment_reward_object_type = (SOCIAL_NETWORK_SPACE_ID << 8) + 2 + }; + + + class comment_content_object + : public object { + public: + comment_content_object() = delete; + + template + comment_content_object(Constructor&& c, allocator a) + :title(a), body(a), json_metadata(a) { + c(*this); + } + + id_type id; + + comment_id_type comment; + + shared_string title; + shared_string body; + shared_string json_metadata; + + uint32_t block_number; + }; + + + using comment_content_id_type = object_id; + + struct by_comment; + struct by_block_number; + + using comment_content_index = multi_index_container< + comment_content_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member>, + ordered_non_unique, member>>, + allocator + >; + + class comment_last_update_object: public object { + public: + comment_last_update_object() = delete; + + template + comment_last_update_object(Constructor&& c, allocator a) { + c(*this); + } + + id_type id; + + comment_id_type comment; + account_name_type parent_author; + account_name_type author; + time_point_sec last_update; + time_point_sec active; ///< the last time this post was "touched" by voting or reply + + uint32_t block_number; + }; + + using comment_last_update_id_type = object_id; + + struct by_last_update; /// parent_auth, last_update + struct by_author_last_update; + + using comment_last_update_index = multi_index_container< + comment_last_update_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member>, + ordered_unique< + tag, + composite_key, + member, + member>, + composite_key_compare, std::greater, std::less>>, + ordered_unique< + tag, + composite_key, + member, + member>, + composite_key_compare, std::greater, std::less>>, + ordered_non_unique< + tag, + member> + >, + allocator + >; + + class comment_reward_object: public object { + public: + comment_reward_object() = delete; + + template + comment_reward_object(Constructor&& c, allocator a) { + c(*this); + } + + id_type id; + + comment_id_type comment; + asset total_payout_value{0, SBD_SYMBOL}; + share_type author_rewards = 0; + asset author_gbg_payout_value{0, SBD_SYMBOL}; + asset author_golos_payout_value{0, STEEM_SYMBOL}; + asset author_gests_payout_value{0, VESTS_SYMBOL}; + asset beneficiary_payout_value{0, SBD_SYMBOL}; + asset beneficiary_gests_payout_value{0, VESTS_SYMBOL}; + asset curator_payout_value{0, SBD_SYMBOL}; + asset curator_gests_payout_value{0, VESTS_SYMBOL}; + }; + + using comment_reward_id_type = object_id; + + using comment_reward_index = multi_index_container< + comment_reward_object, + indexed_by< + ordered_unique, member>, + ordered_unique, member>>, + allocator>; +} } } + + +CHAINBASE_SET_INDEX_TYPE( + golos::plugins::social_network::comment_content_object, + golos::plugins::social_network::comment_content_index +) + +CHAINBASE_SET_INDEX_TYPE( + golos::plugins::social_network::comment_last_update_object, + golos::plugins::social_network::comment_last_update_index) + +CHAINBASE_SET_INDEX_TYPE( + golos::plugins::social_network::comment_reward_object, + golos::plugins::social_network::comment_reward_index) \ No newline at end of file diff --git a/plugins/social_network/social_network.cpp b/plugins/social_network/social_network.cpp index 5b010cba9e..390c472ecb 100644 --- a/plugins/social_network/social_network.cpp +++ b/plugins/social_network/social_network.cpp @@ -3,50 +3,93 @@ #include #include #include + #include // These visitors creates additional tables, we don't really need them in LOW_MEM mode #include +#include +#include +#include +#include +#include -#define CHECK_ARG_SIZE(_S) \ - FC_ASSERT( \ - args.args->size() == _S, \ - "Expected #_S argument(s), was ${n}", \ - ("n", args.args->size()) ); - -#define CHECK_ARG_MIN_SIZE(_S, _M) \ - FC_ASSERT( \ - args.args->size() >= _S && args.args->size() <= _M, \ - "Expected #_S (maximum #_M) argument(s), was ${n}", \ - ("n", args.args->size()) ); +#include +#include -#define GET_OPTIONAL_ARG(_I, _T, _D) \ - (args.args->size() > _I) ? \ - (args.args->at(_I).as<_T>()) : \ - static_cast<_T>(_D) #ifndef DEFAULT_VOTE_LIMIT # define DEFAULT_VOTE_LIMIT 10000 #endif + +// Depth of comment information storage history. +struct comment_depth_params { + comment_depth_params() { + } + + bool miss_content() const { + return + has_comment_title_depth && !comment_title_depth && + has_comment_body_depth && !comment_body_depth && + has_comment_json_metadata_depth && !comment_json_metadata_depth; + } + + bool need_clear_content() const { + return has_comment_title_depth || has_comment_body_depth || has_comment_json_metadata_depth; + } + + bool need_clear_last_update() const { + return has_comment_last_update_depth; + } + + bool should_delete_whole_content_object(const uint32_t delta) const { + return + (has_comment_title_depth && delta > comment_title_depth) && + (has_comment_body_depth && delta > comment_body_depth) && + (has_comment_json_metadata_depth && delta > comment_json_metadata_depth); + } + + bool should_delete_part_of_content_object(const uint32_t delta) const { + return + (has_comment_title_depth && delta > comment_title_depth) || + (has_comment_body_depth && delta > comment_body_depth) || + (has_comment_json_metadata_depth && delta > comment_json_metadata_depth); + } + + bool should_delete_last_update_object(const uint32_t delta) const { + if (!has_comment_last_update_depth) { + return false; + } + return delta > comment_last_update_depth; + } + + uint32_t comment_title_depth; + uint32_t comment_body_depth; + uint32_t comment_json_metadata_depth; + uint32_t comment_last_update_depth; + + bool has_comment_title_depth = false; + bool has_comment_body_depth = false; + bool has_comment_json_metadata_depth = false; + bool has_comment_last_update_depth = false; + + bool set_null_after_update = false; +}; + namespace golos { namespace plugins { namespace social_network { using golos::plugins::tags::fill_promoted; using golos::api::discussion_helper; + using golos::plugins::social_network::comment_last_update_index; + + using boost::locale::conv::utf_to_utf; struct social_network::impl final { - impl(): database_(appbase::app().get_plugin().db()) { - helper = std::make_unique(database_, follow::fill_account_reputation, fill_promoted); + impl(): db(appbase::app().get_plugin().db()) { + helper = std::make_unique(db, follow::fill_account_reputation, fill_promoted, fill_comment_info); } ~impl() = default; - golos::chain::database& database() { - return database_; - } - - golos::chain::database& database() const { - return database_; - } - void select_active_votes ( std::vector& result, uint32_t& total_count, const std::string& author, const std::string& permlink, uint32_t limit @@ -71,13 +114,58 @@ namespace golos { namespace plugins { namespace social_network { discussion get_content(std::string author, std::string permlink, uint32_t limit) const; - discussion get_discussion(const comment_object& c, uint32_t vote_limit) const ; + discussion get_discussion(const comment_object& c, uint32_t vote_limit) const; + + void set_depth_parameters(const comment_depth_params& params); + + // Looks for a comment_operation, fills the comment_content state objects. + void pre_operation(const operation_notification& o); + + void post_operation(const operation_notification& o); + + void on_block(const signed_block& b); + + comment_api_object create_comment_api_object(const comment_object& o) const; + + const comment_content_object& get_comment_content(const comment_id_type& comment) const; + + const comment_content_object* find_comment_content(const comment_id_type& comment) const; + + bool set_comment_update(const comment_object& comment, time_point_sec active, bool set_last_update) const; + + void activate_parent_comments(const comment_object& comment) const; - private: - golos::chain::database& database_; + golos::chain::database& db; std::unique_ptr helper; + comment_depth_params depth_parameters; + + // variables to temporarily store values through states of operation visitor + asset author_gbg_payout_value{0, SBD_SYMBOL}; // part of author payout + asset author_golos_payout_value{0, STEEM_SYMBOL}; // part of author payout + asset author_gests_payout_value{0, VESTS_SYMBOL}; // part of author payout + asset benef_payout_gests{0, VESTS_SYMBOL}; // GESTS version of benef payout + asset curator_payout_gests{0, VESTS_SYMBOL}; // GESTS version of curator payout }; + const comment_content_object& social_network::impl::get_comment_content(const comment_id_type& comment) const { + try { + return db.get(comment); + } catch(const std::out_of_range &e) { + GOLOS_THROW_MISSING_OBJECT("comment_content", comment); + } FC_CAPTURE_AND_RETHROW((comment)) + } + + const comment_content_object& social_network::get_comment_content(const comment_id_type& comment) const { + return pimpl->get_comment_content(comment); + } + + const comment_content_object* social_network::impl::find_comment_content(const comment_id_type& comment) const { + return db.find(comment); + } + + const comment_content_object* social_network::find_comment_content(const comment_id_type& comment) const { + return pimpl->find_comment_content(comment); + } discussion social_network::impl::get_discussion(const comment_object& c, uint32_t vote_limit) const { return helper->get_discussion(c, vote_limit); @@ -90,6 +178,351 @@ namespace golos { namespace plugins { namespace social_network { helper->select_active_votes(result, total_count, author, permlink, limit); } + bool social_network::impl::set_comment_update(const comment_object& comment, time_point_sec active, bool set_last_update) const { + const auto& clu_idx = db.get_index().indices().get(); + auto clu_itr = clu_idx.find(comment.id); + if (clu_itr != clu_idx.end()) { + db.modify(*clu_itr, [&](comment_last_update_object& clu) { + clu.active = active; + if (set_last_update) { + clu.last_update = clu.active; + } + }); + return true; + } else { + db.create([&](comment_last_update_object& clu) { + clu.comment = comment.id; + clu.author = comment.author; + clu.parent_author = comment.parent_author; + clu.active = active; + if (set_last_update) { + clu.last_update = clu.active; + } + }); + return false; + } + } + + void social_network::impl::activate_parent_comments(const comment_object& comment) const { + if (db.has_hardfork(STEEMIT_HARDFORK_0_6__80) && comment.parent_author != STEEMIT_ROOT_POST_PARENT) { + auto parent = &db.get_comment(comment.parent_author, comment.parent_permlink); + auto now = db.head_block_time(); + while (parent) { + set_comment_update(*parent, now, false); + if (parent->parent_author != STEEMIT_ROOT_POST_PARENT) { + parent = &db.get_comment(parent->parent_author, parent->parent_permlink); + } else { + parent = nullptr; + } + } + } + } + + template + struct delete_visitor { + using result_type = void; + + TImpl& impl; + + delete_visitor(TImpl& impl) : impl(impl) { + } + + template + void operator()(const T& o) const { + } + + void operator()(const delete_comment_operation& o) const { + const auto& comment = impl.db.get_comment(o.author, o.permlink); + const auto content = impl.find_comment_content(comment.id); + + if (content == nullptr) { + return; + } + + impl.db.remove(*content); + + if (impl.db.template has_index()) { + if (comment.net_rshares > 0) { + return; + } + + impl.activate_parent_comments(comment); + + auto& idx = impl.db.template get_index().indices().template get(); + auto itr = idx.find(comment.id); + if (idx.end() != itr) { + impl.db.remove(*itr); + } + } + + if (impl.db.template has_index()) { + auto& idx = impl.db.template get_index().indices().template get(); + auto itr = idx.find(comment.id); + if (idx.end() != itr) { + impl.db.remove(*itr); + } + } + } + }; + + template + struct operation_visitor { + using result_type = void; + + TImpl& impl; + golos::chain::database& db; + comment_depth_params& depth_parameters; + + operation_visitor(TImpl& p): impl(p), db(p.db), depth_parameters(p.depth_parameters) { + } + + std::wstring utf8_to_wstring(const std::string& str) const { + return utf_to_utf(str.c_str(), str.c_str() + str.size()); + } + + std::string wstring_to_utf8(const std::wstring& str) const { + return utf_to_utf(str.c_str(), str.c_str() + str.size()); + } + + template + void operator()(const T& o) const { + } /// ignore all other ops + + void operator()(const golos::protocol::comment_operation& o) const { + const auto& comment = db.get_comment(o.author, o.permlink); + const auto& dp = depth_parameters; + if (!dp.miss_content()) { + const auto comment_content = impl.find_comment_content(comment.id); + if ( comment_content != nullptr) { + // Edit case + db.modify(*comment_content, [&]( comment_content_object& con ) { + if (o.title.size() && (!dp.has_comment_title_depth || dp.comment_title_depth > 0)) { + from_string(con.title, o.title); + } + if (o.json_metadata.size()) { + if ((!dp.has_comment_json_metadata_depth || dp.comment_json_metadata_depth > 0) && + fc::is_utf8(o.json_metadata) + ) { + from_string(con.json_metadata, o.json_metadata ); + } + } + if (o.body.size() && (!dp.has_comment_body_depth || dp.comment_body_depth > 0)) { + try { + diff_match_patch dmp; + auto patch = dmp.patch_fromText(utf8_to_wstring(o.body)); + if (patch.size()) { + auto result = dmp.patch_apply(patch, utf8_to_wstring(to_string(con.body))); + auto patched_body = wstring_to_utf8(result.first); + if(!fc::is_utf8(patched_body)) { + from_string(con.body, fc::prune_invalid_utf8(patched_body)); + } else { + from_string(con.body, patched_body); + } + } else { // replace + from_string(con.body, o.body); + } + } catch ( ... ) { + from_string(con.body, o.body); + } + } + // Set depth null if needed (this parameter is given in config) + if (dp.set_null_after_update) { + con.block_number = db.head_block_num(); + } + }); + } else { + // Creation case + db.create([&](comment_content_object& con) { + con.comment = comment.id; + if (!dp.has_comment_title_depth || dp.comment_title_depth > 0) { + from_string(con.title, o.title); + } + + if ((!dp.has_comment_body_depth || dp.comment_body_depth > 0) && o.body.size() < 1024*1024*128) { + from_string(con.body, o.body); + } + if ((!dp.has_comment_json_metadata_depth || dp.comment_json_metadata_depth > 0) && + fc::is_utf8(o.json_metadata) + ) { + from_string(con.json_metadata, o.json_metadata); + } + con.block_number = db.head_block_num(); + }); + } + } + + if (db.has_index()) { + auto now = db.head_block_time(); + if (!impl.set_comment_update(comment, now, true)) { // If create case + impl.activate_parent_comments(comment); + } + } + } + + result_type operator()(const author_reward_operation& op) const { + if (!db.has_index()) { + return; + } + + impl.author_gbg_payout_value += op.sbd_payout; + impl.author_golos_payout_value += op.steem_payout; + impl.author_gests_payout_value += op.vesting_payout; + } + + result_type operator()(const comment_payout_update_operation& op) const { + if (!db.has_index()) { + return; + } + + const auto& comment = db.get_comment(op.author, op.permlink); + + const auto& cprops = db.get_dynamic_global_properties(); + auto vesting_sp = cprops.get_vesting_share_price(); + + // Calculation author rewards value + + auto author_rewards = impl.author_golos_payout_value.amount; + author_rewards += (impl.author_gests_payout_value * vesting_sp).amount; + author_rewards += db.to_steem(impl.author_gbg_payout_value).amount; + + // Converting + + asset total_payout_golos = asset(author_rewards, STEEM_SYMBOL); + asset total_payout_gbg = db.to_sbd(total_payout_golos); + + asset benef_payout_golos = impl.benef_payout_gests * vesting_sp; + asset benef_payout_gbg = db.to_sbd(benef_payout_golos); + + asset curator_payout_golos = impl.curator_payout_gests * vesting_sp; + asset curator_payout_gbg = db.to_sbd(curator_payout_golos); + + if (total_payout_golos.amount.value || benef_payout_golos.amount.value || curator_payout_golos.amount.value ) { + const auto& cr_idx = db.get_index().indices() + .template get(); + auto cr_itr = cr_idx.find(comment.id); + if (cr_itr == cr_idx.end()) { + db.create([&](golos::plugins::social_network::comment_reward_object& cr) { + cr.comment = comment.id; + cr.author_rewards = author_rewards; + cr.author_gbg_payout_value = impl.author_gbg_payout_value; + cr.author_golos_payout_value = impl.author_golos_payout_value; + cr.author_gests_payout_value = impl.author_gests_payout_value; + cr.total_payout_value = total_payout_gbg; + cr.beneficiary_payout_value = benef_payout_gbg; + cr.beneficiary_gests_payout_value = impl.benef_payout_gests; + cr.curator_payout_value = curator_payout_gbg; + cr.curator_gests_payout_value = impl.curator_payout_gests; + }); + } else { + db.modify(*cr_itr, [&](comment_reward_object& cr) { + cr.author_rewards += author_rewards; + cr.author_gbg_payout_value += impl.author_gbg_payout_value; + cr.author_golos_payout_value += impl.author_golos_payout_value; + cr.author_gests_payout_value += impl.author_gests_payout_value; + cr.total_payout_value += total_payout_gbg; + cr.beneficiary_payout_value += benef_payout_gbg; + cr.beneficiary_gests_payout_value = impl.benef_payout_gests; + cr.curator_payout_value += curator_payout_gbg; + cr.curator_gests_payout_value += impl.curator_payout_gests; + }); + } + } + + impl.author_gbg_payout_value.amount = 0; + impl.benef_payout_gests.amount = 0; + impl.curator_payout_gests.amount = 0; + impl.author_golos_payout_value.amount = 0; + impl.author_gests_payout_value.amount = 0; + } + + result_type operator()(const curation_reward_operation& op) const { + if (!db.has_index()) { + return; + } + + impl.curator_payout_gests += op.reward; + } + + result_type operator()(const comment_benefactor_reward_operation& op) const { + if (!db.has_index()) { + return; + } + + impl.benef_payout_gests += op.reward; + } + }; + + void social_network::impl::pre_operation(const operation_notification& o) { try { + delete_visitor ovisit(*this); + o.op.visit(ovisit); + } FC_CAPTURE_AND_RETHROW() } + + void social_network::impl::post_operation(const operation_notification& o) { try { + operation_visitor ovisit(*this); + o.op.visit(ovisit); + } FC_CAPTURE_AND_RETHROW() } + + + void social_network::impl::on_block(const signed_block& b) { try { + const auto& dp = depth_parameters; + + if (dp.need_clear_content()) { + const auto& content_idx = db.get_index().indices().get(); + + auto head_block_num = db.head_block_num(); + for (auto itr = content_idx.begin(); itr != content_idx.end();) { + auto& content = *itr; + ++itr; + + auto& comment = db.get_comment(content.comment); + + auto delta = head_block_num - content.block_number; + if (comment.mode == archived && dp.should_delete_part_of_content_object(delta)) { + if (dp.should_delete_whole_content_object(delta)) { + db.remove(content); + continue; + } + + db.modify(content, [&](comment_content_object& con) { + if (dp.has_comment_title_depth && delta > dp.comment_title_depth) { + con.title.clear(); + } + + if (dp.has_comment_body_depth && delta > dp.comment_body_depth) { + con.body.clear(); + } + + if (dp.has_comment_json_metadata_depth && delta > dp.comment_json_metadata_depth) { + con.json_metadata.clear(); + } + }); + + } else { + break; + } + } + } + + if (dp.need_clear_last_update()) { + const auto& clu_idx = db.get_index().indices().get(); + + auto head_block_num = db.head_block_num(); + for (auto itr = clu_idx.begin(); itr != clu_idx.end();) { + auto& clu = *itr; + ++itr; + + auto& comment = db.get_comment(clu.comment); + + auto delta = head_block_num - clu.block_number; + if (comment.mode == archived && depth_parameters.should_delete_last_update_object(delta)) { + db.remove(clu); + } else { + break; + } + } + } + } FC_CAPTURE_AND_RETHROW() } + void social_network::plugin_startup() { wlog("social_network plugin: plugin_startup()"); } @@ -106,23 +539,102 @@ namespace golos { namespace plugins { namespace social_network { social_network::social_network() = default; void social_network::set_program_options( - boost::program_options::options_description&, + boost::program_options::options_description& cli, boost::program_options::options_description& config_file_options ) { + config_file_options.add_options() + ( // Depth of comment_content information storage history. + "comment-title-depth", boost::program_options::value(), + "If set, remove comment titles older than specified number of blocks" + ) ( + "comment-body-depth", boost::program_options::value(), + "If set, remove comment bodies older than specified number of blocks." + ) ( + "comment-json-metadata-depth", boost::program_options::value(), + "If set, remove comment json-metadatas older than specified number of blocks." + ) ( + "set-content-storing-depth-null-after-update", boost::program_options::value()->default_value(false), + "should content's depth be set to null after update" + ) ( + "comment-last-update-depth", boost::program_options::value()->default_value(std::numeric_limits::max()), + "mode of storing records of comment.active and comment.last_update: 4294967295 = store all, 0 = do not store, N = storing N blocks depth" + ) ( + "store-comment-rewards", boost::program_options::value()->default_value(true), + "store comment rewards" + ); + // Do not use bool_switch() in cfg! } void social_network::plugin_initialize(const boost::program_options::variables_map& options) { pimpl = std::make_unique(); JSON_RPC_REGISTER_API(name()); + + auto& db = pimpl->db; + + add_plugin_index(db); + + comment_depth_params& params = pimpl->depth_parameters; + + auto comment_last_update_depth = options.at("comment-last-update-depth").as(); + if (comment_last_update_depth != 0) { + add_plugin_index(db); + if (comment_last_update_depth != std::numeric_limits::max()) { + params.comment_last_update_depth = comment_last_update_depth; + params.has_comment_last_update_depth = true; + } + } + + if (options.at("store-comment-rewards").as()) { + add_plugin_index(db); + } + + db.pre_apply_operation.connect([&](const operation_notification &o) { + pimpl->pre_operation(o); + }); + + db.post_apply_operation.connect([&](const operation_notification &o) { + pimpl->post_operation(o); + }); + + db.applied_block.connect([&](const signed_block &b) { + pimpl->on_block(b); + }); + + if (options.count("comment-title-depth")) { + params.comment_title_depth = options.at("comment-title-depth").as(); + params.has_comment_title_depth = true; + } + + if (options.count("comment-body-depth")) { + params.comment_body_depth = options.at("comment-body-depth").as(); + params.has_comment_body_depth = true; + } + + if (options.count("comment-json-metadata-depth")) { + params.comment_json_metadata_depth = options.at("comment-json-metadata-depth").as(); + params.has_comment_json_metadata_depth = true; + } + + if (options.count("set-content-storing-depth-null-after-update")) { + params.set_null_after_update = options.at("set-content-storing-depth-null-after-update").as(); + } } social_network::~social_network() = default; + comment_api_object social_network::impl::create_comment_api_object(const comment_object& o) const { + return helper->create_comment_api_object(o); + } + + comment_api_object social_network::create_comment_api_object(const comment_object& o) const { + return pimpl->create_comment_api_object(o); + } + void social_network::impl::select_content_replies( std::vector& result, std::string author, std::string permlink, uint32_t limit ) const { account_name_type acc_name = account_name_type(author); - const auto& by_permlink_idx = database().get_index().indices().get(); + const auto& by_permlink_idx = db.get_index().indices().get(); auto itr = by_permlink_idx.find(std::make_tuple(acc_name, permlink)); while ( itr != by_permlink_idx.end() && @@ -143,11 +655,12 @@ namespace golos { namespace plugins { namespace social_network { } DEFINE_API(social_network, get_content_replies) { - CHECK_ARG_MIN_SIZE(2, 3) - auto author = args.args->at(0).as(); - auto permlink = args.args->at(1).as(); - auto vote_limit = GET_OPTIONAL_ARG(2, uint32_t, DEFAULT_VOTE_LIMIT); - return pimpl->database().with_weak_read_lock([&]() { + PLUGIN_API_VALIDATE_ARGS( + (string, author) + (string, permlink) + (uint32_t, vote_limit, DEFAULT_VOTE_LIMIT) + ); + return pimpl->db.with_weak_read_lock([&]() { return pimpl->get_content_replies(author, permlink, vote_limit); }); } @@ -170,22 +683,23 @@ namespace golos { namespace plugins { namespace social_network { } DEFINE_API(social_network, get_all_content_replies) { - CHECK_ARG_MIN_SIZE(2, 3) - auto author = args.args->at(0).as(); - auto permlink = args.args->at(1).as(); - auto vote_limit = GET_OPTIONAL_ARG(2, uint32_t, DEFAULT_VOTE_LIMIT); - return pimpl->database().with_weak_read_lock([&]() { + PLUGIN_API_VALIDATE_ARGS( + (string, author) + (string, permlink) + (uint32_t, vote_limit, DEFAULT_VOTE_LIMIT) + ); + return pimpl->db.with_weak_read_lock([&]() { return pimpl->get_all_content_replies(author, permlink, vote_limit); }); } DEFINE_API(social_network, get_account_votes) { - CHECK_ARG_MIN_SIZE(1, 3) - account_name_type voter = args.args->at(0).as(); - auto from = GET_OPTIONAL_ARG(1, uint32_t, 0); - auto limit = GET_OPTIONAL_ARG(2, uint64_t, DEFAULT_VOTE_LIMIT); - - auto& db = pimpl->database(); + PLUGIN_API_VALIDATE_ARGS( + (account_name_type, voter) + (uint32_t, from, 0) + (uint64_t, limit, DEFAULT_VOTE_LIMIT) + ); + auto& db = pimpl->db; return db.with_weak_read_lock([&]() { std::vector result; @@ -216,7 +730,7 @@ namespace golos { namespace plugins { namespace social_network { } discussion social_network::impl::get_content(std::string author, std::string permlink, uint32_t limit) const { - const auto& by_permlink_idx = database().get_index().indices().get(); + const auto& by_permlink_idx = db.get_index().indices().get(); auto itr = by_permlink_idx.find(std::make_tuple(author, permlink)); if (itr != by_permlink_idx.end()) { return get_discussion(*itr, limit); @@ -225,24 +739,26 @@ namespace golos { namespace plugins { namespace social_network { } DEFINE_API(social_network, get_content) { - CHECK_ARG_MIN_SIZE(2, 3) - auto author = args.args->at(0).as(); - auto permlink = args.args->at(1).as(); - auto vote_limit = GET_OPTIONAL_ARG(2, uint32_t, DEFAULT_VOTE_LIMIT); - return pimpl->database().with_weak_read_lock([&]() { + PLUGIN_API_VALIDATE_ARGS( + (string, author) + (string, permlink) + (uint32_t, vote_limit, DEFAULT_VOTE_LIMIT) + ); + return pimpl->db.with_weak_read_lock([&]() { return pimpl->get_content(author, permlink, vote_limit); }); } DEFINE_API(social_network, get_active_votes) { - CHECK_ARG_MIN_SIZE(2, 3) - auto author = args.args->at(0).as(); - auto permlink = args.args->at(1).as(); - auto limit = GET_OPTIONAL_ARG(2, uint32_t, DEFAULT_VOTE_LIMIT); - return pimpl->database().with_weak_read_lock([&]() { + PLUGIN_API_VALIDATE_ARGS( + (string, author) + (string, permlink) + (uint32_t, vote_limit, DEFAULT_VOTE_LIMIT) + ); + return pimpl->db.with_weak_read_lock([&]() { std::vector result; uint32_t total_count; - pimpl->select_active_votes(result, total_count, author, permlink, limit); + pimpl->select_active_votes(result, total_count, author, permlink, vote_limit); return result; }); } @@ -254,27 +770,42 @@ namespace golos { namespace plugins { namespace social_network { uint32_t vote_limit ) const { std::vector result; -#ifndef IS_LOW_MEM - auto& db = database(); - const auto& last_update_idx = db.get_index().indices().get(); - auto itr = last_update_idx.begin(); + + if (!db.has_index()) { + return result; + } + + // Method returns only comments which are created when config flag was true + + const auto& clu_index = db.get_index(); + const auto& clu_cmt_idx = clu_index.indices().get(); + const auto& clu_idx = clu_index.indices().get(); + + auto itr = clu_idx.begin(); const account_name_type* parent_author = &start_parent_author; if (start_permlink.size()) { - const auto& comment = db.get_comment(start_parent_author, start_permlink); - itr = last_update_idx.iterator_to(comment); - parent_author = &comment.parent_author; + const auto& comment = db.find_comment(start_parent_author, start_permlink); + if (nullptr == comment) { + return result; + } + auto clu_itr = clu_cmt_idx.find(comment->id); + if (clu_itr == clu_cmt_idx.end()) { + return result; + } + itr = clu_idx.iterator_to(*clu_itr); + parent_author = &comment->parent_author; } else if (start_parent_author.size()) { - itr = last_update_idx.lower_bound(start_parent_author); + itr = clu_idx.lower_bound(start_parent_author); } result.reserve(limit); - while (itr != last_update_idx.end() && result.size() < limit && itr->parent_author == *parent_author) { - result.emplace_back(get_discussion(*itr, vote_limit)); + while (itr != clu_idx.end() && result.size() < limit && itr->parent_author == *parent_author) { + result.emplace_back(get_discussion(db.get_comment(itr->comment), vote_limit)); ++itr; } -#endif + return result; } @@ -285,15 +816,79 @@ namespace golos { namespace plugins { namespace social_network { * Subsequent calls should be (last_author, last_permlink, limit) */ DEFINE_API(social_network, get_replies_by_last_update) { - CHECK_ARG_MIN_SIZE(3, 4) - auto start_parent_author = args.args->at(0).as(); - auto start_permlink = args.args->at(1).as(); - auto limit = args.args->at(2).as(); - auto vote_limit = GET_OPTIONAL_ARG(3, uint32_t, DEFAULT_VOTE_LIMIT); - FC_ASSERT(limit <= 100); - return pimpl->database().with_weak_read_lock([&]() { + PLUGIN_API_VALIDATE_ARGS( + (string, start_parent_author) + (string, start_permlink) + (uint32_t, limit) + (uint32_t, vote_limit, DEFAULT_VOTE_LIMIT) + ); + GOLOS_CHECK_LIMIT_PARAM(limit, 100); + return pimpl->db.with_weak_read_lock([&]() { return pimpl->get_replies_by_last_update(start_parent_author, start_permlink, limit, vote_limit); }); } + void fill_comment_info(const golos::chain::database& db, const comment_object& co, comment_api_object& con) { + if (db.has_index()) { + const auto content = db.find(co.id); + if (content != nullptr) { + con.title = to_string(content->title); + con.body = to_string(content->body); + con.json_metadata = to_string(content->json_metadata); + } + + const auto root_content = db.find(co.root_comment); + if (root_content != nullptr) { + con.root_title = to_string(root_content->title); + } + } + + if (db.has_index()) { + const auto last_update = db.find(co.id); + if (last_update != nullptr) { + con.active = last_update->active; + con.last_update = last_update->last_update; + } else { + con.active = time_point_sec::min(); + con.last_update = time_point_sec::min(); + } + } + + if (db.has_index()) { + const auto reward = db.find(co.id); + if (reward != nullptr) { + con.author_rewards = reward->author_rewards; + con.author_gbg_payout_value = reward->author_gbg_payout_value; + con.author_golos_payout_value = reward->author_golos_payout_value; + con.author_gests_payout_value = reward->author_gests_payout_value; + con.total_payout_value = reward->total_payout_value; + con.beneficiary_payout_value = reward->beneficiary_payout_value; + con.beneficiary_gests_payout_value = reward->beneficiary_gests_payout_value; + con.curator_payout_value = reward->curator_payout_value; + con.curator_gests_payout_value = reward->curator_gests_payout_value; + } else { + con.author_rewards = 0; + con.author_gbg_payout_value = asset(0, SBD_SYMBOL); + con.author_golos_payout_value = asset(0, STEEM_SYMBOL); + con.author_gests_payout_value = asset(0, VESTS_SYMBOL); + con.total_payout_value = asset(0, SBD_SYMBOL); + con.beneficiary_payout_value = asset(0, SBD_SYMBOL); + con.beneficiary_gests_payout_value = asset(0, VESTS_SYMBOL); + con.curator_payout_value = asset(0, SBD_SYMBOL); + con.curator_gests_payout_value = asset(0, VESTS_SYMBOL); + } + } + } + + std::string get_json_metadata(const golos::chain::database& db, const comment_object& c) { + if (!db.has_index()) { + return std::string(); + } + const auto content = db.find(c.id); + if (content != nullptr) { + return to_string(content->json_metadata); + } + return std::string(); + } + } } } // golos::plugins::social_network diff --git a/plugins/tags/CMakeLists.txt b/plugins/tags/CMakeLists.txt index 6a4f68bd73..6150650e40 100644 --- a/plugins/tags/CMakeLists.txt +++ b/plugins/tags/CMakeLists.txt @@ -38,6 +38,7 @@ target_link_libraries( golos::network golos::follow golos::api + golos::social_network appbase ) diff --git a/plugins/tags/discussion_query.cpp b/plugins/tags/discussion_query.cpp index 6bdc4452d3..793751b330 100644 --- a/plugins/tags/discussion_query.cpp +++ b/plugins/tags/discussion_query.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include namespace golos { namespace plugins { namespace tags { @@ -23,15 +25,23 @@ namespace golos { namespace plugins { namespace tags { } void discussion_query::validate() const { - FC_ASSERT(limit <= 100); + GOLOS_CHECK_LIMIT_PARAM(limit, 100); - for (auto& itr : filter_tags) { - FC_ASSERT(select_tags.find(itr) == select_tags.end()); - } + GOLOS_CHECK_PARAM(filter_tags, { + for (auto& itr : filter_tags) { + GOLOS_CHECK_VALUE(select_tags.find(itr) == select_tags.end(), + "Can't filter and select tag '${tag}' at the same time", + ("tag", itr)); + } + }); - for (auto& itr : filter_languages) { - FC_ASSERT(select_languages.find(itr) == select_languages.end()); - } + GOLOS_CHECK_PARAM(filter_languages, { + for (auto& itr : filter_languages) { + GOLOS_CHECK_VALUE(select_languages.find(itr) == select_languages.end(), + "Can't filter and select language '${language}' at the same time", + ("language", itr)); + } + }); } bool discussion_query::is_good_tags(const discussion& d) const { @@ -39,7 +49,7 @@ namespace golos { namespace plugins { namespace tags { return true; } - auto meta = tags::get_metadata(d); + auto meta = get_metadata(d.json_metadata); if ((has_language_selector() && !select_languages.count(meta.language)) || (has_language_filter() && filter_languages.count(meta.language)) ) { diff --git a/plugins/tags/include/golos/plugins/tags/discussion_query.hpp b/plugins/tags/include/golos/plugins/tags/discussion_query.hpp index b01a48d8ef..7bac1eccd4 100644 --- a/plugins/tags/include/golos/plugins/tags/discussion_query.hpp +++ b/plugins/tags/include/golos/plugins/tags/discussion_query.hpp @@ -25,7 +25,7 @@ namespace golos { namespace plugins { namespace tags { using golos::chain::comment_object; using golos::api::comment_api_object; using golos::api::discussion; - + /** * @class discussion_query * @brief The discussion_query structure implements the RPC API param set. diff --git a/plugins/tags/include/golos/plugins/tags/tag_visitor.hpp b/plugins/tags/include/golos/plugins/tags/tag_visitor.hpp index 0a889f9fcc..032ae45871 100644 --- a/plugins/tags/include/golos/plugins/tags/tag_visitor.hpp +++ b/plugins/tags/include/golos/plugins/tags/tag_visitor.hpp @@ -5,7 +5,13 @@ #include #include + namespace golos { namespace plugins { namespace tags { + using golos::api::discussion_helper; + + comment_metadata get_metadata(const std::string& json_metadata); + + struct comment_date { time_point_sec active; time_point_sec last_update; }; struct operation_visitor { operation_visitor(database& db); @@ -21,6 +27,8 @@ namespace golos { namespace plugins { namespace tags { const tag_stats_object& get_stats(const tag_object&) const; + comment_date get_comment_last_update(const comment_object& comment) const; + void update_tag(const tag_object&, const comment_object&, double hot, double trending) const; void create_tag(const std::string&, const tag_type, const comment_object&, double hot, double trending) const; @@ -46,9 +54,9 @@ namespace golos { namespace plugins { namespace tags { return sign * order + double(created.sec_since_epoch()) / double(T); } - inline double calculate_hot(const share_type& score, const time_point_sec& created) const; + double calculate_hot(const share_type& score, const time_point_sec& created) const; - inline double calculate_trending(const share_type& score, const time_point_sec& created) const; + double calculate_trending(const share_type& score, const time_point_sec& created) const; /** finds tags that have been added or removed or updated */ void create_update_tags(const account_name_type& author, const std::string& permlink) const; diff --git a/plugins/tags/include/golos/plugins/tags/tags_object.hpp b/plugins/tags/include/golos/plugins/tags/tags_object.hpp index 61d233b375..14cd89d537 100644 --- a/plugins/tags/include/golos/plugins/tags/tags_object.hpp +++ b/plugins/tags/include/golos/plugins/tags/tags_object.hpp @@ -17,8 +17,6 @@ namespace golos { namespace plugins { namespace tags { using golos::api::comment_object; using golos::api::comment_api_object; - using golos::api::get_metadata; - using namespace golos::chain; using namespace boost::multi_index; using chainbase::object; diff --git a/plugins/tags/include/golos/plugins/tags/tags_sort.hpp b/plugins/tags/include/golos/plugins/tags/tags_sort.hpp index 155c418d13..5e11d43f6f 100644 --- a/plugins/tags/include/golos/plugins/tags/tags_sort.hpp +++ b/plugins/tags/include/golos/plugins/tags/tags_sort.hpp @@ -50,9 +50,12 @@ namespace golos { namespace plugins { namespace tags { namespace sort { struct by_active { bool operator()(const discussion& first, const discussion& second) const { - if (std::greater()(first.active, second.active)) { + if (!first.active.valid() || !second.active.valid()) { + return false; + } + if (std::greater()(*first.active, *second.active)) { return true; - } else if (std::equal_to()(first.active, second.active)) { + } else if (std::equal_to()(*first.active, *second.active)) { return std::less()(first.id, second.id); } return false; @@ -61,9 +64,12 @@ namespace golos { namespace plugins { namespace tags { namespace sort { struct by_updated { bool operator()(const discussion& first, const discussion& second) const { - if (std::greater()(first.last_update, second.last_update)) { + if (!first.last_update.valid() || !second.last_update.valid()) { + return false; + } + if (std::greater()(*first.last_update, *second.last_update)) { return true; - } else if (std::equal_to()(first.last_update, second.last_update)) { + } else if (std::equal_to()(*first.last_update, *second.last_update)) { return std::less()(first.id, second.id); } return false; diff --git a/plugins/tags/plugin.cpp b/plugins/tags/plugin.cpp index 15bdff3f24..6aa8a1adf8 100644 --- a/plugins/tags/plugin.cpp +++ b/plugins/tags/plugin.cpp @@ -1,35 +1,23 @@ #include #include #include +#include #include #include #include #include #include #include -// These visitors creates additional tables, we don't really need them in LOW_MEM mode #include #include +#include +#include -#define CHECK_ARG_SIZE(_S) \ - FC_ASSERT( \ - args.args->size() == _S, \ - "Expected #_S argument(s), was ${n}", \ - ("n", args.args->size()) ); - -#define CHECK_ARG_MIN_SIZE(_S, _M) \ - FC_ASSERT( \ - args.args->size() >= _S && args.args->size() <= _M, \ - "Expected #_S (maximum #_M) argument(s), was ${n}", \ - ("n", args.args->size()) ); - -#define GET_OPTIONAL_ARG(_I, _T, _D) \ - (args.args->size() > _I) ? \ - (args.args->at(_I).as<_T>()) : \ - static_cast<_T>(_D) namespace golos { namespace plugins { namespace tags { + using golos::plugins::social_network::comment_last_update_index; + using golos::chain::feed_history_object; using golos::api::discussion_helper; @@ -38,22 +26,21 @@ namespace golos { namespace plugins { namespace tags { helper = std::make_unique( database_, follow::fill_account_reputation, - fill_promoted); + fill_promoted, + social_network::fill_comment_info); } ~impl() {} void on_operation(const operation_notification& note) { -#ifndef IS_LOW_MEM try { /// plugins shouldn't ever throw - note.op.visit(tags::operation_visitor(database())); + note.op.visit(tags::operation_visitor(database_)); } catch (const fc::exception& e) { edump((e.to_detail_string())); } catch (...) { elog("unhandled exception"); } -#endif } golos::chain::database& database() { @@ -79,8 +66,8 @@ namespace golos { namespace plugins { namespace tags { bool filter_query(discussion_query& query) const; - template - std::vector select_unordered_discussions(discussion_query& query) const; + template + std::vector select_unordered_discussions(discussion_query&, Fill&&) const; template void select_discussions( @@ -114,6 +101,9 @@ namespace golos { namespace plugins { namespace tags { discussion create_discussion(const comment_object& o) const; discussion create_discussion(const comment_object& o, const discussion_query& query) const; void fill_discussion(discussion& d, const discussion_query& query) const; + void fill_comment_api_object(const comment_object& o, discussion& d) const; + + comment_api_object create_comment_api_object(const comment_object & o) const; get_languages_result get_languages(); @@ -146,6 +136,10 @@ namespace golos { namespace plugins { namespace tags { return helper->create_discussion(o); } + void tags_plugin::impl::fill_comment_api_object(const comment_object& o, discussion& d) const { + helper->fill_comment_api_object(o, d); + } + void tags_plugin::impl::fill_discussion(discussion& d, const discussion_query& query) const { set_url(d); set_pending_payout(d); @@ -178,7 +172,12 @@ namespace golos { namespace plugins { namespace tags { return d; } + comment_api_object tags_plugin::impl::create_comment_api_object(const comment_object & o) const { + return helper->create_comment_api_object( o ); + } + DEFINE_API(tags_plugin, get_languages) { + PLUGIN_API_VALIDATE_ARGS(); return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_languages(); }); @@ -208,8 +207,6 @@ namespace golos { namespace plugins { namespace tags { void tags_plugin::plugin_initialize(const boost::program_options::variables_map& options) { pimpl.reset(new impl()); -// Disable index creation for tag visitor -#ifndef IS_LOW_MEM auto& db = pimpl->database(); db.post_apply_operation.connect([&](const operation_notification& note) { pimpl->on_operation(note); @@ -218,7 +215,6 @@ namespace golos { namespace plugins { namespace tags { add_plugin_index(db); add_plugin_index(db); add_plugin_index(db); -#endif JSON_RPC_REGISTER_API (name()); } @@ -282,7 +278,13 @@ namespace golos { namespace plugins { namespace tags { if (!comment) { return false; } + query.start_comment = create_discussion(*comment, query); + auto& d = query.start_comment; + operation_visitor v(database_); + + d.hot = v.calculate_hot(d.net_rshares, d.created); + d.trending = v.calculate_trending(d.net_rshares, d.created); } return true; } @@ -314,8 +316,12 @@ namespace golos { namespace plugins { namespace tags { template< typename DatabaseIndex, - typename DiscussionIndex> - std::vector tags_plugin::impl::select_unordered_discussions(discussion_query& query) const { + typename DiscussionIndex, + typename Fill> + std::vector tags_plugin::impl::select_unordered_discussions( + discussion_query& query, + Fill&& fill + ) const { std::vector result; if (!filter_start_comment(query) || !filter_query(query)) { @@ -367,7 +373,8 @@ namespace golos { namespace plugins { namespace tags { } fill_discussion(d, query); - result.push_back(d); + fill(d, *itr); + result.push_back(std::move(d)); } } return result; @@ -495,7 +502,6 @@ namespace golos { namespace plugins { namespace tags { } query.reset_start_comment(); itr = idx.iterator_to(*citr); - ++itr; } unordered.reserve(query.limit); @@ -536,60 +542,74 @@ namespace golos { namespace plugins { namespace tags { } DEFINE_API(tags_plugin, get_discussions_by_blog) { - CHECK_ARG_SIZE(1) - std::vector result; + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); - auto query = args.args->at(0).as(); query.prepare(); query.validate(); - FC_ASSERT(query.select_authors.size(), "Must get blogs for specific authors"); - + GOLOS_CHECK_PARAM(query.select_authors, + GOLOS_CHECK_VALUE(query.select_authors.size(), "Must get blogs for specific authors")); -#ifndef IS_LOW_MEM auto& db = pimpl->database(); - FC_ASSERT(db.has_index(), "Node is not running the follow plugin"); + GOLOS_ASSERT(db.has_index(), golos::unsupported_api_method, + "Node is not running the follow plugin"); return db.with_weak_read_lock([&]() { - return pimpl->select_unordered_discussions(query); + return pimpl->select_unordered_discussions( + query, + [&](discussion& d, const follow::blog_object& b) { + d.first_reblogged_on = b.reblogged_on; + }); }); -#endif - return result; } DEFINE_API(tags_plugin, get_discussions_by_feed) { - CHECK_ARG_SIZE(1) - std::vector result; - - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); - FC_ASSERT(query.select_authors.size(), "Must get feeds for specific authors"); + GOLOS_CHECK_PARAM(query.select_authors, + GOLOS_CHECK_VALUE(query.select_authors.size(), "Must get feeds for specific authors")); -#ifndef IS_LOW_MEM auto& db = pimpl->database(); - FC_ASSERT(db.has_index(), "Node is not running the follow plugin"); + GOLOS_ASSERT(db.has_index(), golos::unsupported_api_method, + "Node is not running the follow plugin"); return db.with_weak_read_lock([&]() { - return pimpl->select_unordered_discussions(query); + return pimpl->select_unordered_discussions( + query, + [&](discussion& d, const follow::feed_object& f) { + d.reblogged_by.assign(f.reblogged_by.begin(), f.reblogged_by.end()); + d.first_reblogged_by = f.first_reblogged_by; + d.first_reblogged_on = f.first_reblogged_on; + }); }); -#endif - return result; } DEFINE_API(tags_plugin, get_discussions_by_comments) { - CHECK_ARG_SIZE(1) + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); std::vector result; -#ifndef IS_LOW_MEM - auto query = args.args->at(0).as(); query.prepare(); query.validate(); - FC_ASSERT(!!query.start_author, "Must get comments for a specific author"); + GOLOS_CHECK_PARAM(query.start_author, + GOLOS_CHECK_VALUE(!!query.start_author, "Must get comments for specific authors")); auto& db = pimpl->database(); + + if (!db.has_index()) { + return result; + } + return db.with_weak_read_lock([&]() { - const auto &idx = db.get_index().indices().get(); - auto itr = idx.lower_bound(*query.start_author); - if (itr == idx.end()) { + const auto& clu_cmt_idx = db.get_index().indices().get(); + const auto& clu_idx = db.get_index().indices().get(); + + auto itr = clu_idx.lower_bound(*query.start_author); + if (itr == clu_idx.end()) { return result; } @@ -599,7 +619,12 @@ namespace golos { namespace plugins { namespace tags { if (litr == lidx.end()) { return result; } - itr = idx.iterator_to(*litr); + auto clu_itr = clu_cmt_idx.find(litr->id); + if (clu_itr == clu_cmt_idx.end()) { + return result; + } else { + itr = clu_idx.iterator_to(*clu_itr); + } } if (!pimpl->filter_query(query)) { @@ -608,171 +633,158 @@ namespace golos { namespace plugins { namespace tags { result.reserve(query.limit); - for (; itr != idx.end() && itr->author == *query.start_author && result.size() < query.limit; ++itr) { + for (; itr != clu_idx.end() && itr->author == *query.start_author && result.size() < query.limit; ++itr) { if (itr->parent_author.size() > 0) { - discussion p(db.get(itr->root_comment), db); + discussion p; + auto& comment = db.get_comment(itr->comment); + pimpl->fill_comment_api_object(db.get_comment(comment.root_comment), p); if (!query.is_good_tags(p) || !query.is_good_author(p.author)) { continue; } - result.emplace_back(discussion(*itr, db)); + discussion d; + pimpl->fill_comment_api_object(comment, d); + result.push_back(std::move(d)); pimpl->fill_discussion(result.back(), query); } } return result; }); -#endif - return result; } DEFINE_API(tags_plugin, get_discussions_by_trending) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return d.net_rshares > 0; } ); -#endif - return std::vector(); } DEFINE_API(tags_plugin, get_discussions_by_promoted) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return !!d.promoted && d.promoted->amount > 0; } ); -#endif - return std::vector(); } DEFINE_API(tags_plugin, get_discussions_by_created) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return true; } ); -#endif - return std::vector(); } DEFINE_API(tags_plugin, get_discussions_by_active) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM + auto& db = pimpl->database(); + if (!db.has_index()) { + return std::vector(); + } return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return true; } ); -#endif - return std::vector(); } DEFINE_API(tags_plugin, get_discussions_by_cashout) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return d.net_rshares > 0; } ); -#endif - return std::vector(); } DEFINE_API(tags_plugin, get_discussions_by_payout) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return d.net_rshares > 0; } ); -#endif - return std::vector(); } DEFINE_API(tags_plugin, get_discussions_by_votes) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return true; } ); -#endif - return std::vector(); } DEFINE_API(tags_plugin, get_discussions_by_children) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return true; } ); -#endif - return std::vector(); } DEFINE_API(tags_plugin, get_discussions_by_hot) { - CHECK_ARG_SIZE(1) - auto query = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (discussion_query, query) + ); query.prepare(); query.validate(); -#ifndef IS_LOW_MEM return pimpl->select_ordered_discussions( query, [&](const discussion& d) -> bool { return d.net_rshares > 0; } ); -#endif - return std::vector(); } std::vector tags_plugin::impl::get_trending_tags(const std::string& after, uint32_t limit) const { limit = std::min(limit, uint32_t(1000)); std::vector result; -#ifndef IS_LOW_MEM result.reserve(limit); const auto& nidx = database().get_index().indices().get(); @@ -800,14 +812,14 @@ namespace golos { namespace plugins { namespace tags { result.emplace_back(push_object); } -#endif return result; } DEFINE_API(tags_plugin, get_trending_tags) { - CHECK_ARG_SIZE(2) - auto after = args.args->at(0).as(); - auto limit = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, after) + (uint32_t, limit) + ); return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_trending_tags(after, limit); @@ -818,7 +830,6 @@ namespace golos { namespace plugins { namespace tags { const std::string& author ) const { std::vector> result; -#ifndef IS_LOW_MEM auto& db = database(); const auto* acnt = db.find_account(author); if (acnt == nullptr) { @@ -835,13 +846,13 @@ namespace golos { namespace plugins { namespace tags { } } } -#endif return result; } DEFINE_API(tags_plugin, get_tags_used_by_author) { - CHECK_ARG_SIZE(1) - auto author = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, author) + ); return pimpl->database().with_weak_read_lock([&]() { return pimpl->get_tags_used_by_author(author); }); @@ -850,15 +861,14 @@ namespace golos { namespace plugins { namespace tags { DEFINE_API(tags_plugin, get_discussions_by_author_before_date) { std::vector result; - CHECK_ARG_MIN_SIZE(4, 5) -#ifndef IS_LOW_MEM - auto author = args.args->at(0).as(); - auto start_permlink = args.args->at(1).as(); - auto before_date = args.args->at(2).as(); - auto limit = args.args->at(3).as(); - auto vote_limit = GET_OPTIONAL_ARG(4, uint32_t, DEFAULT_VOTE_LIMIT); - - FC_ASSERT(limit <= 100); + PLUGIN_API_VALIDATE_ARGS( + (string, author) + (string, start_permlink) + (time_point_sec, before_date) + (uint32_t, limit) + (uint32_t, vote_limit, DEFAULT_VOTE_LIMIT) + ); + GOLOS_CHECK_LIMIT_PARAM(limit, 100); result.reserve(limit); @@ -868,32 +878,38 @@ namespace golos { namespace plugins { namespace tags { auto& db = pimpl->database(); + if (!db.has_index()) { + return result; + } + return db.with_weak_read_lock([&]() { try { uint32_t count = 0; - const auto& didx = db.get_index().indices().get(); + const auto& clu_cmt_idx = db.get_index().indices().get(); + const auto& clu_idx = db.get_index().indices().get(); - auto itr = didx.lower_bound(std::make_tuple(author, before_date)); + auto itr = clu_idx.lower_bound(std::make_tuple(author, before_date)); if (start_permlink.size()) { - const auto& comment = db.get_comment(author, start_permlink); - if (comment.last_update < before_date) { - itr = didx.iterator_to(comment); + const auto comment = db.find_comment(author, start_permlink); + if (comment == nullptr) { + return result; + } + auto clu_itr = clu_cmt_idx.find(comment->id); + if (clu_itr != clu_cmt_idx.end() && clu_itr->last_update < before_date) { + itr = clu_idx.iterator_to(*clu_itr); } } - while (itr != didx.end() && itr->author == author && count < limit) { + for (; itr != clu_idx.end() && itr->author == author && count < limit; ++itr) { if (itr->parent_author.size() == 0) { - result.push_back(pimpl->get_discussion(*itr, vote_limit)); + result.push_back(pimpl->get_discussion(db.get_comment(itr->comment), vote_limit)); ++count; } - ++itr; } return result; } FC_CAPTURE_AND_RETHROW((author)(start_permlink)(before_date)(limit)) }); -#endif - return result; } // Needed for correct work of golos::api::discussion_helper::set_pending_payout and etc api methods diff --git a/plugins/tags/tag_visitor.cpp b/plugins/tags/tag_visitor.cpp index 5e54276856..6597b4c0f2 100644 --- a/plugins/tags/tag_visitor.cpp +++ b/plugins/tags/tag_visitor.cpp @@ -1,8 +1,49 @@ #include #include +#include namespace golos { namespace plugins { namespace tags { + using golos::plugins::social_network::comment_last_update_index; + + comment_metadata get_metadata(const std::string& json_metadata) { + comment_metadata meta; + + if (!json_metadata.empty()) { + try { + meta = fc::json::from_string(json_metadata).as(); + } catch (const fc::exception& e) { + // Do nothing on malformed json_metadata + } + } + + std::set lower_tags; + + std::size_t tag_limit = 15; + for (const auto& name : meta.tags) { + if (lower_tags.size() > tag_limit) { + break; + } + auto value = boost::trim_copy(name); + if (value.empty()) { + continue; + } + boost::to_lower(value); + lower_tags.insert(value); + } + + meta.tags.swap(lower_tags); + + boost::trim(meta.language); + boost::to_lower(meta.language); + + return meta; + } + + comment_metadata get_metadata(const golos::chain::database& db, const comment_object& c) { + return get_metadata(golos::plugins::social_network::get_json_metadata(db, c)); + } + operation_visitor::operation_visitor(database& db) : db_(db) { } @@ -81,13 +122,28 @@ namespace golos { namespace plugins { namespace tags { db_.remove(tag); } + comment_date operation_visitor::get_comment_last_update(const comment_object& comment) const { + comment_date result; + if (db_.has_index()) { + const auto& clu_idx = db_.get_index().indices().get(); + auto clu_itr = clu_idx.find(comment.id); + if (clu_itr != clu_idx.end()) { + result.active = clu_itr->active; + result.last_update = clu_itr->last_update; + return result; + } + } + return result; + } + void operation_visitor::update_tag( const tag_object& current, const comment_object& comment, double hot, double trending ) const { auto cashout_time = db_.calculate_discussion_payout_time(comment); remove_stats(current); + db_.modify(current, [&](tag_object& obj) { - obj.active = comment.active; + obj.active = get_comment_last_update(comment).active; obj.cashout = cashout_time; obj.children = comment.children; obj.net_rshares = comment.net_rshares.value; @@ -112,14 +168,16 @@ namespace golos { namespace plugins { namespace tags { parent = db_.get_comment(comment.parent_author, comment.parent_permlink).id; } + auto com_date = get_comment_last_update(comment); + const auto& tag_obj = db_.create([&](tag_object& obj) { obj.name = name; obj.type = type; obj.comment = comment.id; obj.parent = parent; obj.created = comment.created; - obj.active = comment.active; - obj.updated = comment.last_update; + obj.active = com_date.active; + obj.updated = com_date.last_update; obj.cashout = comment.cashout_time; obj.net_votes = comment.net_votes; obj.children = comment.children; @@ -174,7 +232,7 @@ namespace golos { namespace plugins { namespace tags { auto trending = calculate_trending(comment.net_rshares, comment.created); const auto& comment_idx = db_.get_index().indices().get(); - auto meta = get_metadata(comment_api_object(comment, db_)); + auto meta = get_metadata(db_, comment); auto citr = comment_idx.lower_bound(comment.id); const tag_object* language_tag = nullptr; @@ -307,7 +365,7 @@ namespace golos { namespace plugins { namespace tags { const auto& comment = db_.get_comment(op.author, op.permlink); const auto& author = db_.get_account(op.author).id; - auto meta = get_metadata(comment_api_object(comment, db_)); + auto meta = get_metadata(db_, comment); const auto& stats_idx = db_.get_index().indices().get(); const auto& auth_idx = db_.get_index().indices().get(); diff --git a/plugins/witness/witness.cpp b/plugins/witness/witness.cpp index 972b84b2ca..f4dbc4b083 100644 --- a/plugins/witness/witness.cpp +++ b/plugins/witness/witness.cpp @@ -169,7 +169,7 @@ namespace golos { idump((m)); fc::optional private_key = golos::utilities::wif_to_key(m.second); - FC_ASSERT(private_key.valid(), "unable to parse private key"); + GOLOS_CHECK_OPTION(private_key.valid(), "unable to parse private key"); pimpl->_private_keys[private_key->get_public_key()] = *private_key; pimpl->_miners[m.first] = private_key->get_public_key(); } @@ -197,7 +197,7 @@ namespace golos { const std::vector keys = options["private-key"].as>(); for (const std::string &wif_key : keys) { fc::optional private_key = golos::utilities::wif_to_key(wif_key); - FC_ASSERT(private_key.valid(), "unable to parse private key"); + GOLOS_CHECK_OPTION(private_key.valid(), "unable to parse private key"); pimpl->_private_keys[private_key->get_public_key()] = *private_key; } } @@ -263,7 +263,6 @@ namespace golos { } void witness_plugin::plugin_shutdown() { - golos::time::shutdown_ntp_time(); if (pimpl->mining_threads_) { ilog("shutting downing mining threads"); pimpl->mining_service_.stop(); @@ -561,7 +560,7 @@ namespace golos { op.props = _miner_prop_vote; while (true) { - if (golos::time::nonblocking_now() > stop) { + if (golos::time::now() > stop) { // ilog( "stop mining due to time out, nonce: ${n}", ("n",op.nonce) ); return; } @@ -609,7 +608,7 @@ namespace golos { op.props = _miner_prop_vote; while (true) { // if( ((op.nonce/num_threads) % 1000) == 0 ) idump((op.nonce)); - if (golos::time::nonblocking_now() > stop) { + if (golos::time::now() > stop) { // ilog( "stop mining due to time out, nonce: ${n}", ("n",op.nonce) ); return; } diff --git a/plugins/witness_api/plugin.cpp b/plugins/witness_api/plugin.cpp index 440dbedfb9..2d7b69f24b 100644 --- a/plugins/witness_api/plugin.cpp +++ b/plugins/witness_api/plugin.cpp @@ -1,10 +1,9 @@ #include #include +#include +#include -#define CHECK_ARG_SIZE(s) \ - FC_ASSERT( args.args->size() == s, "Expected #s argument(s), was ${n}", ("n", args.args->size()) ); - namespace golos { namespace plugins { namespace witness_api { using namespace golos::protocol; @@ -29,12 +28,14 @@ struct plugin::witness_plugin_impl { DEFINE_API(plugin, get_current_median_history_price) { + PLUGIN_API_VALIDATE_ARGS(); return my->database.with_weak_read_lock([&]() { return my->database.get_feed_history().current_median_history; }); } DEFINE_API(plugin, get_feed_history) { + PLUGIN_API_VALIDATE_ARGS(); return my->database.with_weak_read_lock([&]() { return feed_history_api_object(my->database.get_feed_history()); }); @@ -55,6 +56,7 @@ std::vector plugin::witness_plugin_impl::get_miner_queue() co } DEFINE_API(plugin, get_miner_queue) { + PLUGIN_API_VALIDATE_ARGS(); return my->database.with_weak_read_lock([&]() { return my->get_miner_queue(); }); @@ -62,6 +64,7 @@ DEFINE_API(plugin, get_miner_queue) { DEFINE_API(plugin, get_active_witnesses) { + PLUGIN_API_VALIDATE_ARGS(); return my->database.with_weak_read_lock([&]() { const auto &wso = my->database.get_witness_schedule_object(); size_t n = wso.current_shuffled_witnesses.size(); @@ -77,6 +80,7 @@ DEFINE_API(plugin, get_active_witnesses) { } DEFINE_API(plugin, get_witness_schedule) { + PLUGIN_API_VALIDATE_ARGS(); return my->database.with_weak_read_lock([&]() { return my->database.get(witness_schedule_object::id_type()); }); @@ -99,16 +103,18 @@ std::vector> plugin::witness_plugin_impl::get_witne } DEFINE_API(plugin, get_witnesses) { - CHECK_ARG_SIZE(1) - auto witness_ids = args.args->at(0).as >(); + PLUGIN_API_VALIDATE_ARGS( + (vector, witness_ids) + ); return my->database.with_weak_read_lock([&]() { return my->get_witnesses(witness_ids); }); } DEFINE_API(plugin, get_witness_by_account) { - CHECK_ARG_SIZE(1) - auto account_name = args.args->at(0).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, account_name) + ); return my->database.with_weak_read_lock([&]() { return my->get_witness_by_account(account_name); }); @@ -125,9 +131,10 @@ fc::optional plugin::witness_plugin_impl::get_witness_by_acc } DEFINE_API(plugin, get_witnesses_by_vote) { - CHECK_ARG_SIZE(2) - auto from = args.args->at(0).as(); - auto limit = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, from) + (uint32_t, limit) + ); return my->database.with_weak_read_lock([&]() { return my->get_witnesses_by_vote(from, limit); }); @@ -136,7 +143,7 @@ DEFINE_API(plugin, get_witnesses_by_vote) { std::vector plugin::witness_plugin_impl::get_witnesses_by_vote( std::string from, uint32_t limit ) const { - FC_ASSERT(limit <= 100); + GOLOS_CHECK_LIMIT_PARAM(limit, 100); std::vector result; result.reserve(limit); @@ -147,7 +154,8 @@ std::vector plugin::witness_plugin_impl::get_witnesses_by_vo auto itr = vote_idx.begin(); if (from.size()) { auto nameitr = name_idx.find(from); - FC_ASSERT(nameitr != name_idx.end(), "invalid witness name ${n}", ("n", from)); + GOLOS_CHECK_PARAM(from, + GOLOS_CHECK_VALUE(nameitr != name_idx.end(), "Witness name after last witness")); itr = vote_idx.iterator_to(*nameitr); } @@ -159,6 +167,7 @@ std::vector plugin::witness_plugin_impl::get_witnesses_by_vo } DEFINE_API(plugin, get_witness_count) { + PLUGIN_API_VALIDATE_ARGS(); return my->database.with_weak_read_lock([&]() { return my->get_witness_count(); }); @@ -169,9 +178,10 @@ uint64_t plugin::witness_plugin_impl::get_witness_count() const { } DEFINE_API(plugin, lookup_witness_accounts) { - CHECK_ARG_SIZE(2) - auto lower_bound_name = args.args->at(0).as(); - auto limit = args.args->at(1).as(); + PLUGIN_API_VALIDATE_ARGS( + (string, lower_bound_name) + (uint32_t, limit) + ); return my->database.with_weak_read_lock([&]() { return my->lookup_witness_accounts(lower_bound_name, limit); }); @@ -181,7 +191,7 @@ std::set plugin::witness_plugin_impl::lookup_witness_accounts const std::string &lower_bound_name, uint32_t limit ) const { - FC_ASSERT(limit <= 1000); + GOLOS_CHECK_LIMIT_PARAM(limit, 1000); const auto &witnesses_by_id = database.get_index().indices().get(); // get all the names and look them all up, sort them, then figure out what diff --git a/programs/build_helpers/configure_build.py b/programs/build_helpers/configure_build.py index 919108761f..46e39b4b98 100755 --- a/programs/build_helpers/configure_build.py +++ b/programs/build_helpers/configure_build.py @@ -76,14 +76,7 @@ def try_default_dir_from_env(args, flag_name, metavar_name, env_name=None): parser.add_argument("--openssl-dir", metavar="OPENSSL_ROOT", type=convert_to_dir, default=argparse.SUPPRESS, help="OpenSSL root directory (can alternatively specify with OPENSSL_ROOT_DIR environment variable)") - node_type = parser.add_mutually_exclusive_group() - node_type.add_argument("-f", "--full", dest="low_mem_node", - action="store_false", default=argparse.SUPPRESS, - help="build with LOW_MEMORY_NODE=OFF (default)") - node_type.add_argument("-w", "--witness", dest="low_mem_node", - action="store_true", default=argparse.SUPPRESS, - help="build with LOW_MEMORY_NODE=ON") - build_type = parser.add_mutually_exclusive_group() + build_type = parser.add_mutually_exclusive_group() build_type.add_argument("-r", "--release", dest="release", action="store_true", default=argparse.SUPPRESS, help="build with CMAKE_BUILD_TYPE=RELEASE (default)") @@ -178,8 +171,6 @@ def main(args): command.append("-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY") # Add Golos flags - command.append( - "-DLOW_MEMORY_NODE=" + ("ON" if args.low_mem_node else "OFF")) command.append( "-DCMAKE_BUILD_TYPE=" + ("RELEASE" if args.release else "DEBUG")) diff --git a/share/golosd/config/config.ini b/share/golosd/config/config.ini index d67c3095e0..c11c664a10 100644 --- a/share/golosd/config/config.ini +++ b/share/golosd/config/config.ini @@ -71,6 +71,46 @@ plugin = chain p2p json_rpc webserver network_broadcast_api witness test_api dat # Remove votes before defined block, should increase performance clear-votes-before-block = 4294967295 # clear votes after each cashout +# If set, remove votes older than specified number of blocks. +# -1 = do not remove; +# 0 = remove after cashout; +# any other value N - remove votes older than N blocks. +# Note: votes don't removed before post cashout +# clear-votes-older-n-blocks = 0 + +# Store account metadata for all accounts if true, for no one if else. +# store-account-metadata = true + +# Names of accounts to store metadata +# store-account-metadata-list = + +# Store memo for all savings withdraws +# store-memo-in-savings-withdraws = true + +# If set, remove comment titles older than specified number of blocks. +# comment-title-depth = + +# If set, remove comment bodies older than specified number of blocks. +# comment-body-depth = + +# If set, remove comment json-metadatas older than specified number of blocks. +# comment-json-metadata-depth = + +# should content's depth be set to null after update +# set-content-storing-depth-null-after-update = false + +# Mode of storing records of comment.active and comment.last_update: +# -1 = store all +# 0 = do not store +# N = storing N blocks depth +# comment-last-update-depth = -1 + +# Store comment rewards +# store-comment-rewards = true + +# Replay all blocks if shared memory is corrupted +replay-if-corrupted = true + # Virtual operations will not be passed to the plugins, enabling of the option helps to save some memory. skip-virtual-ops = false @@ -98,6 +138,9 @@ history-per-size = 5760 # Defines a range of accounts to private messages to/from as a json pair ["from","to"] [from,to) # pm-account-range = +# Defines a list of accounts to private messages to/from +# pm-account-list = + # Enable block production, even if the chain is stale. enable-stale-production = false diff --git a/share/golosd/config/config_debug.ini b/share/golosd/config/config_debug.ini index 74562ac3fa..e53c84b751 100644 --- a/share/golosd/config/config_debug.ini +++ b/share/golosd/config/config_debug.ini @@ -71,6 +71,46 @@ plugin = chain p2p json_rpc webserver network_broadcast_api witness test_api dat # Remove votes before defined block, should increase performance clear-votes-before-block = 0 # don't clear votes +# If set, remove votes older than specified number of blocks. +# -1 = do not remove; +# 0 = remove after cashout; +# any other value N - remove votes older than N blocks. +# Note: votes don't removed before post cashout +# clear-votes-older-n-blocks = 0 + +# Store account metadata for all accounts if true, for no one if else. +# store-account-metadata = true + +# Names of accounts to store metadata +# store-account-metadata-list = + +# Store memo for all savings withdraws +# store-memo-in-savings-withdraws = true + +# If set, remove comment titles older than specified number of blocks. +# comment-title-depth = + +# If set, remove comment bodies older than specified number of blocks. +# comment-body-depth = + +# If set, remove comment json-metadatas older than specified number of blocks. +# comment-json-metadata-depth = + +# should content's depth be set to null after update +# set-content-storing-depth-null-after-update = false + +# Mode of storing records of comment.active and comment.last_update: +# -1 = store all +# 0 = do not store +# N = storing N blocks depth +# comment-last-update-depth = -1 + +# Store comment rewards +# store-comment-rewards = true + +# Replay all blocks if shared memory is corrupted +replay-if-corrupted = true + # Virtual operations will not be passed to the plugins, enabling of the option helps to save some memory. skip-virtual-ops = false @@ -98,6 +138,9 @@ history-per-size = 5760 # Defines a range of accounts to private messages to/from as a json pair ["from","to"] [from,to) # pm-account-range = +# Defines a list of accounts to private messages to/from +pm-account-list = + # Enable block production, even if the chain is stale. enable-stale-production = true diff --git a/share/golosd/config/config_debug_mongo.ini b/share/golosd/config/config_debug_mongo.ini index 65e629d1f5..9215d3bcf3 100644 --- a/share/golosd/config/config_debug_mongo.ini +++ b/share/golosd/config/config_debug_mongo.ini @@ -74,6 +74,46 @@ mongodb-uri = mongodb://172.17.0.1:27017/Golos # Remove votes before defined block, should increase performance clear-votes-before-block = 0 # don't clear votes +# If set, remove votes older than specified number of blocks. +# -1 = do not remove; +# 0 = remove after cashout; +# any other value N - remove votes older than N blocks. +# Note: votes don't removed before post cashout +# clear-votes-older-n-blocks = 0 + +# Store account metadata for all accounts if true, for no one if else. +# store-account-metadata = true + +# Names of accounts to store metadata +# store-account-metadata-list = + +# Store memo for all savings withdraws +# store-memo-in-savings-withdraws = true + +# If set, remove comment titles older than specified number of blocks. +# comment-title-depth = + +# If set, remove comment bodies older than specified number of blocks. +# comment-body-depth = + +# If set, remove comment json-metadatas older than specified number of blocks. +# comment-json-metadata-depth = + +# should content's depth be set to null after update +# set-content-storing-depth-null-after-update = false + +# Mode of storing records of comment.active and comment.last_update: +# -1 = store all +# 0 = do not store +# N = storing N blocks depth +# comment-last-update-depth = -1 + +# Store comment rewards +# store-comment-rewards = true + +# Replay all blocks if shared memory is corrupted +replay-if-corrupted = true + # Virtual operations will not be passed to the plugins, enabling of the option helps to save some memory. skip-virtual-ops = false @@ -101,6 +141,9 @@ history-per-size = 5760 # Defines a range of accounts to private messages to/from as a json pair ["from","to"] [from,to) # pm-account-range = +# Defines a list of accounts to private messages to/from +# pm-account-list = + # Enable block production, even if the chain is stale. enable-stale-production = true diff --git a/share/golosd/config/config_mongo.ini b/share/golosd/config/config_mongo.ini index f8ac3fcf25..ecf22c471a 100644 --- a/share/golosd/config/config_mongo.ini +++ b/share/golosd/config/config_mongo.ini @@ -74,6 +74,46 @@ mongodb-uri = mongodb://172.17.0.1:27017/Golos # Remove votes before defined block, should increase performance clear-votes-before-block = 4294967295 # clear votes after each cashout +# If set, remove votes older than specified number of blocks. +# -1 = do not remove; +# 0 = remove after cashout; +# any other value N - remove votes older than N blocks. +# Note: votes don't removed before post cashout +# clear-votes-older-n-blocks = 0 + +# Store account metadata for all accounts if true, for no one if else. +# store-account-metadata = true + +# Names of accounts to store metadata +# store-account-metadata-list = + +# Store memo for all savings withdraws +# store-memo-in-savings-withdraws = true + +# If set, remove comment titles older than specified number of blocks. +# comment-title-depth = + +# If set, remove comment bodies older than specified number of blocks. +# comment-body-depth = + +# If set, remove comment json-metadatas older than specified number of blocks. +# comment-json-metadata-depth = + +# should content's depth be set to null after update +# set-content-storing-depth-null-after-update = false + +# Mode of storing records of comment.active and comment.last_update: +# -1 = store all +# 0 = do not store +# N = storing N blocks depth +# comment-last-update-depth = -1 + +# Store comment rewards +# store-comment-rewards = true + +# Replay all blocks if shared memory is corrupted +replay-if-corrupted = true + # Virtual operations will not be passed to the plugins, enabling of the option helps to save some memory. skip-virtual-ops = false @@ -101,6 +141,9 @@ history-per-size = 5760 # Defines a range of accounts to private messages to/from as a json pair ["from","to"] [from,to) # pm-account-range = +# Defines a list of accounts to private messages to/from +# pm-account-list = + # Enable block production, even if the chain is stale. enable-stale-production = false diff --git a/share/golosd/config/config_stock_exchange.ini b/share/golosd/config/config_stock_exchange.ini index 42895b314d..943d7558d8 100644 --- a/share/golosd/config/config_stock_exchange.ini +++ b/share/golosd/config/config_stock_exchange.ini @@ -71,6 +71,46 @@ plugin = chain p2p json_rpc webserver network_broadcast_api witness database_api # Remove votes before defined block, should increase performance clear-votes-before-block = 4294967295 # clear votes after each cashout +# If set, remove votes older than specified number of blocks. +# -1 = do not remove; +# 0 = remove after cashout; +# any other value N - remove votes older than N blocks. +# Note: votes don't removed before post cashout +# clear-votes-older-n-blocks = 0 + +# Store account metadata for all accounts if true, for no one if else. +# store-account-metadata = true + +# Names of accounts to store metadata +# store-account-metadata-list = + +# Store memo for all savings withdraws +# store-memo-in-savings-withdraws = true + +# If set, remove comment titles older than specified number of blocks. +# comment-title-depth = + +# If set, remove comment bodies older than specified number of blocks. +# comment-body-depth = + +# If set, remove comment json-metadatas older than specified number of blocks. +# comment-json-metadata-depth = + +# should content's depth be set to null after update +# set-content-storing-depth-null-after-update = false + +# Mode of storing records of comment.active and comment.last_update: +# -1 = store all +# 0 = do not store +# N = storing N blocks depth +# comment-last-update-depth = -1 + +# Store comment rewards +# store-comment-rewards = true + +# Replay all blocks if shared memory is corrupted +replay-if-corrupted = true + # Virtual operations will not be passed to the plugins, enabling of the option helps to save some memory. skip-virtual-ops = true @@ -92,6 +132,12 @@ bucket-size = [15,60,300,3600,86400] # How far back in time to track history for each bucket size, measured in the number of buckets (default: 5760) history-per-size = 5760 +# Defines a range of accounts to private messages to/from as a json pair ["from","to"] [from,to) +# pm-account-range = + +# Defines a list of accounts to private messages to/from +pm-account-list = + # Enable block production, even if the chain is stale. enable-stale-production = false diff --git a/share/golosd/config/config_witness.ini b/share/golosd/config/config_witness.ini index 1fa73044f3..dd2c4dd33e 100644 --- a/share/golosd/config/config_witness.ini +++ b/share/golosd/config/config_witness.ini @@ -70,6 +70,46 @@ plugin = chain p2p json_rpc webserver network_broadcast_api witness database_api # Remove votes before defined block, should increase performance clear-votes-before-block = 4294967295 # clear votes after each cashout +# If set, remove votes older than specified number of blocks. +# -1 = do not remove; +# 0 = remove after cashout; +# any other value N - remove votes older than N blocks. +# Note: votes don't removed before post cashout +# clear-votes-older-n-blocks = 0 + +# Store account metadata for all accounts if true, for no one if else. +# store-account-metadata = true + +# Names of accounts to store metadata +# store-account-metadata-list = + +# Store memo for all savings withdraws +# store-memo-in-savings-withdraws = true + +# If set, remove comment titles older than specified number of blocks. +# comment-title-depth = + +# If set, remove comment bodies older than specified number of blocks. +# comment-body-depth = + +# If set, remove comment json-metadatas older than specified number of blocks. +# comment-json-metadata-depth = + +# should content's depth be set to null after update +# set-content-storing-depth-null-after-update = false + +# Mode of storing records of comment.active and comment.last_update: +# -1 = store all +# 0 = do not store +# N = storing N blocks depth +# comment-last-update-depth = -1 + +# Store comment rewards +# store-comment-rewards = true + +# Replay all blocks if shared memory is corrupted +replay-if-corrupted = true + # Virtual operations will not be passed to the plugins, enabling of the option helps to save some memory. skip-virtual-ops = true @@ -79,6 +119,12 @@ enable-stale-production = false # Percent of witnesses (0-99) that must be participating in order to produce blocks required-participation = 0 +# Defines a range of accounts to private messages to/from as a json pair ["from","to"] [from,to) +# pm-account-range = + +# Defines a list of accounts to private messages to/from +pm-account-list = + # name of witness controlled by this node (e.g. initwitness ) witness = "cyberfounder" diff --git a/share/golosd/docker/Dockerfile-lowmem b/share/golosd/docker/Dockerfile-lowmem deleted file mode 100644 index 5ee50f95dc..0000000000 --- a/share/golosd/docker/Dockerfile-lowmem +++ /dev/null @@ -1,133 +0,0 @@ -FROM phusion/baseimage:0.9.19 - -ENV LANG=en_US.UTF-8 - -RUN \ - apt-get update && \ - apt-get install -y \ - autoconf \ - automake \ - autotools-dev \ - bsdmainutils \ - build-essential \ - cmake \ - doxygen \ - git \ - ccache\ - libboost-all-dev \ - libreadline-dev \ - libssl-dev \ - libtool \ - ncurses-dev \ - pbzip2 \ - pkg-config \ - python3 \ - python3-dev \ - python3-pip \ - && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ - pip3 install gcovr - -ADD . /usr/local/src/golos - -RUN \ - cd /usr/local/src/golos && \ - git submodule deinit -f . && \ - git submodule update --init --recursive -f && \ - mkdir build && \ - cd build && \ - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_GOLOS_TESTNET=FALSE \ - -DBUILD_SHARED_LIBRARIES=FALSE \ - -DLOW_MEMORY_NODE=TRUE \ - -DCHAINBASE_CHECK_LOCKING=FALSE \ - -DENABLE_MONGO_PLUGIN=FALSE \ - .. \ - && \ - make -j$(nproc) && \ - make install && \ - rm -rf /usr/local/src/golos - -RUN \ - apt-get remove -y \ - automake \ - autotools-dev \ - bsdmainutils \ - build-essential \ - cmake \ - doxygen \ - dpkg-dev \ - git \ - libboost-all-dev \ - libc6-dev \ - libexpat1-dev \ - libgcc-5-dev \ - libhwloc-dev \ - libibverbs-dev \ - libicu-dev \ - libltdl-dev \ - libncurses5-dev \ - libnuma-dev \ - libopenmpi-dev \ - libpython-dev \ - libpython2.7-dev \ - libreadline-dev \ - libreadline6-dev \ - libssl-dev \ - libstdc++-5-dev \ - libtinfo-dev \ - libtool \ - linux-libc-dev \ - m4 \ - make \ - manpages \ - manpages-dev \ - mpi-default-dev \ - python-dev \ - python2.7-dev \ - python3-dev \ - && \ - apt-get autoremove -y && \ - rm -rf \ - /var/lib/apt/lists/* \ - /tmp/* \ - /var/tmp/* \ - /var/cache/* \ - /usr/include \ - /usr/local/include - -ADD share/golosd/golosdctl /usr/local/bin/golosdctl -RUN chmod +x /usr/local/bin/golosdctl - -RUN useradd -s /bin/bash -m -d /var/lib/golosd golosd - -RUN mkdir /var/cache/golosd && \ - chown golosd:golosd -R /var/cache/golosd - -# add blockchain cache to image -#ADD $STEEMD_BLOCKCHAIN /var/cache/golosd/blocks.tbz2 - -ENV HOME /var/lib/golosd -RUN chown golosd:golosd -R /var/lib/golosd - -ADD share/golosd/snapshot5392323.json /var/lib/golosd - -# rpc service: -# http -EXPOSE 8090 -# ws -EXPOSE 8091 -# p2p service: -EXPOSE 4243 - -RUN mkdir -p /etc/service/golosd -ADD share/golosd/golosd.sh /etc/service/golosd/run -RUN chmod +x /etc/service/golosd/run - -# add seednodes from documentation to image -ADD share/golosd/seednodes /etc/golosd/seednodes - -# the following adds lots of logging info to stdout -ADD share/golosd/config/config.ini /etc/golosd/config.ini diff --git a/share/golosd/docker/Dockerfile-lowmem-small b/share/golosd/docker/Dockerfile-lowmem-small deleted file mode 100644 index 37ccec02e1..0000000000 --- a/share/golosd/docker/Dockerfile-lowmem-small +++ /dev/null @@ -1,126 +0,0 @@ -FROM phusion/baseimage:0.9.19 - -ENV LANG=en_US.UTF-8 - -ADD . /usr/local/src/golos - -RUN \ - apt-get update && \ - apt-get install -y \ - autoconf \ - automake \ - autotools-dev \ - bsdmainutils \ - build-essential \ - cmake \ - doxygen \ - git \ - ccache\ - libboost-all-dev \ - libreadline-dev \ - libssl-dev \ - libtool \ - ncurses-dev \ - pbzip2 \ - pkg-config \ - python3 \ - python3-dev \ - python3-pip \ - && \ - pip3 install gcovr && \ - # build golosd - cd /usr/local/src/golos && \ - git submodule deinit -f . && \ - git submodule update --init --recursive -f && \ - mkdir build && \ - cd build && \ - cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_GOLOS_TESTNET=FALSE \ - -DBUILD_SHARED_LIBRARIES=FALSE \ - -DLOW_MEMORY_NODE=TRUE \ - -DCHAINBASE_CHECK_LOCKING=FALSE \ - -DENABLE_MONGO_PLUGIN=FALSE \ - .. \ - && \ - make -j$(nproc) && \ - make install && \ - # perform cleanup - rm -rf /usr/local/src/golos && \ - apt-get remove -y \ - automake \ - autotools-dev \ - bsdmainutils \ - build-essential \ - cmake \ - doxygen \ - dpkg-dev \ - git \ - libboost-all-dev \ - libc6-dev \ - libexpat1-dev \ - libgcc-5-dev \ - libhwloc-dev \ - libibverbs-dev \ - libicu-dev \ - libltdl-dev \ - libncurses5-dev \ - libnuma-dev \ - libopenmpi-dev \ - libpython-dev \ - libpython2.7-dev \ - libreadline-dev \ - libreadline6-dev \ - libssl-dev \ - libstdc++-5-dev \ - libtinfo-dev \ - libtool \ - linux-libc-dev \ - m4 \ - make \ - manpages \ - manpages-dev \ - mpi-default-dev \ - python-dev \ - python2.7-dev \ - python3-dev \ - && \ - apt-get autoremove -y && \ - rm -rf \ - /var/lib/apt/lists/* \ - /tmp/* \ - /var/tmp/* \ - /var/cache/* \ - /usr/include \ - /usr/local/include && \ - # add pseudouser - useradd -s /bin/bash -m -d /var/lib/golosd golosd && \ - # prepare cache directory - mkdir /var/cache/golosd && \ - chown golosd:golosd -R /var/cache/golosd - -ADD share/golosd/golosdctl /usr/local/bin/golosdctl -RUN chmod +x /usr/local/bin/golosdctl - -ENV HOME /var/lib/golosd -RUN chown golosd:golosd -R /var/lib/golosd - -ADD share/golosd/snapshot5392323.json /var/lib/golosd - -# rpc service: -# http -EXPOSE 8090 -# ws -EXPOSE 8091 -# p2p service: -EXPOSE 4243 - -RUN mkdir -p /etc/service/golosd -ADD share/golosd/golosd.sh /etc/service/golosd/run -RUN chmod +x /etc/service/golosd/run - -# add seednodes from documentation to image -ADD share/golosd/seednodes /etc/golosd/seednodes - -# the following adds lots of logging info to stdout -ADD share/golosd/config/config.ini /etc/golosd/config.ini diff --git a/share/golosd/docker/Dockerfile-mongo b/share/golosd/docker/Dockerfile-mongo index 96c4f10c53..51e55940b0 100644 --- a/share/golosd/docker/Dockerfile-mongo +++ b/share/golosd/docker/Dockerfile-mongo @@ -70,7 +70,6 @@ RUN \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_GOLOS_TESTNET=FALSE \ -DBUILD_SHARED_LIBRARIES=FALSE \ - -DLOW_MEMORY_NODE=FALSE \ -DCHAINBASE_CHECK_LOCKING=FALSE \ -DENABLE_MONGO_PLUGIN=TRUE \ .. \ diff --git a/share/golosd/docker/Dockerfile-small b/share/golosd/docker/Dockerfile-small index 1d4adc41b3..c764f699c8 100644 --- a/share/golosd/docker/Dockerfile-small +++ b/share/golosd/docker/Dockerfile-small @@ -38,7 +38,6 @@ RUN \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_GOLOS_TESTNET=FALSE \ -DBUILD_SHARED_LIBRARIES=FALSE \ - -DLOW_MEMORY_NODE=FALSE \ -DCHAINBASE_CHECK_LOCKING=FALSE \ -DENABLE_MONGO_PLUGIN=FALSE \ .. \ diff --git a/share/golosd/docker/Dockerfile-test b/share/golosd/docker/Dockerfile-test index 909c98ecf5..3c2d9da061 100644 --- a/share/golosd/docker/Dockerfile-test +++ b/share/golosd/docker/Dockerfile-test @@ -40,7 +40,6 @@ RUN \ cmake \ -DCMAKE_BUILD_TYPE=Debug \ -DBUILD_GOLOS_TESTNET=TRUE \ - -DLOW_MEMORY_NODE=FALSE \ -DMAX_19_VOTED_WITNESSES=TRUE \ -DENABLE_MONGO_PLUGIN=FALSE \ .. && \ @@ -60,7 +59,6 @@ RUN \ # -DCMAKE_BUILD_TYPE=Debug \ # -DENABLE_COVERAGE_TESTING=TRUE \ # -DBUILD_GOLOS_TESTNET=TRUE \ -# -DLOW_MEMORY_NODE=FALSE \ # -DMAX_19_VOTED_WITNESSES=TRUE \ # -DENABLE_MONGO_PLUGIN=FALSE \ # .. && \ diff --git a/share/golosd/docker/Dockerfile-testnet b/share/golosd/docker/Dockerfile-testnet index 58fc86d921..990e0242bc 100644 --- a/share/golosd/docker/Dockerfile-testnet +++ b/share/golosd/docker/Dockerfile-testnet @@ -40,7 +40,6 @@ RUN \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_GOLOS_TESTNET=TRUE \ -DBUILD_SHARED_LIBRARIES=FALSE \ - -DLOW_MEMORY_NODE=FALSE \ -DCHAINBASE_CHECK_LOCKING=FALSE \ -DENABLE_MONGO_PLUGIN=FALSE \ .. \ diff --git a/share/golosd/docker/Dockerfile-testnet-mongo b/share/golosd/docker/Dockerfile-testnet-mongo index 411cd555bd..8d94dffd4f 100644 --- a/share/golosd/docker/Dockerfile-testnet-mongo +++ b/share/golosd/docker/Dockerfile-testnet-mongo @@ -69,7 +69,6 @@ RUN \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_GOLOS_TESTNET=TRUE \ -DBUILD_SHARED_LIBRARIES=FALSE \ - -DLOW_MEMORY_NODE=FALSE \ -DCHAINBASE_CHECK_LOCKING=FALSE \ -DENABLE_MONGO_PLUGIN=TRUE \ .. \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9f89f682db..7a3024e317 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,13 +24,32 @@ target_link_libraries( golos_account_history golos_market_history golos_debug_node + golos::api + golos_social_network fc ${PLATFORM_SPECIFIC_LIBS}) add_test(NAME chain_test_run COMMAND chain_test) -file(GLOB PLUGIN_TESTS "plugin_tests/*.cpp") +file(GLOB PLUGIN_TESTS + "plugin_tests/main.cpp" + "plugin_tests/market_history.cpp" + "plugin_tests/plugin_ops.cpp" + "plugin_tests/json_rpc.cpp" + "plugin_tests/chain.cpp" + "plugin_tests/operation_history.cpp" + "plugin_tests/account_history.cpp" + "plugin_tests/follow.cpp" + "plugin_tests/private_message.cpp") add_executable(plugin_test ${PLUGIN_TESTS} ${COMMON_SOURCES}) -target_link_libraries(plugin_test golos_chain golos_protocol golos_account_history golos_market_history golos_debug_node fc ${PLATFORM_SPECIFIC_LIBS}) +target_link_libraries(plugin_test + golos_chain golos_protocol + golos_account_history + golos_market_history + golos_debug_node + golos_social_network + golos_private_message + fc + ${PLATFORM_SPECIFIC_LIBS}) target_include_directories(plugin_test PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/common") add_test(NAME plugin_test_run COMMAND plugin_test) diff --git a/tests/common/comment_reward.hpp b/tests/common/comment_reward.hpp index 5135b8260a..056e898814 100644 --- a/tests/common/comment_reward.hpp +++ b/tests/common/comment_reward.hpp @@ -151,7 +151,7 @@ namespace golos { namespace chain { } asset total_payout() const { - return total_payout_; + return sbd_payout_ + db_.to_sbd(vesting_payout_ * db_.get_dynamic_global_properties().get_vesting_share_price()); } private: @@ -218,7 +218,6 @@ namespace golos { namespace chain { auto sbd_payout_value = comment_rewards_ / 2; auto vesting_payout_value = comment_rewards_ - sbd_payout_value; - total_payout_ = db_.to_sbd(asset(sbd_payout_value + vesting_payout_value, STEEM_SYMBOL)); sbd_payout_ = asset(sbd_payout_value, SBD_SYMBOL); vesting_payout_ = fund_.create_vesting(asset(vesting_payout_value, STEEM_SYMBOL)); } @@ -240,7 +239,6 @@ namespace golos { namespace chain { asset sbd_payout_; asset vesting_payout_; - asset total_payout_; }; } } // namespace golos::chain \ No newline at end of file diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 9f07df2a38..9bd6b8b0e2 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1,9 +1,8 @@ #include #include -#include #include - +#include #include #include @@ -11,11 +10,121 @@ #include #include "database_fixture.hpp" +#include "helpers.hpp" -//using namespace golos::chain::test; + +#define STEEM_NAMESPACE_PREFIX std::string("golos::protocol::") uint32_t STEEMIT_TESTING_GENESIS_TIMESTAMP = 1431700000; + +namespace fc { + +std::ostream& operator<<(std::ostream& out, const fc::exception& e) { + out << e.to_detail_string(); + return out; +} + +std::ostream& operator<<(std::ostream& out, const fc::time_point& v) { + out << static_cast(v); + return out; +} + +std::ostream& operator<<(std::ostream& out, const fc::uint128_t& v) { + out << static_cast(v); + return out; +} + +std::ostream& operator<<(std::ostream& out, const fc::fixed_string& v) { + out << static_cast(v); + return out; +} + +std::ostream& operator<<(std::ostream& out, const fc::variant_object &v) { + out << fc::json::to_string(v); + return out; +} + +bool compare(const fc::variant &left, const fc::variant &right) { + if (left.get_type() != right.get_type()) return false; + switch (left.get_type()) { + case variant::null_type: return true; + case variant::int64_type: return left.as_int64() == right.as_int64(); + case variant::uint64_type: return left.as_uint64() == right.as_uint64(); + case variant::double_type: return left.as_double() == right.as_double(); + case variant::bool_type: return left.as_bool() == right.as_bool(); + case variant::string_type: return left.get_string() == right.get_string(); + case variant::array_type: + { + const variants &l = left.get_array(); + const variants &r = right.get_array(); + if (l.size() != r.size()) return false; + return std::equal(l.begin(), l.end(), r.begin(), compare); + } + case variant::object_type: return left.get_object() == right.get_object(); + case variant::blob_type: return left.get_string() == right.get_string(); + default: return false; + } +} + +bool operator==(const fc::variant_object &left, const fc::variant_object &right) { + if (left.size() != right.size()) return false; + for (const auto &v: left) { + const auto &ptr = right.find(v.key()); + if (ptr == right.end()) return false; + if (false == compare(v.value(), ptr->value())) return false; + } + return true; +} + +} // namespace fc + + +namespace fc { namespace ecc { + +std::ostream& operator<<(std::ostream& out, const public_key& v) { + out << v.to_base58(); + return out; +} + +} } // namespace fc::ecc + + +namespace golos { namespace protocol { + +std::ostream& operator<<(std::ostream& out, const asset& v) { + out << v.to_string(); + return out; +} + +std::ostream& operator<<(std::ostream& out, const public_key_type& v) { + out << std::string(v); + return out; +} + +std::ostream& operator<<(std::ostream& out, const authority& v) { + out << v.weight_threshold << " / " << v.account_auths << " / " << v.key_auths; + return out; +} + +std::ostream& operator<<(std::ostream& out, const price& v) { + out << v.base << '/' << v.quote << '=' << v.to_real(); + return out; +} + +} } // namespace golos::protocol + + +namespace golos { namespace chain { + +std::ostream& operator<<(std::ostream& out, const shared_authority& v) { + out << static_cast(v); + return out; +} + +} } // namespace golos::chain + + namespace golos { namespace chain { using std::cout; @@ -24,6 +133,28 @@ namespace golos { namespace chain { using golos::plugins::json_rpc::msg_pack; + fc::variant_object make_comment_id(const std::string& author, const std::string& permlink) { + auto res = fc::mutable_variant_object()("account",author)("permlink",permlink); + return fc::variant_object(res); + } + + fc::variant_object make_limit_order_id(const std::string& author, uint32_t orderid) { + auto res = fc::mutable_variant_object()("account",author)("order_id",orderid); + return fc::variant_object(res); + } + + fc::variant_object make_convert_request_id(const std::string& account, uint32_t requestid) { + auto res = fc::mutable_variant_object()("account",account)("request_id",requestid); + return fc::variant_object(res); + } + + fc::variant_object make_escrow_id(const string& name, uint32_t escrow_id) { + auto res = fc::mutable_variant_object()("account",name)("escrow",escrow_id); + return fc::variant_object(res); + } + + + database_fixture::~database_fixture() { if (db_plugin) { // clear all debug updates @@ -31,6 +162,10 @@ namespace golos { namespace chain { } close_database(); + db->_plugin_index_signal = fc::signal(); + appbase::app().quit(); + appbase::app().shutdown(); + appbase::reset(); } clean_database_fixture::clean_database_fixture() { @@ -97,46 +232,100 @@ namespace golos { namespace chain { FC_LOG_AND_RETHROW() } - fc::ecc::private_key database_fixture::generate_private_key(string seed) { - return fc::ecc::private_key::regenerate(fc::sha256::hash(seed)); - } - - string database_fixture::generate_anon_acct_name() { - // names of the form "anon-acct-x123" ; the "x" is necessary - // to workaround issue #46 - return "anon-acct-x" + std::to_string(anon_acct_count++); - } - - void database_fixture::initialize() { - int argc = boost::unit_test::framework::master_test_suite().argc; - char **argv = boost::unit_test::framework::master_test_suite().argv; - for (int i = 1; i < argc; i++) { - const std::string arg = argv[i]; - if (arg == "--record-assert-trip") { - fc::enable_record_assert_trip = true; - } - if (arg == "--show-test-names") { - std::cout << "running test " - << boost::unit_test::framework::current_test_case().p_name - << std::endl; + struct app_initialise { + app_initialise() = default; + ~app_initialise() = default; + + template + plugin_type* get_plugin() { + int argc = boost::unit_test::framework::master_test_suite().argc; + char **argv = boost::unit_test::framework::master_test_suite().argv; + for (int i = 1; i < argc; i++) { + const std::string arg = argv[i]; + if (arg == "--record-assert-trip") { + fc::enable_record_assert_trip = true; + } + if (arg == "--show-test-names") { + std::cout << "running test " + << boost::unit_test::framework::current_test_case().p_name + << std::endl; + } } + auto plg = &appbase::app().register_plugin(); + appbase::app().initialize(argc, argv); + return plg; } + }; + + add_operations_database_fixture::operations_map add_operations_database_fixture::add_operations() { try { + operations_map _added_ops; + + ACTORS((alice)(bob)(sam)) + fund("alice", 10000); + vest("alice", 10000); + fund("bob", 7500); + vest("bob", 7500); + fund("sam", 8000); + vest("sam", 8000); + + comment_operation com; + com.author = "bob"; + com.permlink = "test"; + com.parent_author = ""; + com.parent_permlink = "test"; + com.title = "foo"; + com.body = "bar"; + signed_transaction tx; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, com)); + generate_block(); + _added_ops.insert(std::make_pair(tx.id().str(), STEEM_NAMESPACE_PREFIX + "comment_operation")); + ilog("Generate: " + tx.id().str() + " comment_operation"); + + tx.clear(); + vote_operation vote; + vote.voter = "alice"; + vote.author = "bob"; + vote.permlink = "test"; + vote.weight = -1; // Necessary to allow delete_comment_operation + tx.operations.push_back(vote); + vote.voter = "bob"; + tx.operations.push_back(vote); + vote.voter = "sam"; + tx.operations.push_back(vote); + tx.sign(alice_private_key, db->get_chain_id()); + tx.sign(bob_private_key, db->get_chain_id()); + tx.sign(sam_private_key, db->get_chain_id()); + GOLOS_CHECK_NO_THROW(db->push_transaction(tx, 0)); + generate_block(); + _added_ops.insert(std::make_pair(tx.id().str(), STEEM_NAMESPACE_PREFIX + "vote_operation")); + ilog("Generate: " + tx.id().str() + " vote_operation"); - ch_plugin = &appbase::app().register_plugin(); - oh_plugin = &appbase::app().register_plugin(); - ah_plugin = &appbase::app().register_plugin(); - db_plugin = &appbase::app().register_plugin(); + delete_comment_operation dco; + dco.author = "bob"; + dco.permlink = "test"; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, dco)); + generate_block(); + _added_ops.insert(std::make_pair(tx.id().str(), STEEM_NAMESPACE_PREFIX + "delete_comment_operation")); + ilog("Generate: " + tx.id().str() + " delete_comment_operation"); + + account_create_operation aco; + aco.new_account_name = "dave"; + aco.creator = STEEMIT_INIT_MINER_NAME; + aco.owner = authority(1, init_account_pub_key, 1); + aco.active = aco.owner; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(tx, init_account_priv_key, aco)); + generate_block(); + _added_ops.insert(std::make_pair(tx.id().str(), STEEM_NAMESPACE_PREFIX + "account_create_operation")); + ilog("Generate: " + tx.id().str() + " account_create_operation"); + + validate_database(); - appbase::app().initialize< - golos::plugins::chain::plugin, - account_history::plugin, - debug_node::plugin - >( argc, argv ); + return _added_ops; + } FC_LOG_AND_RETHROW(); } - db_plugin->set_logging(false); - db = &ch_plugin->db(); - BOOST_REQUIRE( db ); + fc::ecc::private_key database_fixture::generate_private_key(string seed) { + return fc::ecc::private_key::regenerate(fc::sha256::hash(seed)); } void database_fixture::startup(bool generate_hardfork) { @@ -149,18 +338,16 @@ namespace golos { namespace chain { vest(STEEMIT_INIT_MINER_NAME, 10000); // Fill up the rest of the required miners - for (int i = STEEMIT_NUM_INIT_MINERS; - i < STEEMIT_MAX_WITNESSES; i++) { + for (int i = STEEMIT_NUM_INIT_MINERS; i < STEEMIT_MAX_WITNESSES; i++) { account_create(STEEMIT_INIT_MINER_NAME + fc::to_string(i), init_account_pub_key); fund(STEEMIT_INIT_MINER_NAME + fc::to_string(i), STEEMIT_MIN_PRODUCER_REWARD.amount.value); - witness_create(STEEMIT_INIT_MINER_NAME + fc::to_string(i), - init_account_priv_key, "foo.bar", init_account_pub_key, - STEEMIT_MIN_PRODUCER_REWARD.amount); + witness_create( + STEEMIT_INIT_MINER_NAME + fc::to_string(i), + init_account_priv_key, "foo.bar", init_account_pub_key, + STEEMIT_MIN_PRODUCER_REWARD.amount); } - oh_plugin->plugin_startup(); - ah_plugin->plugin_startup(); - db_plugin->plugin_startup(); + appbase::app().startup(); validate_database(); } @@ -169,9 +356,9 @@ namespace golos { namespace chain { if (!data_dir) { data_dir = fc::temp_directory(golos::utilities::temp_directory_path()); db->_log_hardforks = false; + db->_is_testing = true; db->open(data_dir->path(), data_dir->path(), INITIAL_TEST_SUPPLY, - 1024 * 1024 * - 8, chainbase::database::read_write); // 8 MB file for testing + 1024 * 1024 * 10, chainbase::database::read_write); // 10 MB file for testing } } @@ -449,14 +636,12 @@ namespace golos { namespace chain { vector database_fixture::get_last_operations(uint32_t num_ops) { vector ops; - const auto &acc_hist_idx = db->get_index().indices().get(); + const auto& acc_hist_idx = db->get_index().indices().get(); auto itr = acc_hist_idx.end(); - while (itr != acc_hist_idx.begin() && ops.size() < num_ops) { itr--; ops.push_back(fc::raw::unpack(db->get(itr->op).serialized_op)); } - return ops; } diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 20c8890563..1004f9085e 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -3,67 +3,27 @@ #include #include - -#include -#include - +#include +#include #include #include +#include #include +#include +#include #include #define INITIAL_TEST_SUPPLY (10000000000ll) -extern uint32_t ( STEEMIT_TESTING_GENESIS_TIMESTAMP ); - -#define PUSH_TX \ - golos::chain::test::_push_transaction - -#define PUSH_BLOCK \ - golos::chain::test::_push_block - -// See below -#define REQUIRE_OP_VALIDATION_SUCCESS(op, field, value) \ -{ \ - const auto temp = op.field; \ - op.field = value; \ - op.validate(); \ - op.field = temp; \ -} -#define REQUIRE_OP_EVALUATION_SUCCESS(op, field, value) \ -{ \ - const auto temp = op.field; \ - op.field = value; \ - trx.operations.back() = op; \ - op.field = temp; \ - db.push_transaction( trx, ~0 ); \ -} +extern uint32_t STEEMIT_TESTING_GENESIS_TIMESTAMP; -/*#define STEEMIT_REQUIRE_THROW( expr, exc_type ) \ -{ \ - std::string req_throw_info = fc::json::to_string( \ - fc::mutable_variant_object() \ - ("source_file", __FILE__) \ - ("source_lineno", __LINE__) \ - ("expr", #expr) \ - ("exc_type", #exc_type) \ - ); \ - if( fc::enable_record_assert_trip ) \ - std::cout << "STEEMIT_REQUIRE_THROW begin " \ - << req_throw_info << std::endl; \ - BOOST_REQUIRE_THROW( expr, exc_type ); \ - if( fc::enable_record_assert_trip ) \ - std::cout << "STEEMIT_REQUIRE_THROW end " \ - << req_throw_info << std::endl; \ -}*/ #define STEEMIT_REQUIRE_THROW(expr, exc_type) \ - BOOST_REQUIRE_THROW( expr, exc_type ); + BOOST_REQUIRE_THROW(expr, exc_type); -#define STEEMIT_CHECK_THROW(expr, exc_type) \ -{ \ +#define STEEMIT_CHECK_THROW(expr, exc_type) { \ std::string req_throw_info = fc::json::to_string( \ fc::mutable_variant_object() \ ("source_file", __FILE__) \ @@ -72,35 +32,190 @@ extern uint32_t ( STEEMIT_TESTING_GENESIS_TIMESTAMP ); ("exc_type", #exc_type) \ ); \ if( fc::enable_record_assert_trip ) \ - std::cout << "STEEMIT_CHECK_THROW begin " \ + std::cout << "STEEMIT_CHECK_THROW begin " \ << req_throw_info << std::endl; \ BOOST_CHECK_THROW( expr, exc_type ); \ if( fc::enable_record_assert_trip ) \ - std::cout << "STEEMIT_CHECK_THROW end " \ + std::cout << "STEEMIT_CHECK_THROW end " \ << req_throw_info << std::endl; \ } -#define REQUIRE_OP_VALIDATION_FAILURE_2(op, field, value, exc_type) \ -{ \ - const auto temp = op.field; \ - op.field = value; \ - STEEMIT_REQUIRE_THROW( op.validate(), exc_type ); \ - op.field = temp; \ -} -#define REQUIRE_OP_VALIDATION_FAILURE(op, field, value) \ - REQUIRE_OP_VALIDATION_FAILURE_2( op, field, value, fc::exception ) - -#define REQUIRE_THROW_WITH_VALUE_2(op, field, value, exc_type) \ -{ \ - auto bak = op.field; \ - op.field = value; \ - trx.operations.back() = op; \ - op.field = bak; \ - STEEMIT_REQUIRE_THROW(db.push_transaction(trx, ~0), exc_type); \ +#define GOLOS_CHECK_THROW_PROPS_IMPL(S, E, C, TL) \ + try { \ + BOOST_TEST_PASSPOINT(); \ + S; \ + BOOST_##TL( "exception '" BOOST_STRINGIZE( E ) "' is expected, but no exception thrown" ); } \ + catch( E const& ex ) { \ + const fc::variant_object &props = ex.get_log().at(0).get_data(); \ + (void)props; /*avoid "unused var" spam. TODO: @smedvedev, make it used?*/ \ + try { \ + C; \ + } catch (const fc::exception& err) { \ + BOOST_##TL( "caught exception '" << err.name() << "' while check props:" << \ + err.to_detail_string()); \ + } \ + } catch ( ... ) { \ + try { \ + throw; \ + } catch (const fc::exception& ex) { \ + BOOST_##TL( "exception '" BOOST_STRINGIZE( E ) "' is expected, " \ + "but '" << ex.name() << "' is caught"); \ + } catch (...) { \ + BOOST_##TL( "exception " BOOST_STRINGIZE( E ) " is expected, " \ + "but unknown is caught"); \ + } \ + } \ + +#define GOLOS_WARN_THROW_PROPS(S, E, C) GOLOS_CHECK_THROW_PROPS_IMPL(S, E, C, WARN) +#define GOLOS_CHECK_THROW_PROPS(S, E, C) GOLOS_CHECK_THROW_PROPS_IMPL(S, E, C, ERROR) +#define GOLOS_REQUIRE_THROW_PROPS(S, E, C) GOLOS_CHECK_THROW_PROPS_IMPL(S, E, C, FAIL) + +#define GOLOS_CHECK_NO_THROW_IMPL(S, TL) \ + try { \ + BOOST_TEST_PASSPOINT(); \ + S; \ + } catch(fc::exception const& ex) { \ + BOOST_##TL("no exception expected, but '" << ex.name() << "' thrown: \n" << \ + ex.to_detail_string()); \ + } catch (...) { \ + BOOST_##TL("no exception expected, but unknown exception thrown"); \ + } + +#define GOLOS_WARN_NO_THROW(S) GOLOS_CHECK_NO_THROW_IMPL(S, WARN) +#define GOLOS_CHECK_NO_THROW(S) GOLOS_CHECK_NO_THROW_IMPL(S, ERROR) +#define GOLOS_REQUIRE_NO_THROW(S) GOLOS_CHECK_NO_THROW_IMPL(S, FAIL) + +template +struct ErrorValidator {}; + +using ErrorValidateFunc = std::function; + +#define CHECK_ERROR(exception, ...) [&](const std::string& name, const fc::variant& props) {\ + ErrorValidator v; \ + v.validate(name, props, __VA_ARGS__); \ } -#define REQUIRE_THROW_WITH_VALUE(op, field, value) \ - REQUIRE_THROW_WITH_VALUE_2( op, field, value, fc::exception ) +template<> +struct ErrorValidator { + void validate(const std::string& name, const fc::variant& props, + const std::string& param) { + BOOST_CHECK_EQUAL(name, "invalid_parameter"); + BOOST_CHECK_EQUAL(props["param"].get_string(), param); + } +}; + +template<> +struct ErrorValidator { + void validate(const std::string& name, const fc::variant& props, + const std::string& account, const std::string& balance, const std::string& amount) { + BOOST_CHECK_EQUAL(name, "insufficient_funds"); + BOOST_CHECK_EQUAL(props["account"].get_string(), account); + BOOST_CHECK_EQUAL(props["balance"].get_string(), balance); + BOOST_CHECK_EQUAL(props["required"].get_string(), amount); + } + void validate(const std::string& name, const fc::variant& props, + const std::string& account, const std::string& balance, const golos::protocol::asset& amount) { + BOOST_CHECK_EQUAL(name, "insufficient_funds"); + BOOST_CHECK_EQUAL(props["account"].get_string(), account); + BOOST_CHECK_EQUAL(props["balance"].get_string(), balance); + BOOST_CHECK_EQUAL(props["required"].get_string(), amount.to_string()); + } +}; + +template<> +struct ErrorValidator { + void validate(const std::string& name, const fc::variant& props, + int index, ErrorValidateFunc validator = NULL) { + BOOST_CHECK_EQUAL(name, "tx_invalid_operation"); + BOOST_CHECK_EQUAL(props["index"].as_int64(), index); + if(validator) { + validator(props["error"]["name"].get_string(), props["error"]["stack"][(size_t)0]["data"]); + } + } +}; + +template<> +struct ErrorValidator { + void validate(const std::string& name, const fc::variant& props, + const std::string& type, const std::string& id) { + BOOST_CHECK_EQUAL(name, "missing_object"); + BOOST_CHECK_EQUAL(props["type"].get_string(), type); + BOOST_CHECK_EQUAL(props["id"].get_string(), id); + } + + void validate(const std::string& name, const fc::variant& props, + const std::string& type, const fc::variant_object& id) { + BOOST_CHECK_EQUAL(name, "missing_object"); + BOOST_CHECK_EQUAL(props["type"].get_string(), type); + BOOST_CHECK_EQUAL(props["id"].get_object(), id); + } +}; + +template<> +struct ErrorValidator { + void validate(const std::string& name, const fc::variant& props, + const std::string& type, const std::string& id) { + BOOST_CHECK_EQUAL(name, "object_already_exist"); + BOOST_CHECK_EQUAL(props["type"].get_string(), type); + BOOST_CHECK_EQUAL(props["id"].get_string(), id); + } + + void validate(const std::string& name, const fc::variant& props, + const std::string& type, const fc::variant_object& id) { + BOOST_CHECK_EQUAL(name, "object_already_exist"); + BOOST_CHECK_EQUAL(props["type"].get_string(), type); + BOOST_CHECK_EQUAL(props["id"].get_object(), id); + } +}; + +template<> +struct ErrorValidator { + template + void validate(const std::string& name, const fc::variant& props, errors err) { + BOOST_CHECK_EQUAL(name, "logic_exception"); + BOOST_CHECK_EQUAL(props["errid"].get_string(), fc::reflector::to_string(err)); + BOOST_CHECK_EQUAL(props["namespace"].get_string(), golos::get_logic_error_namespace()); + } +}; + +template<> +struct ErrorValidator { + void validate(const std::string& name, const fc::variant& props, + golos::bandwidth_exception::bandwidth_types type) { + BOOST_CHECK_EQUAL(name, "bandwidth_exception"); + BOOST_CHECK_EQUAL(props["bandwidth"].get_string(), + fc::reflector::to_string(type)); + BOOST_CHECK_NO_THROW(props["now"].get_string()); + BOOST_CHECK_NO_THROW(props["next"].get_string()); + } +}; + +#define SIMPLE_PROTOCOL_ERROR_VALIDATOR(E) SIMPLE_ERROR_VALIDATOR(golos::protocol, E) +#define SIMPLE_ERROR_VALIDATOR(NS, E) \ +template<> \ +struct ErrorValidator { \ + void validate(const std::string& name, const fc::variant& props, int) { \ + BOOST_CHECK_EQUAL(name, #E); \ + } \ +}; + +// Auto-generate trivial ErrorValidators +SIMPLE_PROTOCOL_ERROR_VALIDATOR(tx_irrelevant_sig); +SIMPLE_PROTOCOL_ERROR_VALIDATOR(tx_duplicate_sig); +SIMPLE_PROTOCOL_ERROR_VALIDATOR(tx_duplicate_transaction); +SIMPLE_PROTOCOL_ERROR_VALIDATOR(tx_missing_posting_auth); +SIMPLE_PROTOCOL_ERROR_VALIDATOR(tx_missing_active_auth); +SIMPLE_PROTOCOL_ERROR_VALIDATOR(tx_missing_owner_auth); +SIMPLE_PROTOCOL_ERROR_VALIDATOR(tx_missing_other_auth); + + +#define GOLOS_CHECK_ERROR_PROPS_IMPL(S, C, TL) \ + GOLOS_CHECK_THROW_PROPS_IMPL(S, golos::golos_exception, C(ex.name(), ex.get_log().at(0).get_data()), TL) + +#define GOLOS_WARN_ERROR_PROPS(S, C) GOLOS_CHECK_ERROR_PROPS_IMPL(S, C, WARN) +#define GOLOS_CHECK_ERROR_PROPS(S, C) GOLOS_CHECK_ERROR_PROPS_IMPL(S, C, ERROR) +#define GOLOS_REQUIRE_ERROR_PROPS(S, C) GOLOS_CHECK_ERROR_PROPS_IMPL(S, C, FAIL) + ///This simply resets v back to its default-constructed value. Requires v to have a working assingment operator and /// default constructor. @@ -141,11 +256,125 @@ extern uint32_t ( STEEMIT_TESTING_GENESIS_TIMESTAMP ); // that is why comparision can be done only with some correction #define GOLOS_VEST_REQUIRE_EQUAL(left, right) \ BOOST_REQUIRE( \ - std::abs((left).amount.value - (right).amount.value) < 5 && \ - (left).symbol == (right).symbol \ + std::abs((left).amount.value - (right).amount.value) < 5 && \ + (left).symbol == (right).symbol \ ) +// ostream << +//------------------------------------------------------------- + +namespace fc { + +std::ostream& operator<<(std::ostream& out, const fc::exception& e); +std::ostream& operator<<(std::ostream& out, const fc::time_point& v); +std::ostream& operator<<(std::ostream& out, const fc::uint128_t& v); +std::ostream& operator<<(std::ostream& out, const fc::fixed_string& v); +std::ostream& operator<<(std::ostream& out, const fc::variant_object& v); + +template +std::ostream& operator<<(std::ostream& out, const fc::safe& v) { + out << v.value; + return out; +} + +bool operator==(const fc::variant_object& left, const fc::variant_object& right); + +} // fc + + +namespace fc { namespace ecc { + +std::ostream& operator<<(std::ostream& out, const public_key& v); + +} } // fc::ecc + + +namespace chainbase { + +template +std::ostream& operator<<(std::ostream& out, const object_id &v) { + out << v._id; + return out; +} + +} // chainbase + + +namespace std { + +template +std::ostream& operator<<(std::ostream& out, const std::pair& v) { + out << "<" << v.first << ":" << v.second << ">"; + return out; +} + +template +std::ostream& operator<<(std::ostream& out, const std::vector &vec) { + out << "("; + if (!vec.empty()) { + std::for_each(vec.begin(), vec.end()-1, [&](const T& v) {out << v << ",";}); + out << *vec.rbegin(); + } + out << ")"; + return out; +} + +} // std + + +namespace boost { namespace container { + +template +std::ostream& operator<<(std::ostream& out, const flat_set& t) { + out << "("; + if (!t.empty()) { + std::for_each(t.begin(), t.end()-1, [&](const T& v) {out << v << ",";}); + out << *t.rbegin(); + } + out << ")"; + return out; +} + +template +std::ostream& operator<<(std::ostream& out, const flat_map& t) { + out << "("; + if (!t.empty()) { + std::for_each(t.begin(), t.end()-1, + [&](const typename flat_map::value_type& v) { + out << v.first << ":" << v.second << ","; + }); + auto last = *t.rbegin(); + out << last.first << ":" << last.second; + } + out << ")"; + return out; +} + +} } // boost::container + + +namespace golos { namespace protocol { + +std::ostream& operator<<(std::ostream& out, const asset& v); +std::ostream& operator<<(std::ostream& out, const public_key_type& v); +std::ostream& operator<<(std::ostream& out, const authority& v); +std::ostream& operator<<(std::ostream& out, const price& v); + +} } // golos::protocol + + +namespace golos { namespace chain { + +std::ostream& operator<<(std::ostream& out, const shared_authority& v); + +} } // golos::chain + + +/////////////////////////////////////////////////////////////// +// database_fixture +/////////////////////////////////////////////////////////////// + #ifndef STEEMIT_INIT_PRIVATE_KEY # define STEEMIT_INIT_PRIVATE_KEY (fc::ecc::private_key::regenerate(fc::sha256::hash(BLOCKCHAIN_NAME))) @@ -155,27 +384,49 @@ namespace golos { namespace chain { using namespace golos::protocol; + fc::variant_object make_comment_id(const std::string& author, const std::string& permlink); + fc::variant_object make_limit_order_id(const std::string& author, uint32_t orderid); + fc::variant_object make_convert_request_id(const std::string& account, uint32_t requestid); + fc::variant_object make_escrow_id(const string& name, uint32_t escrow_id); + + using account_name_set = fc::flat_set; + + namespace { + template + struct PluginRegistrator; + + template + struct PluginRegistrator { + static void register_plugins() { + appbase::app().register_plugin

(); + PluginRegistrator::register_plugins(); + } + }; + + template<> + struct PluginRegistrator<> { + static void register_plugins() {} + }; + } // anonymous namespace + + struct database_fixture { - // the reason we use an app is to exercise the indexes of built-in - // plugins - chain::database *db; + // the reason we use an app is to exercise the indexes of built-in plugins + chain::database* db; signed_transaction trx; fc::ecc::private_key init_account_priv_key = STEEMIT_INIT_PRIVATE_KEY; string debug_key = golos::utilities::key_to_wif(init_account_priv_key); public_key_type init_account_pub_key = init_account_priv_key.get_public_key(); - uint32_t default_skip = 0 | database::skip_undo_history_check | - database::skip_authority_check; + uint32_t default_skip = 0 | database::skip_undo_history_check | database::skip_authority_check; - golos::plugins::chain::plugin *ch_plugin = nullptr; - golos::plugins::debug_node::plugin *db_plugin = nullptr; - golos::plugins::operation_history::plugin *oh_plugin = nullptr; - golos::plugins::account_history::plugin *ah_plugin = nullptr; + golos::plugins::chain::plugin* ch_plugin = nullptr; + golos::plugins::debug_node::plugin* db_plugin = nullptr; + golos::plugins::account_history::plugin* ah_plugin = nullptr; + golos::plugins::social_network::social_network* sn_plugin = nullptr; optional data_dir; bool skip_key_index_test = false; - uint32_t anon_acct_count; - database_fixture() { } @@ -183,17 +434,75 @@ namespace golos { namespace chain { static fc::ecc::private_key generate_private_key(string seed); - string generate_anon_acct_name(); + template + Plugin* find_plugin() { + return dynamic_cast(appbase::app().find_plugin()); + } + + using plugin_options = std::map; + template + void initialize(const plugin_options& opts = {}) { + int argc = boost::unit_test::framework::master_test_suite().argc; + char** argv = boost::unit_test::framework::master_test_suite().argv; + + for (int i = 1; i < argc; i++) { + const std::string arg = argv[i]; + if (arg == "--record-assert-trip") { + fc::enable_record_assert_trip = true; + } + if (arg == "--show-test-names") { + std::cout << "running test " + << boost::unit_test::framework::current_test_case().p_name + << std::endl; + } + } + + ch_plugin = &appbase::app().register_plugin(); + db_plugin = &appbase::app().register_plugin(); + sn_plugin = &appbase::app().register_plugin(); + ah_plugin = &appbase::app().register_plugin(); + PluginRegistrator::register_plugins(); + + ch_plugin->skip_startup = true; + + std::vector args_data; + // fill all elements first so c_str() pointers stay valid + for (const auto& opt: opts) { + args_data.push_back(std::string("--") + opt.first); + args_data.push_back(opt.second); + } + std::vector args; + args.push_back(argv[0]); + for (const auto& arg: args_data) { + args.push_back(arg.c_str()); + } + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); + } + + appbase::app().initialize< + golos::plugins::chain::plugin, + golos::plugins::account_history::plugin, + golos::plugins::debug_node::plugin, + golos::plugins::social_network::social_network, + Plugins... + >(args.size(), const_cast(args.data())); + + db_plugin->set_logging(false); + + db = &ch_plugin->db(); + BOOST_REQUIRE(db); + } - void initialize(); void startup(bool generate_hardfork = true); void open_database(); void close_database(); - void generate_block(uint32_t skip = 0, - const fc::ecc::private_key &key = STEEMIT_INIT_PRIVATE_KEY, - int miss_blocks = 0); + void generate_block( + uint32_t skip = 0, + const fc::ecc::private_key& key = STEEMIT_INIT_PRIVATE_KEY, + int miss_blocks = 0); /** * @brief Generates block_count blocks @@ -259,6 +568,23 @@ namespace golos { namespace chain { vector get_last_operations(uint32_t ops); + // we have producer_reward virtual op now, so get_last_operations + // can be hard to use after generate_blocks. + // this get_last_operations variant helps to get only required op type + template + vector get_last_operations(uint32_t num_ops) { + vector ops; + const auto& acc_hist_idx = db->get_index().indices().get(); + auto itr = acc_hist_idx.end(); + while (itr != acc_hist_idx.begin() && ops.size() < num_ops) { + itr--; + auto op = fc::raw::unpack(db->get(itr->op).serialized_op); + if (op.which() == operation::tag::value) + ops.push_back(op.get()); + } + return ops; + } + void validate_database(void); @@ -305,11 +631,27 @@ namespace golos { namespace chain { fc::path _chain_dir; }; + struct add_operations_database_fixture : public database_fixture { + using operation_history_plugin = golos::plugins::operation_history::plugin; + using operations_map = std::map; + + template + void initialize(const plugin_options& opts = {}) { + database_fixture::initialize(opts); + oh_plugin = find_plugin(); + open_database(); + startup(); + } + + operations_map add_operations(); + operation_history_plugin* oh_plugin = nullptr; + }; + namespace test { - bool _push_block(database &db, const signed_block &b, uint32_t skip_flags = 0); + bool _push_block(database& db, const signed_block& b, uint32_t skip_flags = 0); - void _push_transaction(database &db, const signed_transaction &tx, uint32_t skip_flags = 0); + void _push_transaction(database& db, const signed_transaction& tx, uint32_t skip_flags = 0); } } } // golos:chain -#endif \ No newline at end of file +#endif diff --git a/tests/common/helpers.hpp b/tests/common/helpers.hpp new file mode 100644 index 0000000000..67f2adac5e --- /dev/null +++ b/tests/common/helpers.hpp @@ -0,0 +1,56 @@ +#pragma once + +// Common code sequences for testing and boost container extensions + + +// Validate +//------------------------------------------------------------- + +/// validate operation +#define CHECK_OP_VALID(OP) BOOST_CHECK_NO_THROW(OP.validate()) + +/// change operation parameter value and check it's still valid +#define CHECK_PARAM_VALID(OP, NAME, VAL) \ + CHECK_PARAM_VALID_I(OP, NAME, VAL, CHECK_OP_VALID(OP)) + +/// change operation parameter value and check it's invalid (+restore param) +#define CHECK_PARAM_INVALID(OP, N, V) \ + CHECK_PARAM_VALIDATION_FAIL(OP, N, V, CHECK_ERROR(invalid_parameter, #N)) + +/// change operation parameter value and check it fails with logic_exceprion (+restore param) +#define CHECK_PARAM_INVALID_LOGIC(OP, N, V, EX) \ + CHECK_PARAM_VALIDATION_FAIL(OP, N, V, CHECK_ERROR(logic_exception, logic_exception:: EX)) + +/// same as previous but with configurable CHECK_ERROR parameters +#define CHECK_PARAM_VALIDATION_FAIL(OP, N, V, VALIDATOR) \ + CHECK_PARAM_VALID_I(OP, N, V, \ + GOLOS_CHECK_ERROR_PROPS(OP.validate(), VALIDATOR)) + + +// Check operation authorities +#define CHECK_OP_AUTHS(OP, OWNER, ACTIVE, POSTING) { \ + golos::chain::account_name_set \ + owner_auths, active_auths, posting_auths; \ + OP.get_required_owner_authorities(owner_auths); \ + BOOST_CHECK_EQUAL(owner_auths, OWNER); \ + OP.get_required_active_authorities(active_auths); \ + BOOST_CHECK_EQUAL(active_auths, ACTIVE); \ + OP.get_required_posting_authorities(posting_auths); \ + BOOST_CHECK_EQUAL(posting_auths, POSTING); \ +} + +// Check if 2 numeric values are approximately equal +#define APPROX_CHECK_EQUAL(X, Y, DELTA) { \ + BOOST_CHECK(std::abs(X - Y) <= DELTA); \ +} + +// internals +//------------------------------------------------------------- + +// validate +#define CHECK_PARAM_VALID_I(OP, NAME, VAL, CHECK) { \ + auto t = OP.NAME; \ + OP.NAME = VAL; \ + CHECK; \ + OP.NAME = t; \ +} diff --git a/tests/plugin_tests/account_history.cpp b/tests/plugin_tests/account_history.cpp new file mode 100644 index 0000000000..b56a65a206 --- /dev/null +++ b/tests/plugin_tests/account_history.cpp @@ -0,0 +1,178 @@ +#include +#include + +#include "database_fixture.hpp" + + +using namespace golos::chain; +using golos::plugins::json_rpc::msg_pack; +using golos::plugins::operation_history::applied_operation; +using namespace golos::plugins::account_history; + + +struct account_history_fixture : public add_operations_database_fixture { + using checked_accounts_map = std::map>; // pair { [block], [[account names] operations]} + + checked_accounts_map check(const account_name_set& names) { + checked_accounts_map _found_accs; + for (auto n : names) { + msg_pack mp; + mp.args = std::vector({fc::variant(n), fc::variant(100), fc::variant(100)}); + auto accs = ah_plugin->get_account_history(mp); + for (auto a : accs) { + auto it = _found_accs.find(a.second.block); + if (it == _found_accs.end()) { + std::set set; + set.insert(n); + _found_accs.insert(std::make_pair(a.second.block, set)); + } else { + it->second.insert(n); + } + } + } + return _found_accs; + } +}; + + +BOOST_FIXTURE_TEST_SUITE(account_history_plugin, account_history_fixture) + +BOOST_AUTO_TEST_CASE(account_history_blocks) { + BOOST_TEST_MESSAGE("Testing: account_history_blocks"); + const uint32_t HISTORY_BLOCKS = 3; + initialize({{"history-blocks", std::to_string(HISTORY_BLOCKS)}}); + add_operations(); + + account_name_set names = {"alice", "bob", "sam", "dave"}; + auto _found_accs = check(names); + + std::set blocks; + for (auto a : _found_accs) { + for (auto n : a.second) { + ilog("block:" + std::to_string(a.first) + ", \"" + n + "\""); + auto iter = names.find(n); + bool is_found = (iter != names.end()); + BOOST_CHECK(is_found); + if (is_found) { + blocks.insert(a.first); + } else { + BOOST_TEST_MESSAGE("Account [" + std::to_string(a.first) + "]: \"" + n + "\" is not found"); + } + } + } + BOOST_CHECK_EQUAL(blocks.size(), HISTORY_BLOCKS); +} + + +/////////////////////////////////////////////////////////////// +// filtering +/////////////////////////////////////////////////////////////// +struct accounts_filter_fixture : public add_operations_database_fixture { + accounts_filter_fixture() {}; + ~accounts_filter_fixture() {}; +}; + + +struct op_name_visitor { + using result_type = std::string; + template + std::string operator()(const T&) const { + auto n = std::string(fc::get_typename::name()); + auto prefix_len = sizeof("golos::protocol::") - 1; + return n.substr(prefix_len, n.size() - prefix_len + 1 - sizeof("_operation")); + } +}; + +BOOST_AUTO_TEST_CASE(account_history_filter) { + initialize(); + +# define INITIAL_OPS "account_create|transfer|transfer_to_vesting" + using op_names = fc::flat_set; + + auto get_history = [this](const std::string& acc, const int from, const int limit, const account_history_query& q) { + msg_pack mp; + mp.args = std::vector({ + fc::variant(acc), fc::variant(from), fc::variant(limit), fc::variant(q)}); + auto ops = ah_plugin->get_account_history(mp); + return ops; + }; + + BOOST_TEST_MESSAGE("Testing: account_history_filter"); + + // NOTE: this test depends on operations generated with add_operations() + add_operations(); + + auto check_ops = [](const history_operations& ops, const std::string& expected) { + op_name_visitor ovisit; + std::vector chk; + if (!expected.empty()) { + boost::split(chk, expected, boost::is_any_of("|")); + } + BOOST_CHECK_EQUAL(chk.size(), ops.size()); + int i = 0; + for (const auto& o : ops) { + const auto got_op = o.second.op.visit(ovisit); + BOOST_CHECK_EQUAL(got_op, chk[i++]); + } + }; + + auto q = account_history_query(); + BOOST_TEST_MESSAGE("--- Test non-filtered history"); + auto alice_all = get_history("alice", -1, 10, q); + check_ops(alice_all, INITIAL_OPS "|vote"); + + auto bob_all = get_history("bob", -1, 10, q); + check_ops(bob_all, INITIAL_OPS "|comment|vote|vote|vote|delete_comment"); + + auto sam_all = get_history("sam", -1, 10, q); + check_ops(sam_all, INITIAL_OPS "|vote"); + + auto dave_all = get_history("dave", -1, 10, q); + check_ops(dave_all, "account_create"); + + auto cf_all = get_history("cyberfounder", -1, 10, q); + BOOST_CHECK_EQUAL(11, cf_all.size()); + check_ops(cf_all, "account_create|account_create|account_create|transfer|transfer|transfer|" + "producer_reward|producer_reward|producer_reward|account_create|producer_reward"); + + BOOST_TEST_MESSAGE("--- Test 'sender' direction"); + q.direction = operation_direction::sender; + auto bob_sender = get_history("bob", -1, 10, q); + check_ops(bob_sender, "transfer_to_vesting|comment|vote|delete_comment"); + + BOOST_TEST_MESSAGE("--- Test 'receiver' direction"); + q.direction = operation_direction::receiver; + auto dave_receiver = get_history("dave", -1, 10, q); + check_ops(dave_receiver, "account_create"); + + BOOST_TEST_MESSAGE("--- Test 'dual' direction"); + q.direction = operation_direction::dual; + auto bob_dual = get_history("bob", -1, 10, q); + check_ops(bob_dual, "transfer_to_vesting|vote|delete_comment"); + + BOOST_TEST_MESSAGE("--- Test virtual only select"); + q.direction = operation_direction::any; + q.select_ops = op_names({"VIRTUAL"}); + auto cf_vops = get_history("cyberfounder", -1, 10, q); + check_ops(cf_vops, "producer_reward|producer_reward|producer_reward|producer_reward|producer_reward|producer_reward"); + + BOOST_TEST_MESSAGE("--- Test virtual select with filtered op"); + q.filter_ops = op_names({"producer_reward_operation"}); + auto cf_vops2 = get_history("cyberfounder", -1, 10, q); + check_ops(cf_vops2, ""); + + BOOST_TEST_MESSAGE("--- Test pagination"); + generate_blocks(3); + q.select_ops = op_names({"REAL", "producer_reward"}); + q.filter_ops = op_names({"account_create", "transfer"}); + auto cf_ops = get_history("cyberfounder", -1, 6, q); + check_ops(cf_ops, "producer_reward|producer_reward|producer_reward|producer_reward|producer_reward|producer_reward|producer_reward"); + for (const auto& i: cf_ops) { + uint32_t page2 = i.first; + auto cf_ops2 = get_history("cyberfounder", page2, std::min(page2, uint32_t(6)), q); + check_ops(cf_ops2, "producer_reward|custom|producer_reward|transfer_to_vesting|producer_reward"); + break; + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/plugin_tests/chain.cpp b/tests/plugin_tests/chain.cpp new file mode 100644 index 0000000000..729a74700d --- /dev/null +++ b/tests/plugin_tests/chain.cpp @@ -0,0 +1,289 @@ +#include +#include + +#include "database_fixture.hpp" +#include "helpers.hpp" + +#include +#include +#include + +using golos::protocol::comment_operation; +using golos::protocol::vote_operation; +using golos::protocol::public_key_type; +using golos::protocol::signed_transaction; +using golos::chain::account_id_type; +using golos::chain::account_name_set; + +using namespace golos::plugins::chain; + + +struct chain_fixture : public golos::chain::database_fixture { + + void initialize(const plugin_options& opts = {}) { + database_fixture::initialize(opts); + open_database(); + startup(); + } + + fc::ecc::private_key vote_key; + uint32_t current_voter = 0; + static const uint32_t cashout_blocks = STEEMIT_CASHOUT_WINDOW_SECONDS / STEEMIT_BLOCK_INTERVAL; + + void generate_voters(uint32_t n) { + fc::ecc::private_key private_key = generate_private_key("test"); + fc::ecc::private_key post_key = generate_private_key("test_post"); + vote_key = post_key; + for (auto i = 0; i < n; i++) { + const auto name = "voter" + std::to_string(i); + GOLOS_CHECK_NO_THROW(account_create(name, private_key.get_public_key(), post_key.get_public_key())); + } + generate_block(); + validate_database(); + } + + void vote_sequence(const std::string& author, const std::string& permlink, uint32_t n_votes, uint32_t interval = 0) { + uint32_t end = current_voter + n_votes; + for (; current_voter < end; current_voter++) { + const auto name = "voter" + std::to_string(current_voter); + vote_operation op; + op.voter = name; + op.author = author; + op.permlink = permlink; + op.weight = STEEMIT_100_PERCENT; + signed_transaction tx; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(tx, vote_key, op)); + if (interval > 0) { + generate_blocks(interval); + } + } + validate_database(); + } + + void post(const std::string& permlink = "post", const std::string& parent_permlink = "test") { + ACTOR(alice); + comment_operation op; + op.author = "alice"; + op.permlink = permlink; + op.parent_author = parent_permlink == "test" ? "" : "alice"; + op.parent_permlink = parent_permlink; + op.title = "foo"; + op.body = "bar"; + signed_transaction tx; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + validate_database(); + } + + uint32_t count_stored_votes() { + const auto n = db->get_index().indices().size(); + return n; + } +}; + + +BOOST_FIXTURE_TEST_SUITE(chain_plugin, chain_fixture) + +BOOST_AUTO_TEST_SUITE(clear_votes) + +BOOST_AUTO_TEST_CASE(clear_votes_default) { + BOOST_TEST_MESSAGE("Testing: clear_votes_default"); + initialize(); + generate_voters(10); + post(); + BOOST_TEST_MESSAGE("--- vote 9 times and check votes stored"); + vote_sequence("alice", "post", 5); + auto interval = cashout_blocks / 5; + vote_sequence("alice", "post", 4, interval); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to 1 block before cashout and check 9 votes stored"); + generate_blocks(interval - 1); + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK(post.mode != golos::chain::archived); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to cashout block and check 9 votes stored"); + generate_blocks(1); + { + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK_EQUAL(post.mode, golos::chain::archived); + } + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to 2 * cashout time forward and check 9 votes stored"); + generate_blocks(cashout_blocks * 2); + BOOST_CHECK_EQUAL(9, count_stored_votes()); +} + +BOOST_AUTO_TEST_CASE(clear_votes_before_block) { + BOOST_TEST_MESSAGE("Testing: clear_votes_before_block"); + int before_block = 4 + cashout_blocks * 2; + initialize({ + {"clear-votes-before-block", std::to_string(before_block)}, + }); + + generate_voters(15); + post(); + BOOST_TEST_MESSAGE("--- vote 9 times and check votes stored"); + vote_sequence("alice", "post", 5); + auto interval = cashout_blocks / 5; + vote_sequence("alice", "post", 4, interval); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to 1 block before cashout and check 9 votes stored"); + generate_blocks(interval - 1); + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK(post.mode != golos::chain::archived); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to cashout block and check votes removed"); + generate_blocks(1); + { + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK_EQUAL(post.mode, golos::chain::archived); + } + BOOST_CHECK_EQUAL(0, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to just before 'clear-votes-before-block', vote and check 1 vote stored"); + generate_blocks(cashout_blocks - 1); + vote_sequence("alice", "post", 1); + BOOST_CHECK_EQUAL(1, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- check, vote removed in the next block"); + generate_blocks(1); + BOOST_CHECK_EQUAL(0, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- vote 5 times and check 5 votes stored"); + vote_sequence("alice", "post", 5, 10); + BOOST_CHECK_EQUAL(5, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to future and check 5 votes still stored"); + generate_blocks(cashout_blocks); + BOOST_CHECK_EQUAL(5, count_stored_votes()); +} + +BOOST_AUTO_TEST_CASE(clear_votes_before_cashout_block) { + BOOST_TEST_MESSAGE("Testing: clear_votes_before_cashout_block"); + int before_block = cashout_blocks / 2; + initialize({ + {"clear-votes-before-block", std::to_string(before_block)}, + }); + + generate_voters(10); + post(); + BOOST_TEST_MESSAGE("--- vote 9 times and check votes stored"); + vote_sequence("alice", "post", 5); + auto interval = cashout_blocks / 5; + vote_sequence("alice", "post", 4, interval); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to 1 block before cashout and check 9 votes stored"); + generate_blocks(interval - 1); + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK(post.mode != golos::chain::archived); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to cashout block and check 9 votes still stored"); + generate_blocks(1); + { + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK_EQUAL(post.mode, golos::chain::archived); + } + BOOST_CHECK_EQUAL(9, count_stored_votes()); +} + +BOOST_AUTO_TEST_CASE(clear_votes_older_n_larger_cashout) { + BOOST_TEST_MESSAGE("Testing: clear_votes_older_n_larger_cashout"); + int n_blocks = cashout_blocks * 2; + initialize({ + {"clear-votes-older-n-blocks", std::to_string(n_blocks)}, + }); + + generate_voters(15); + post(); + BOOST_TEST_MESSAGE("--- vote 9 times and check votes stored"); + vote_sequence("alice", "post", 5); + auto interval = cashout_blocks / 5; + vote_sequence("alice", "post", 4, interval); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to 1 block before cashout and check 9 votes stored"); + generate_blocks(interval - 1); + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK(post.mode != golos::chain::archived); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to cashout block and check 9 votes still stored"); + generate_blocks(1); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + { + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK_EQUAL(post.mode, golos::chain::archived); + } + + BOOST_TEST_MESSAGE("--- go to block just before 'clear_votes_older_n', vote and check 10 votes stored"); + generate_blocks(cashout_blocks); + vote_sequence("alice", "post", 1); + BOOST_CHECK_EQUAL(10, count_stored_votes()); + + // 6 = 5 instant created and 1 from first interval + BOOST_TEST_MESSAGE("--- go to block just after 'clear_votes_older_n' and check 6 votes removed"); + generate_blocks(1); + BOOST_CHECK_EQUAL(4, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- vote 5 times and check 9 votes stored"); + vote_sequence("alice", "post", 4, 10); + vote_sequence("alice", "post", 1); + BOOST_CHECK_EQUAL(9, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to 'clear_votes_older_n' blocks forward and check 1 vote stored"); + generate_blocks(n_blocks); + BOOST_CHECK_EQUAL(1, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to 1 block forward and check all votes removed"); + generate_blocks(1); + BOOST_CHECK_EQUAL(0, count_stored_votes()); +} + +BOOST_AUTO_TEST_CASE(clear_votes_older_n_smaller_cashout) { + BOOST_TEST_MESSAGE("Testing: clear_votes_older_n_smaller_cashout"); + int n_blocks = cashout_blocks / 4; + initialize({ + {"clear-votes-older-n-blocks", std::to_string(n_blocks)}, + }); + + generate_voters(15); + post(); + BOOST_TEST_MESSAGE("--- vote 10 times and check votes stored"); + vote_sequence("alice", "post", 5); + auto interval = cashout_blocks / 5; + vote_sequence("alice", "post", 4, interval); + vote_sequence("alice", "post", 1); + BOOST_CHECK_EQUAL(10, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to 1 block before cashout and check 10 votes stored"); + generate_blocks(interval - 1); + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK(post.mode != golos::chain::archived); + BOOST_CHECK_EQUAL(10, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go to cashout block and check 8 votes removed"); + generate_blocks(1); + BOOST_CHECK_EQUAL(1, count_stored_votes()); + { + const auto& post = db->get_comment("alice", std::string("post")); + BOOST_CHECK_EQUAL(post.mode, golos::chain::archived); + } + + BOOST_TEST_MESSAGE("--- go forward just before last vote should be removed and check it stored"); + generate_blocks(n_blocks - interval); + BOOST_CHECK_EQUAL(1, count_stored_votes()); + + BOOST_TEST_MESSAGE("--- go 1 block forward and check all votes removed"); + generate_blocks(1); + BOOST_CHECK_EQUAL(0, count_stored_votes()); +} + +BOOST_AUTO_TEST_SUITE_END() // clear_votes + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/plugin_tests/follow.cpp b/tests/plugin_tests/follow.cpp new file mode 100644 index 0000000000..3d6683da5e --- /dev/null +++ b/tests/plugin_tests/follow.cpp @@ -0,0 +1,202 @@ +#include + +#include + +#include "database_fixture.hpp" +#include "helpers.hpp" + +#include + +#include +#include + +using boost::container::flat_set; + +using golos::plugins::json_rpc::msg_pack; + +using golos::logic_exception; +using golos::missing_object; +using golos::protocol::comment_operation; +using golos::protocol::tx_invalid_operation; +using golos::protocol::public_key_type; +using golos::protocol::signed_transaction; +using golos::protocol::custom_binary_operation; +using golos::chain::account_id_type; +using golos::chain::make_comment_id; + +using golos::chain::account_name_set; + +using namespace golos::plugins::follow; + + +struct follow_fixture : public golos::chain::database_fixture { + follow_fixture() : golos::chain::database_fixture() { + initialize(); + open_database(); + startup(); + } +}; + + +BOOST_FIXTURE_TEST_SUITE(follow_plugin, follow_fixture) + +BOOST_AUTO_TEST_CASE(follow_validate) { + BOOST_TEST_MESSAGE("Testing: follow_validate"); + + ACTORS((alice)(bob)); + + follow_operation op; + op.follower = "alice"; + op.following = "bob"; + op.what = {"blog"}; + CHECK_OP_VALID(op); + + CHECK_PARAM_VALIDATION_FAIL(op, following, "alice", + CHECK_ERROR(logic_exception, logic_errors::cannot_follow_yourself)); +} + +BOOST_AUTO_TEST_CASE(follow_authorities) { + BOOST_TEST_MESSAGE("Testing: follow_authorities"); + + follow_operation op; + op.follower = "alice"; + op.following = "bob"; + op.what = {"blog"}; + + CHECK_OP_AUTHS(op, account_name_set(), account_name_set(), account_name_set({"alice"})); +} + +BOOST_AUTO_TEST_CASE(follow_apply) { + BOOST_TEST_MESSAGE("Testing: follow_apply"); + + ACTORS((alice)(bob)); + + generate_blocks(60 / STEEMIT_BLOCK_INTERVAL); + + custom_binary_operation cop; + cop.required_posting_auths.insert("alice"); + cop.id = "follow"; + + boost::container::vector vec; + signed_transaction tx; + + BOOST_TEST_MESSAGE("--- success execution"); + follow_operation op; + op.follower = "alice"; + op.following = "bob"; + op.what = {"blog"}; + + vec.push_back(op); + cop.data = fc::raw::pack(vec); + + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, cop)); + + BOOST_TEST_MESSAGE("--- failed when 'blog' & 'ignore' at the same time"); + op.what = {"blog", "ignore"}; + + vec.clear(); + vec.push_back(op); + cop.data = fc::raw::pack(vec); + + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, cop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_errors::cannot_follow_and_ignore_simultaneously))); +} + +BOOST_AUTO_TEST_CASE(reblog_validate) { + BOOST_TEST_MESSAGE("Testing: reblog_validate"); + + reblog_operation op; + op.account = "alice"; + op.author = "bob"; + op.permlink = "foo"; + + CHECK_OP_VALID(op); + + CHECK_PARAM_VALIDATION_FAIL(op, author, "alice", + CHECK_ERROR(logic_exception, logic_errors::cannot_reblog_own_content)); +} + +BOOST_AUTO_TEST_CASE(reblog_authorities) { + BOOST_TEST_MESSAGE("Testing: reblog_authorities"); + + reblog_operation op; + op.account = "alice"; + op.author = "bob"; + op.permlink = "foo"; + + CHECK_OP_AUTHS(op, account_name_set(), account_name_set(), account_name_set({"alice"})); +} + +BOOST_AUTO_TEST_CASE(reblog_apply) { + BOOST_TEST_MESSAGE("Testing: reblog_apply"); + + ACTORS((alice)(bob)); + + generate_blocks(60 / STEEMIT_BLOCK_INTERVAL); + signed_transaction tx; + + { + comment_operation op; + op.author = "bob"; + op.permlink = "lorem"; + op.parent_author = ""; + op.parent_permlink = "ipsum"; + op.title = "Lorem Ipsum"; + op.body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + op.json_metadata = "{\"foo\":\"bar\"}"; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); + + op.author = "alice"; + op.permlink = "foo"; + op.parent_author = "bob"; + op.parent_permlink = "lorem"; + op.title = "Lorem Ipsum"; + op.body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + } + + custom_binary_operation cop; + cop.required_posting_auths.insert("alice"); + cop.id = "follow"; + + boost::container::vector vec; + + BOOST_TEST_MESSAGE("--- success execution"); + reblog_operation op; + op.account = "alice"; + op.author = "bob"; + op.permlink = "lorem"; + + vec.clear(); + vec.push_back(op); + cop.data = fc::raw::pack(vec); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, cop)); + + BOOST_TEST_MESSAGE("--- failed when comment is missing"); + op.permlink = "david"; + + vec.clear(); + vec.push_back(op); + cop.data = fc::raw::pack(vec); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, cop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "comment", make_comment_id("bob", "david")))); + + BOOST_TEST_MESSAGE("--- failed when reblog comment"); + op.account = "bob"; + op.author = "alice"; + op.permlink = "foo"; + + vec.clear(); + vec.push_back(op); + cop.data = fc::raw::pack(vec); + cop.required_posting_auths.clear(); + cop.required_posting_auths.insert("bob"); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, cop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_errors::only_top_level_posts_reblogged))); + +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/plugin_tests/json_rpc.cpp b/tests/plugin_tests/json_rpc.cpp new file mode 100644 index 0000000000..517fe21618 --- /dev/null +++ b/tests/plugin_tests/json_rpc.cpp @@ -0,0 +1,288 @@ +#ifdef STEEMIT_BUILD_TESTNET + +#include + +#include +#include +#include + +#include + +#include "database_fixture.hpp" + +using namespace golos::chain; +using namespace golos::protocol; + +typedef golos::plugins::json_rpc::plugin json_rpc_plugin; + +namespace test_plugin { + + using golos::plugins::json_rpc::msg_pack; + + DEFINE_API_ARGS(throw_exception, msg_pack, std::string) + + class testing_api final : public appbase::plugin { + public: + testing_api() { } + ~testing_api() { } + + constexpr static const char *plugin_name = "testing_api"; + + APPBASE_PLUGIN_REQUIRES((json_rpc_plugin)); + + static const std::string &name() { + static std::string name = plugin_name; + return name; + } + + void set_program_options(boost::program_options::options_description &, + boost::program_options::options_description &) override { + } + + void plugin_initialize(const boost::program_options::variables_map &options) override { + JSON_RPC_REGISTER_API(plugin_name); + } + + void plugin_startup() override { } + + void plugin_shutdown() override { } + + DECLARE_API((throw_exception)) + }; + + DEFINE_API(testing_api, throw_exception) { + auto error = args.args->at(0).get_string(); + + if (error == "unsupported_operation") + FC_THROW_EXCEPTION(golos::unsupported_operation, "Unsupported operation"); + + if (error == "invalid_parameter") + FC_THROW_EXCEPTION(golos::invalid_parameter, "Invalid parameter"); + + if (error == "business_exception") + FC_THROW_EXCEPTION(golos::business_exception, "Business logic error"); + + if (error == "tx_missing_authority") + FC_THROW_EXCEPTION(golos::protocol::tx_missing_authority, "Missing authority"); + + if (error == "tx_invalid_operation") + FC_THROW_EXCEPTION(golos::protocol::tx_invalid_operation, "Invalid operation"); + + if (error == "transaction_exception") + FC_THROW_EXCEPTION(golos::protocol::transaction_exception, "Transaction error"); + + if (error == "golos_exception") + FC_THROW_EXCEPTION(golos::golos_exception, "Internal server exception"); + + if (error == "fc::exception") + FC_THROW_EXCEPTION(fc::exception, "Internal error"); + + if (error == "std::exception") + throw std::logic_error("Internal error"); + + throw "Internal error"; + } +} // namespace test_plugin + +fc::variant call(json_rpc_plugin& plugin, const std::string& request) { + fc::variant response; + plugin.call(request, [&](const std::string& str) {response = fc::json::from_string(str);}); + return response; +} + +void check_error_response(const fc::variant& response, const fc::variant& id, int32_t code, const std::string& error_name = std::string()) { + BOOST_CHECK_EQUAL(response["jsonrpc"].get_string(), "2.0"); + BOOST_CHECK_EQUAL(response["id"].get_type(), id.get_type()); + if (!id.is_null()) { + BOOST_CHECK_EQUAL(response["id"].as_string(), id.as_string()); + } + BOOST_CHECK(response["error"].is_object()); + BOOST_CHECK(response["error"]["message"].is_string()); + BOOST_CHECK(response["error"]["code"].is_integer()); + BOOST_CHECK_EQUAL(response["error"]["code"].as(), code); + + if (!error_name.empty()) { + auto data = response["error"]["data"].get_object(); + BOOST_CHECK_EQUAL(data["name"].get_string(), error_name); + } +} + +BOOST_FIXTURE_TEST_SUITE(json_rpc, database_fixture) + + BOOST_AUTO_TEST_CASE(json_rpc_test) { + try { + initialize(); + + + auto &rpc_plugin = appbase::app().register_plugin(); + auto &testing_api = appbase::app().register_plugin(); + + boost::program_options::variables_map options; + rpc_plugin.plugin_initialize(options); + testing_api.plugin_initialize(options); + + open_database(); + + startup(); + rpc_plugin.plugin_startup(); + testing_api.plugin_startup(); + + + BOOST_TEST_MESSAGE("--- empty request"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "").get_object(); + check_error_response(response, fc::variant(), JSON_RPC_PARSE_ERROR); + }); + + BOOST_TEST_MESSAGE("--- invalid json sctructure"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{]").get_object(); + check_error_response(response, fc::variant(), JSON_RPC_PARSE_ERROR); + }); + + BOOST_TEST_MESSAGE("--- empty array of request"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "[]").get_object(); + check_error_response(response, fc::variant(), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- empty request object"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{}").get_object(); + check_error_response(response, fc::variant(), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- invalid 'jsonrpc' field"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"1.2\"}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- invalid type of 'jsonrpc' field"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":[]}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- invalid 'method' field"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"database_api.get_dynamic_global_properties\"}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- invalid type of 'method' field"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":[]}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- missing 'params' field"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\"}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- invalid type of 'params' field"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":1234}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- invalid type of 'args' field"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",{}]}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INVALID_REQUEST); + }); + + BOOST_TEST_MESSAGE("--- missing API"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"missing_api\",\"missing_method\"]}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_METHOD_NOT_FOUND); + }); + + BOOST_TEST_MESSAGE("--- missing method"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"missing_method\",[]]}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_METHOD_NOT_FOUND); + }); + + BOOST_TEST_MESSAGE("--- return UNSUPPORTED_OPERATION when thrown golos::unsupported_operation"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"unsupported_operation\"]]}").get_object(); + check_error_response(response, fc::variant(1u), SERVER_UNSUPPORTED_OPERATION, "unsupported_operation"); + }); + + BOOST_TEST_MESSAGE("--- return INVALID_PARAMETER when thrown golos::parameter_exception"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"invalid_parameter\"]]}").get_object(); + check_error_response(response, fc::variant(1u), SERVER_INVALID_PARAMETER, "invalid_parameter"); + }); + + BOOST_TEST_MESSAGE("--- return BUSINESS_LOGIC_ERROR when thrown golos::business_exception"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"business_exception\"]]}").get_object(); + check_error_response(response, fc::variant(1u), SERVER_BUSINESS_LOGIC_ERROR, "business_exception"); + }); + + BOOST_TEST_MESSAGE("--- return MISSING_AUTHORITY when thrown golos::protocol::tx_missing_authority"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"tx_missing_authority\"]]}").get_object(); + check_error_response(response, fc::variant(1u), SERVER_MISSING_AUTHORITY, "tx_missing_authority"); + }); + + BOOST_TEST_MESSAGE("--- return INVALID_OPERATION when thrown golos::protocol::tx_invalid_operation"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"tx_invalid_operation\"]]}").get_object(); + check_error_response(response, fc::variant(1u), SERVER_INVALID_OPERATION, "tx_invalid_operation"); + }); + + BOOST_TEST_MESSAGE("--- return INVALID_TRANSACTION when thrown golos::protocol::transaction_exception"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"transaction_exception\"]]}").get_object(); + check_error_response(response, fc::variant(1u), SERVER_INVALID_TRANSACTION, "transaction_exception"); + }); + + BOOST_TEST_MESSAGE("--- return INTERNAL_ERROR when thrown golos::golos_exception"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"golos_exception\"]]}").get_object(); + check_error_response(response, fc::variant(1u), SERVER_INTERNAL_ERROR, "golos_exception"); + }); + + + BOOST_TEST_MESSAGE("--- return INTERNAL_ERROR when thrown fc::exception"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"fc::exception\"]]}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INTERNAL_ERROR); + }); + + BOOST_TEST_MESSAGE("--- return INTERNAL_ERROR when thrown std::excption"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"std::exception\"]]}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INTERNAL_ERROR); + }); + + BOOST_TEST_MESSAGE("--- return INTERNAL_ERROR when thrown unknown exception"); + BOOST_CHECK_NO_THROW({ + auto response = call(rpc_plugin, "{\"id\":1, \"jsonrpc\":\"2.0\",\"method\":\"call\",\"params\":[" + "\"testing_api\",\"throw_exception\",[\"...\"]]}").get_object(); + check_error_response(response, fc::variant(1u), JSON_RPC_INTERNAL_ERROR); + }); + + } + FC_LOG_AND_RETHROW() + } + +BOOST_AUTO_TEST_SUITE_END() +#endif diff --git a/tests/plugin_tests/main.cpp b/tests/plugin_tests/main.cpp index 1b4e0b2839..744d0b4bbc 100644 --- a/tests/plugin_tests/main.cpp +++ b/tests/plugin_tests/main.cpp @@ -23,6 +23,7 @@ */ #include #include +#include #ifdef BOOST_TEST_DYN_LINK #define BOOST_TEST_MODULE plugin_tests @@ -34,6 +35,7 @@ extern uint32_t STEEMIT_TESTING_GENESIS_TIMESTAMP; boost::unit_test::test_suite *init_unit_test_suite(int argc, char *argv[]) { + fc::configure_logging(fc::logging_config::default_config(fc::log_level::error)); std::srand(time(NULL)); std::cout << "Random number generator seeded to " << time(NULL) << std::endl; diff --git a/tests/plugin_tests/operation_history.cpp b/tests/plugin_tests/operation_history.cpp new file mode 100644 index 0000000000..f218b428ca --- /dev/null +++ b/tests/plugin_tests/operation_history.cpp @@ -0,0 +1,153 @@ +#include + +#include "database_fixture.hpp" + +#include +#include + +using golos::chain::add_operations_database_fixture; +using golos::plugins::operation_history::applied_operation; +using golos::plugins::json_rpc::msg_pack; +using golos::protocol::account_create_operation; + +static const std::string OPERATIONS = "account_create_operation,delete_comment_operation,vote,comment"; + +struct operation_visitor { + using result_type = std::string; + template + std::string operator()(const T&) const { + return std::string(fc::get_typename::name()); + } +}; + +void log_applied_options(const applied_operation& ops) { + std::stringstream ss; + ss << "[" << ops.block << "] "; + ss << ops.trx_id.str() << " : "; /// golos::protocol::transaction_id_type + operation_visitor ovisit; + std::string op_name = ops.op.visit(ovisit); + ss << "\"" << op_name << "\""; /// golos::protocol::operation + ilog(ss.str()); +} + +struct operation_history_fixture : public add_operations_database_fixture { + operations_map check_operations() { + uint32_t head_block_num = db->head_block_num(); + ilog("Check history operations, block num is " + std::to_string(head_block_num)); + + operations_map _found_ops; + operation_visitor ovisit; + for (uint32_t i = 1; i <= head_block_num; ++i) { + msg_pack mo; + mo.args = std::vector({fc::variant(i), fc::variant(false)}); + auto ops = oh_plugin->get_ops_in_block(mo); + for (const auto& o : ops) { + auto itr = _found_ops.find(o.trx_id.str()); + if (itr == _found_ops.end()) { + _found_ops.insert(std::make_pair(o.trx_id.str(), o.op.visit(ovisit))); + log_applied_options(o); + } + } + } + return _found_ops; + } +}; + +BOOST_FIXTURE_TEST_SUITE(operation_history_plugin, operation_history_fixture) + +BOOST_AUTO_TEST_CASE(operation_history_blocks) { + const uint32_t HISTORY_BLOCKS = 2; + BOOST_TEST_MESSAGE("Testing: operation_history_blocks"); + initialize({ + {"history-blocks", std::to_string(HISTORY_BLOCKS)}, + {"history-whitelist-ops", OPERATIONS} + }); + + auto _added_ops = add_operations(); + auto _found_ops = check_operations(); + + size_t _checked_ops_count = 0; + for (auto it = _found_ops.begin(); it != _found_ops.end(); ++it) { + auto itr = _added_ops.find(it->first); + bool is_found = itr != _added_ops.end(); + BOOST_CHECK(is_found); + if (is_found) { + BOOST_CHECK_EQUAL(itr->second, it->second); + if (itr->second == it->second) { + ++_checked_ops_count; + } + } else { + BOOST_TEST_MESSAGE("Operation \"" + it->second + "\" by \"" + it->first + "\" is not found"); + } + } + BOOST_CHECK_EQUAL(_checked_ops_count, HISTORY_BLOCKS); +} + +BOOST_AUTO_TEST_CASE(black_options_postfix) { + BOOST_TEST_MESSAGE("Testing: black_options_postfix"); + initialize({{"history-blacklist-ops", OPERATIONS}}); + + auto _added_ops = add_operations(); + auto _found_ops = check_operations(); + + size_t _checked_ops_count = 0; + for (const auto& co : _added_ops) { + auto itr = _found_ops.find(co.first); + bool is_not_found = itr == _found_ops.end(); + BOOST_CHECK(is_not_found); + if (is_not_found) { + ++_checked_ops_count; + } else { + BOOST_TEST_MESSAGE("Operation \"" + co.second + "\" by \"" + co.first + "\" is found"); + } + } + BOOST_CHECK_EQUAL(_checked_ops_count, _added_ops.size()); +} + +BOOST_AUTO_TEST_CASE(white_options_postfix) { + BOOST_TEST_MESSAGE("Testing: white_options_postfix"); + initialize({{"history-whitelist-ops", OPERATIONS}}); + + auto _added_ops = add_operations(); + auto _found_ops = check_operations(); + + size_t _checked_ops_count = 0; + for (const auto& co : _added_ops) { + auto itr = _found_ops.find(co.first); + bool is_found = itr != _found_ops.end(); + BOOST_CHECK(is_found); + if (is_found) { + BOOST_CHECK_EQUAL(itr->second, co.second); + if (itr->second == co.second) { + ++_checked_ops_count; + } + } else { + BOOST_TEST_MESSAGE("Operation \"" + co.second + "\" by \"" + co.first + "\" is not found"); + } + } + BOOST_CHECK_EQUAL(_checked_ops_count, _added_ops.size()); +} + +BOOST_AUTO_TEST_CASE(short_operation_history_blocks) { + BOOST_TEST_MESSAGE("Testing: short_operation_history_blocks"); + initialize({{"history-whitelist-ops", "account_create_operation,delete_comment,comment"}}); + + auto _added_ops = add_operations(); + auto _found_ops = check_operations(); + + size_t _checked_ops_count = 0; + for (auto it = _found_ops.begin(); it != _found_ops.end(); ++it) { + auto itr = _added_ops.find(it->first); + bool is_found = (itr != _added_ops.end()); + if (is_found) { + BOOST_TEST_MESSAGE("Found operation \"" + it->second + "\" by \"" + it->first + "\""); + BOOST_CHECK_EQUAL(itr->second, it->second); + if (itr->second == it->second) { + ++_checked_ops_count; + } + } + } + BOOST_CHECK_EQUAL(_checked_ops_count, 3); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/plugin_tests/plugin_ops.cpp b/tests/plugin_tests/plugin_ops.cpp index d691de0f3c..80e3dbb17f 100644 --- a/tests/plugin_tests/plugin_ops.cpp +++ b/tests/plugin_tests/plugin_ops.cpp @@ -110,4 +110,4 @@ BOOST_AUTO_TEST_CASE( custom_binary ) } */ BOOST_AUTO_TEST_SUITE_END() -#endif \ No newline at end of file +#endif diff --git a/tests/plugin_tests/private_message.cpp b/tests/plugin_tests/private_message.cpp new file mode 100644 index 0000000000..2565746e8c --- /dev/null +++ b/tests/plugin_tests/private_message.cpp @@ -0,0 +1,1096 @@ +#include + +#include "database_fixture.hpp" +#include "helpers.hpp" + +#include + +#include + +#include +#include +#include + +using golos::plugins::json_rpc::msg_pack; +using golos::logic_exception; +using golos::missing_object; +using golos::object_already_exist; +using namespace golos::protocol; +using namespace golos::plugins::private_message; + +struct private_message_fixture : public golos::chain::database_fixture { + private_message_fixture() : golos::chain::database_fixture() { + initialize(); + pm_plugin = appbase::app().find_plugin(); + open_database(); + startup(); + } + + private_message_plugin* pm_plugin = nullptr; +}; + +fc::variant_object make_private_message_id(const std::string& from, const std::string& to, const uint64_t nonce) { + auto res = fc::mutable_variant_object()("from",from)("to",to)("nonce",nonce); + return fc::variant_object(res); +} + +#define _CHECK_EQUAL_MESSAGE_API_OBJECT(L, R) \ + BOOST_CHECK_EQUAL((L).from, (R).from); \ + BOOST_CHECK_EQUAL((L).to, (R).to); \ + BOOST_CHECK_EQUAL((L).nonce, (R).nonce); \ + BOOST_CHECK_EQUAL((L).from_memo_key, (R).from_memo_key); \ + BOOST_CHECK_EQUAL((L).to_memo_key, (R).to_memo_key); \ + BOOST_CHECK_EQUAL((L).checksum, (R).checksum); \ + BOOST_CHECK_EQUAL((L).encrypted_message.size(), (R).encrypted_message.size()); \ + BOOST_CHECK_EQUAL(std::equal( \ + (L).encrypted_message.begin(), (L).encrypted_message.end(), \ + (R).encrypted_message.begin(), (R).encrypted_message.end()), true); \ + BOOST_CHECK_EQUAL((L).create_date, (R).create_date); \ + BOOST_CHECK_EQUAL((L).receive_date, (R).receive_date); \ + BOOST_CHECK_EQUAL((L).read_date, (R).read_date); + + +BOOST_FIXTURE_TEST_SUITE(private_message_plugin, private_message_fixture) + + BOOST_AUTO_TEST_CASE(private_outbox_message) { + BOOST_TEST_MESSAGE("Testing: private_message_operation"); + + ACTORS((alice)(bob)); + + BOOST_TEST_MESSAGE("--- Send message"); + + auto alice_bob_nonce = fc::time_point::now().time_since_epoch().count(); + auto alice_bob_secret = alice_private_key.get_shared_secret(bob_private_key.get_public_key()); + std::string alice_bob_msg = "Hello, Bob! My name is Alice."; + std::vector alice_bob_msg_data(alice_bob_msg.begin(), alice_bob_msg.end()); + + auto bob_alice_nonce = alice_bob_nonce + 10; + auto bob_alice_secret = bob_private_key.get_shared_secret(alice_private_key.get_public_key()); + std::string bob_alice_msg = "Hello, Alice!"; + std::vector bob_alice_msg_data(bob_alice_msg.begin(), bob_alice_msg.end()); + + BOOST_CHECK_EQUAL(alice_bob_secret, bob_alice_secret); + + fc::sha512::encoder enc; + fc::raw::pack(enc, alice_bob_nonce); + fc::raw::pack(enc, alice_bob_secret); + auto alice_bob_encrypt_key = enc.result(); + uint32_t alice_bob_checksum = fc::sha256::hash(alice_bob_encrypt_key)._hash[0]; + + private_message_operation mop; + + mop.from = "alice"; + mop.from_memo_key = alice_private_key.get_public_key(); + mop.to = "bob"; + mop.to_memo_key = bob_private_key.get_public_key(); + mop.nonce = alice_bob_nonce; + mop.encrypted_message = fc::aes_encrypt(alice_bob_encrypt_key, alice_bob_msg_data); + mop.checksum = alice_bob_checksum; + + private_message_plugin_operation pop = mop; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + signed_transaction trx; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + enc.reset(); + fc::raw::pack(enc, bob_alice_nonce); + fc::raw::pack(enc, bob_alice_secret); + auto bob_alice_encrypt_key = enc.result(); + uint32_t bob_alice_checksum = fc::sha256::hash(bob_alice_encrypt_key)._hash[0]; + + mop.from = "bob"; + mop.from_memo_key = bob_private_key.get_public_key(); + mop.to = "alice"; + mop.to_memo_key = alice_private_key.get_public_key(); + mop.nonce = bob_alice_nonce; + mop.encrypted_message = fc::aes_encrypt(bob_alice_encrypt_key, bob_alice_msg_data); + mop.checksum = bob_alice_checksum; + + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"bob"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, bob_private_key, jop)); + + BOOST_TEST_MESSAGE("--- Get message "); + + msg_pack mp; + mp.args = std::vector({fc::variant("bob"), fc::variant(message_box_query())}); + auto bob_inbox = pm_plugin->get_inbox(mp); + + mp.args = std::vector({fc::variant("bob"), fc::variant(message_box_query())}); + auto bob_outbox = pm_plugin->get_outbox(mp); + + mp.args = std::vector({fc::variant("alice"), fc::variant(message_box_query())}); + auto alice_inbox = pm_plugin->get_inbox(mp); + + mp.args = std::vector({fc::variant("alice"), fc::variant(message_box_query())}); + auto alice_outbox = pm_plugin->get_outbox(mp); + + mp.args = std::vector( + {fc::variant("alice"), fc::variant("bob"), fc::variant(message_thread_query())}); + auto alice_bob_thread = pm_plugin->get_thread(mp); + + mp.args = std::vector( + {fc::variant("bob"), fc::variant("alice"), fc::variant(message_thread_query())}); + auto bob_alice_thread = pm_plugin->get_thread(mp); + + BOOST_CHECK_EQUAL(bob_outbox.size(), 1); + BOOST_CHECK_EQUAL(bob_inbox.size(), 1); + BOOST_CHECK_EQUAL(alice_inbox.size(), 1); + BOOST_CHECK_EQUAL(alice_outbox.size(), 1); + BOOST_CHECK_EQUAL(alice_bob_thread.size(), 2); + BOOST_CHECK_EQUAL(bob_alice_thread.size(), 2); + + _CHECK_EQUAL_MESSAGE_API_OBJECT(bob_outbox[0], alice_inbox[0]); + _CHECK_EQUAL_MESSAGE_API_OBJECT(bob_outbox[0], alice_bob_thread[0]); + _CHECK_EQUAL_MESSAGE_API_OBJECT(bob_outbox[0], bob_alice_thread[0]); + BOOST_CHECK_EQUAL(bob_outbox[0].nonce, bob_alice_nonce); + BOOST_CHECK_EQUAL(bob_outbox[0].checksum, bob_alice_checksum); + + _CHECK_EQUAL_MESSAGE_API_OBJECT(alice_outbox[0], bob_inbox[0]); + _CHECK_EQUAL_MESSAGE_API_OBJECT(alice_outbox[0], alice_bob_thread[1]); + _CHECK_EQUAL_MESSAGE_API_OBJECT(alice_outbox[0], bob_alice_thread[1]); + BOOST_CHECK_EQUAL(alice_outbox[0].nonce, alice_bob_nonce); + BOOST_CHECK_EQUAL(alice_outbox[0].checksum, alice_bob_checksum); + + BOOST_TEST_MESSAGE("--- Decrypt message"); + + enc.reset(); + fc::raw::pack(enc, alice_inbox[0].nonce); + fc::raw::pack(enc, alice_private_key.get_shared_secret(alice_inbox[0].from_memo_key)); + auto alice_bob_decrypt_key = enc.result(); + uint32_t alice_bob_decrypt_checksum = fc::sha256::hash(alice_bob_decrypt_key)._hash[0]; + + BOOST_CHECK_EQUAL(alice_bob_decrypt_checksum, alice_inbox[0].checksum); + + auto alice_bob_decrypt_msg_data = fc::aes_decrypt(alice_bob_decrypt_key, alice_inbox[0].encrypted_message); + + BOOST_CHECK_EQUAL(alice_bob_decrypt_msg_data.size(), bob_alice_msg_data.size()); + BOOST_CHECK_EQUAL(std::equal( + alice_bob_decrypt_msg_data.begin(), alice_bob_decrypt_msg_data.end(), + bob_alice_msg_data.begin(), bob_alice_msg_data.end()), true); + + enc.reset(); + fc::raw::pack(enc, bob_inbox[0].nonce); + fc::raw::pack(enc, bob_private_key.get_shared_secret(bob_inbox[0].from_memo_key)); + auto bob_alice_decrypt_key = enc.result(); + uint32_t bob_alice_decrypt_checksum = fc::sha256::hash(bob_alice_decrypt_key)._hash[0]; + + BOOST_CHECK_EQUAL(bob_alice_decrypt_checksum, bob_inbox[0].checksum); + + auto bob_alice_decrypt_msg_data = fc::aes_decrypt(bob_alice_decrypt_key, bob_inbox[0].encrypted_message); + + BOOST_CHECK_EQUAL(bob_alice_decrypt_msg_data.size(), alice_bob_msg_data.size()); + BOOST_CHECK_EQUAL(std::equal( + bob_alice_decrypt_msg_data.begin(), bob_alice_decrypt_msg_data.end(), + alice_bob_msg_data.begin(), alice_bob_msg_data.end()), true); + + BOOST_TEST_MESSAGE("--- Edit message"); + + std::string alice_bob_edited_msg = "Edited: Hello, Bob! My name is Alice."; + std::vector alice_bob_edited_msg_data(alice_bob_edited_msg.begin(), alice_bob_edited_msg.end()); + + mop.from = "alice"; + mop.from_memo_key = alice_private_key.get_public_key(); + mop.to = "bob"; + mop.to_memo_key = bob_private_key.get_public_key(); + mop.nonce = alice_bob_nonce + 1000; + mop.update = true; + mop.encrypted_message = fc::aes_encrypt(alice_bob_encrypt_key, alice_bob_edited_msg_data); + mop.checksum = alice_bob_checksum; + + pop = mop; + + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(trx, alice_private_key, jop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "private_message", + make_private_message_id("alice", "bob", mop.nonce)))); + + mop.nonce = alice_bob_nonce; + mop.update = false; + pop = mop; + jop.json = fc::json::to_string(pop); + + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(trx, alice_private_key, jop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(object_already_exist, "private_message", + make_private_message_id("alice", "bob", mop.nonce)))); + + mop.update = true; + pop = mop; + jop.json = fc::json::to_string(pop); + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mp.args = std::vector({fc::variant("bob"), fc::variant(message_box_query())}); + bob_inbox = pm_plugin->get_inbox(mp); + + mp.args = std::vector({fc::variant("alice"), fc::variant(message_box_query())}); + alice_outbox = pm_plugin->get_outbox(mp); + + BOOST_CHECK_EQUAL(bob_inbox.size(), 1); + BOOST_CHECK_EQUAL(alice_outbox.size(), 1); + _CHECK_EQUAL_MESSAGE_API_OBJECT(bob_inbox[0], alice_outbox[0]); + + auto bob_alice_edited_decrypt_msg_data = fc::aes_decrypt(bob_alice_decrypt_key, bob_inbox[0].encrypted_message); + + BOOST_CHECK_EQUAL(bob_alice_edited_decrypt_msg_data.size(), alice_bob_edited_msg_data.size()); + BOOST_CHECK_EQUAL(std::equal( + bob_alice_edited_decrypt_msg_data.begin(), bob_alice_edited_decrypt_msg_data.end(), + alice_bob_edited_msg_data.begin(), alice_bob_edited_msg_data.end()), true); + } + + BOOST_AUTO_TEST_CASE(private_contact) { + BOOST_TEST_MESSAGE("Testing: private_contact_operation"); + + ACTORS((alice)(bob)(sam)(dave)); + + BOOST_TEST_MESSAGE("--- unknown contact"); + + private_contact_operation cop; + + cop.owner = "alice"; + cop.contact = "bob"; + cop.type = unknown; + + private_message_plugin_operation pop = cop; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + signed_transaction trx; + + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(trx, alice_private_key, jop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_errors::add_unknown_contact))); + + BOOST_TEST_MESSAGE("--- Ignored contact"); + + cop.type = ignored; + cop.json_metadata = "{}"; + pop = cop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + msg_pack mp; + mp.args = std::vector( + {fc::variant("alice"), fc::variant(ignored), fc::variant(100), fc::variant(0)}); + auto alice_contacts = pm_plugin->get_contacts(mp); + + BOOST_CHECK_EQUAL(alice_contacts.size(), 1); + BOOST_CHECK_EQUAL(alice_contacts[0].owner, cop.owner); + BOOST_CHECK_EQUAL(alice_contacts[0].contact, cop.contact); + BOOST_CHECK_EQUAL(alice_contacts[0].local_type, cop.type); + BOOST_CHECK_EQUAL(alice_contacts[0].remote_type, unknown); + BOOST_CHECK_EQUAL(alice_contacts[0].json_metadata, cop.json_metadata); + BOOST_CHECK_EQUAL(alice_contacts[0].size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts[0].size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts[0].size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts[0].size.unread_inbox_messages, 0); + + generate_block(); + + BOOST_TEST_MESSAGE("--- Contact hasn't changed"); + + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(trx, alice_private_key, jop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_errors::contact_has_not_changed))); + + cop.json_metadata = "{\"name\":\"Mark\"}"; + pop = cop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + BOOST_TEST_MESSAGE("--- Send message from ignored contact"); + + auto base_nonce = fc::time_point::now().time_since_epoch().count(); + + fc::sha512::encoder enc; + fc::raw::pack(enc, base_nonce); + auto encrypt_key = enc.result(); + + private_message_operation mop; + + mop.from = "alice"; + mop.from_memo_key = alice_private_key.get_public_key(); + mop.to = "bob"; + mop.to_memo_key = bob_private_key.get_public_key(); + mop.nonce = base_nonce; + mop.encrypted_message = fc::aes_encrypt(encrypt_key, {}); + mop.checksum = encrypt_key._hash[0]; + + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mop.from = "bob"; + mop.to = "alice"; + mop.nonce += 10; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"bob"}; + + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(trx, bob_private_key, jop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_errors::sender_in_ignore_list))); + + mop.from = "sam"; + mop.to = "alice"; + mop.nonce += 10; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"sam"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, sam_private_key, jop)); + + BOOST_TEST_MESSAGE("--- Receive messages only from pinned contacts"); + + mp.args = std::vector({fc::variant("alice")}); + auto alice_settings = pm_plugin->get_settings(mp); + + BOOST_CHECK_EQUAL(alice_settings.ignore_messages_from_unknown_contact, false); + + private_settings_operation sop; + sop.owner = "alice"; + sop.ignore_messages_from_unknown_contact = true; + pop = sop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + alice_settings = pm_plugin->get_settings(mp); + + BOOST_CHECK_EQUAL(alice_settings.ignore_messages_from_unknown_contact, true); + + mop.from = "sam"; + mop.to = "alice"; + mop.nonce += 10; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"sam"}; + + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(trx, sam_private_key, jop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_errors::recepient_ignores_messages_from_unknown_contact))); + + cop.owner = "alice"; + cop.contact = "dave"; + cop.type = pinned; + cop.json_metadata = "{}"; + pop = cop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mop.from = "dave"; + mop.to = "alice"; + mop.nonce += 10; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"dave"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, dave_private_key, jop)); + + mp.args = std::vector({fc::variant("alice"), fc::variant("dave")}); + auto alice_dave_contact = pm_plugin->get_contact_info(mp); + + BOOST_CHECK_EQUAL(alice_dave_contact.owner, "alice"); + BOOST_CHECK_EQUAL(alice_dave_contact.contact, "dave"); + BOOST_CHECK_EQUAL(alice_dave_contact.local_type, pinned); + BOOST_CHECK_EQUAL(alice_dave_contact.remote_type, pinned); + BOOST_CHECK_EQUAL(alice_dave_contact.json_metadata, "{}"); + BOOST_CHECK_EQUAL(alice_dave_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_dave_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_dave_contact.size.total_inbox_messages, 1); + BOOST_CHECK_EQUAL(alice_dave_contact.size.unread_inbox_messages, 1); + } + + BOOST_AUTO_TEST_CASE(private_mark) { + BOOST_TEST_MESSAGE("Testing: private_mark_message_operation"); + + ACTORS((alice)(bob)(sam)); + + BOOST_TEST_MESSAGE("--- Unread messages"); + + private_contact_operation cop; + + cop.owner = "alice"; + cop.contact = "bob"; + cop.type = pinned; + cop.json_metadata = "{}"; + + private_message_plugin_operation pop = cop; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + signed_transaction trx; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + generate_block(); + + auto base_nonce = fc::time_point::now().time_since_epoch().count(); + + fc::sha512::encoder enc; + fc::raw::pack(enc, base_nonce); + auto encrypt_key = enc.result(); + + private_message_operation mop; + + mop.from = "bob"; + mop.from_memo_key = bob_private_key.get_public_key(); + mop.to = "alice"; + mop.to_memo_key = alice_private_key.get_public_key(); + mop.nonce = base_nonce; + mop.encrypted_message = fc::aes_encrypt(encrypt_key, {}); + mop.checksum = encrypt_key._hash[0]; + + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"bob"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, bob_private_key, jop)); + generate_block(); + + mop.from = "bob"; + mop.to = "alice"; + mop.nonce = base_nonce + 10; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"bob"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, bob_private_key, jop)); + generate_block(); + + mop.from = "sam"; + mop.to = "alice"; + mop.nonce = base_nonce + 20; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"sam"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, sam_private_key, jop)); + generate_block(); + + mop.from = "sam"; + mop.to = "alice"; + mop.nonce = base_nonce + 30; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"sam"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, sam_private_key, jop)); + generate_block(); + + msg_pack mp; + mp.args = std::vector({fc::variant("alice")}); + auto alice_contacts_size = pm_plugin->get_contacts_size(mp); + + BOOST_CHECK_EQUAL(alice_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_contacts, 1); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].total_contacts, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant("bob")}); + auto alice_bob_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_inbox_messages, 2); + + mp.args = std::vector({fc::variant("bob")}); + auto bob_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(bob_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(bob_contacts_size.size[unknown].total_contacts, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[unknown].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[unknown].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[unknown].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[unknown].unread_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_outbox_messages, 2); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_outbox_messages, 2); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[ignored].total_contacts, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[ignored].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[ignored].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[ignored].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[ignored].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("bob"), fc::variant("alice")}); + auto bob_alice_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_outbox_messages, 2); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_outbox_messages, 2); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_inbox_messages, 0); + + BOOST_TEST_MESSAGE("--- Change contact type"); + + cop.owner = "alice"; + cop.contact = "sam"; + cop.type = pinned; + cop.json_metadata = "{}"; + pop = cop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mp.args = std::vector({fc::variant("alice")}); + alice_contacts_size = pm_plugin->get_contacts_size(mp); + + BOOST_CHECK_EQUAL(alice_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_contacts, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 4); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 4); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].total_contacts, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].unread_inbox_messages, 0); + + BOOST_TEST_MESSAGE("--- Mark one message"); + + private_mark_message_operation rop; + + rop.from = "bob"; + rop.to = "alice"; + rop.nonce = base_nonce; + pop = rop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mp.args = std::vector({fc::variant("alice")}); + alice_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(alice_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 4); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 3); + + mp.args = std::vector({fc::variant("alice"), fc::variant("bob")}); + alice_bob_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_inbox_messages, 1); + + mp.args = std::vector({fc::variant("bob")}); + bob_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(bob_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_outbox_messages, 2); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_outbox_messages, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("bob"), fc::variant("alice")}); + bob_alice_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_outbox_messages, 2); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_outbox_messages, 1); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_inbox_messages, 0); + + BOOST_TEST_MESSAGE("--- Mark all messages from user"); + + generate_block(); + + rop.from = "sam"; + rop.to = "alice"; + rop.nonce = 0; + rop.stop_date = db->head_block_time(); + pop = rop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mp.args = std::vector({fc::variant("alice")}); + alice_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(alice_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 4); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 1); + + mp.args = std::vector({fc::variant("alice"), fc::variant("sam")}); + auto alice_sam_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_sam_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_sam_contact.size.unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("sam")}); + auto sam_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(sam_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_outbox_messages, 2); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("sam"), fc::variant("alice")}); + auto sam_alice_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(sam_alice_contact.size.total_outbox_messages, 2); + BOOST_CHECK_EQUAL(sam_alice_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(sam_alice_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(sam_alice_contact.size.unread_inbox_messages, 0); + + BOOST_TEST_MESSAGE("--- Mark all messages to user"); + + generate_block(); + + rop.from = ""; + rop.to = "alice"; + rop.nonce = 0; + rop.stop_date = db->head_block_time(); + pop = rop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mp.args = std::vector({fc::variant("alice")}); + alice_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(alice_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 4); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant("sam")}); + alice_sam_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_sam_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_sam_contact.size.unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("sam")}); + sam_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(sam_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_outbox_messages, 2); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("sam"), fc::variant("alice")}); + sam_alice_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(sam_alice_contact.size.total_outbox_messages, 2); + BOOST_CHECK_EQUAL(sam_alice_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(sam_alice_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(sam_alice_contact.size.unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant("bob")}); + alice_bob_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("bob")}); + bob_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(bob_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_outbox_messages, 2); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("bob"), fc::variant("alice")}); + bob_alice_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_outbox_messages, 2); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_inbox_messages, 0); + } + + BOOST_AUTO_TEST_CASE(private_delete) { + BOOST_TEST_MESSAGE("Testing: private_delete_message_operation"); + + ACTORS((alice)(bob)(sam)); + + BOOST_TEST_MESSAGE("--- Delete message"); + + private_contact_operation cop; + + cop.owner = "alice"; + cop.contact = "sam"; + cop.type = pinned; + cop.json_metadata = "{}"; + + private_message_plugin_operation pop = cop; + + custom_json_operation jop; + jop.id = "private_message"; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + + signed_transaction trx; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + generate_block(); + + auto base_nonce = fc::time_point::now().time_since_epoch().count(); + + fc::sha512::encoder enc; + fc::raw::pack(enc, base_nonce); + auto encrypt_key = enc.result(); + + private_message_operation mop; + + mop.from = "bob"; + mop.from_memo_key = bob_private_key.get_public_key(); + mop.to = "alice"; + mop.to_memo_key = alice_private_key.get_public_key(); + mop.nonce = base_nonce; + mop.encrypted_message = fc::aes_encrypt(encrypt_key, {}); + mop.checksum = encrypt_key._hash[0]; + + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"bob"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, bob_private_key, jop)); + + generate_block(); + + mop.from = "bob"; + mop.to = "alice"; + mop.nonce = base_nonce + 10; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"bob"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, bob_private_key, jop)); + + generate_block(); + + mop.from = "sam"; + mop.to = "alice"; + mop.nonce = base_nonce + 20; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"sam"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, sam_private_key, jop)); + + generate_block(); + + mop.from = "sam"; + mop.to = "alice"; + mop.nonce = base_nonce + 30; + pop = mop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"sam"}; + + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, sam_private_key, jop)); + + generate_block(); + + private_delete_message_operation dop; + + dop.requester = "bob"; + dop.from = "bob"; + dop.to = "alice"; + dop.nonce = base_nonce; + pop = dop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"bob"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, bob_private_key, jop)); + + msg_pack mp; + mp.args = std::vector({fc::variant("alice")}); + auto alice_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(alice_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_contacts, 1); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 2); + + mp.args = std::vector({fc::variant("alice"), fc::variant("bob")}); + auto alice_bob_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_inbox_messages, 2); + + mp.args = std::vector({fc::variant("bob")}); + auto bob_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(bob_contacts_size.size.size(), 3); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_outbox_messages, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_outbox_messages, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("bob"), fc::variant("alice")}); + auto bob_alice_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_outbox_messages, 1); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_outbox_messages, 1); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant(message_box_query())}); + auto alice_inbox = pm_plugin->get_inbox(mp); + BOOST_CHECK_EQUAL(alice_inbox.size(), 4); + + mp.args = std::vector({fc::variant("alice"), fc::variant("bob"), fc::variant(message_thread_query())}); + auto alice_bob_thread = pm_plugin->get_thread(mp); + BOOST_CHECK_EQUAL(alice_bob_thread.size(), 2); + + mp.args = std::vector({fc::variant("bob"), fc::variant(message_box_query())}); + auto bob_outbox = pm_plugin->get_outbox(mp); + BOOST_CHECK_EQUAL(bob_outbox.size(), 1); + + mp.args = std::vector({fc::variant("bob"), fc::variant("alice"), fc::variant(message_thread_query())}); + auto bob_alice_thread = pm_plugin->get_thread(mp); + BOOST_CHECK_EQUAL(bob_alice_thread.size(), 1); + + mp.args = std::vector({fc::variant("sam"), fc::variant(message_box_query())}); + auto sam_outbox = pm_plugin->get_outbox(mp); + BOOST_CHECK_EQUAL(sam_outbox.size(), 2); + + BOOST_TEST_MESSAGE("--- Delete all messages in outbox from unknown contact"); + + dop.requester = "bob"; + dop.from = "bob"; + dop.to = "alice"; + dop.nonce = 0; + dop.stop_date = db->head_block_time(); + pop = dop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"bob"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, bob_private_key, jop)); + + mp.args = std::vector({fc::variant("alice")}); + alice_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_contacts, 1); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 2); + + mp.args = std::vector({fc::variant("alice"), fc::variant("bob")}); + alice_bob_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_inbox_messages, 2); + + mp.args = std::vector({fc::variant("bob")}); + bob_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("bob"), fc::variant("alice")}); + bob_alice_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(bob_alice_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(bob_alice_contact.size.unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant(message_box_query())}); + alice_inbox = pm_plugin->get_inbox(mp); + BOOST_CHECK_EQUAL(alice_inbox.size(), 4); + + mp.args = std::vector({fc::variant("bob"), fc::variant(message_box_query())}); + bob_outbox = pm_plugin->get_outbox(mp); + BOOST_CHECK_EQUAL(bob_outbox.size(), 0); + + mp.args = std::vector({fc::variant("sam"), fc::variant(message_box_query())}); + sam_outbox = pm_plugin->get_outbox(mp); + BOOST_CHECK_EQUAL(sam_outbox.size(), 2); + + BOOST_TEST_MESSAGE("--- Delete all messages in outbox from pinned contact"); + + dop.requester = "sam"; + dop.from = "sam"; + dop.to = "alice"; + dop.nonce = 0; + dop.stop_date = db->head_block_time(); + pop = dop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"sam"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, sam_private_key, jop)); + + mp.args = std::vector({fc::variant("alice")}); + alice_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 2); + + mp.args = std::vector({fc::variant("alice"), fc::variant("sam")}); + auto alice_sam_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_sam_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.total_inbox_messages, 2); + BOOST_CHECK_EQUAL(alice_sam_contact.size.unread_inbox_messages, 2); + + mp.args = std::vector({fc::variant("sam")}); + auto sam_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("sam"), fc::variant("alice")}); + auto sam_alice_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(sam_alice_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(sam_alice_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(sam_alice_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(sam_alice_contact.size.unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant(message_box_query())}); + alice_inbox = pm_plugin->get_inbox(mp); + BOOST_CHECK_EQUAL(alice_inbox.size(), 4); + + mp.args = std::vector({fc::variant("bob"), fc::variant(message_box_query())}); + bob_outbox = pm_plugin->get_outbox(mp); + BOOST_CHECK_EQUAL(bob_outbox.size(), 0); + + mp.args = std::vector({fc::variant("sam"), fc::variant(message_box_query())}); + sam_outbox = pm_plugin->get_outbox(mp); + BOOST_CHECK_EQUAL(sam_outbox.size(), 0); + + BOOST_TEST_MESSAGE("--- Delete all messages from inbox"); + + dop.requester = "alice"; + dop.from = ""; + dop.to = "alice"; + dop.nonce = 0; + dop.stop_date = db->head_block_time(); + pop = dop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mp.args = std::vector({fc::variant("alice")}); + alice_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_contacts, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].unread_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant("sam")}); + alice_sam_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_sam_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_sam_contact.size.unread_inbox_messages, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant("bob")}); + alice_bob_contact = pm_plugin->get_contact_info(mp); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_outbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.total_inbox_messages, 0); + BOOST_CHECK_EQUAL(alice_bob_contact.size.unread_inbox_messages, 0); + + BOOST_TEST_MESSAGE("--- Change contact type to unknown"); + + cop.owner = "alice"; + cop.contact = "sam"; + cop.type = unknown; + cop.json_metadata = ""; + pop = cop; + jop.json = fc::json::to_string(pop); + jop.required_posting_auths = {"alice"}; + GOLOS_CHECK_NO_THROW(push_tx_with_ops(trx, alice_private_key, jop)); + + mp.args = std::vector({fc::variant("alice")}); + alice_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(alice_contacts_size.size[unknown].total_contacts, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[pinned].total_contacts, 0); + BOOST_CHECK_EQUAL(alice_contacts_size.size[ignored].total_contacts, 0); + + mp.args = std::vector({fc::variant("sam")}); + sam_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(sam_contacts_size.size[unknown].total_contacts, 0); + BOOST_CHECK_EQUAL(sam_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(sam_contacts_size.size[ignored].total_contacts, 0); + + mp.args = std::vector({fc::variant("bob")}); + bob_contacts_size = pm_plugin->get_contacts_size(mp); + BOOST_CHECK_EQUAL(bob_contacts_size.size[unknown].total_contacts, 0); + BOOST_CHECK_EQUAL(bob_contacts_size.size[pinned].total_contacts, 1); + BOOST_CHECK_EQUAL(bob_contacts_size.size[ignored].total_contacts, 0); + + mp.args = std::vector({fc::variant("alice"), fc::variant(message_box_query())}); + alice_inbox = pm_plugin->get_inbox(mp); + BOOST_CHECK_EQUAL(alice_inbox.size(), 0); + + mp.args = std::vector({fc::variant("bob"), fc::variant(message_box_query())}); + bob_outbox = pm_plugin->get_outbox(mp); + BOOST_CHECK_EQUAL(bob_outbox.size(), 0); + + mp.args = std::vector({fc::variant("sam"), fc::variant(message_box_query())}); + sam_outbox = pm_plugin->get_outbox(mp); + BOOST_CHECK_EQUAL(sam_outbox.size(), 0); + } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 3910c51b06..7c3bb76fb1 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -46,6 +46,10 @@ using namespace golos::plugins; #define TEST_SHARED_MEM_SIZE (1024 * 1024 * 8) +#define PUSH_TX golos::chain::test::_push_transaction +#define PUSH_BLOCK golos::chain::test::_push_block + + BOOST_AUTO_TEST_SUITE(block_tests) BOOST_AUTO_TEST_CASE(generate_empty_blocks) { diff --git a/tests/tests/main.cpp b/tests/tests/main.cpp index eac82a29a8..6a931b5d96 100644 --- a/tests/tests/main.cpp +++ b/tests/tests/main.cpp @@ -32,7 +32,6 @@ #include #endif -// extern uint32_t STEEMIT_TESTING_GENESIS_TIMESTAMP; boost::unit_test::test_suite *init_unit_test_suite(int argc, char *argv[]) { fc::configure_logging(fc::logging_config::default_config(fc::log_level::error)); @@ -40,13 +39,5 @@ boost::unit_test::test_suite *init_unit_test_suite(int argc, char *argv[]) { std::srand(time(NULL)); std::cout << "Random number generator seeded to " << time(NULL) << std::endl; -/* - const char* genesis_timestamp_str = getenv("STEEMIT_TESTING_GENESIS_TIMESTAMP"); - if( genesis_timestamp_str != nullptr ) - { - STEEMIT_TESTING_GENESIS_TIMESTAMP = std::stoul( genesis_timestamp_str ); - } - std::cout << "STEEMIT_TESTING_GENESIS_TIMESTAMP is " << STEEMIT_TESTING_GENESIS_TIMESTAMP << std::endl; -*/ return nullptr; } \ No newline at end of file diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 7dfce6c315..ed96a8b676 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -9,23 +9,70 @@ #include #include +#include + #include +#include #include "database_fixture.hpp" +#include "helpers.hpp" #include #include #include +#include using namespace golos; +using namespace golos::api; using namespace golos::chain; using namespace golos::protocol; +using golos::plugins::social_network::comment_content_object; using std::string; + +#define BAD_UTF8_STRING "\xc3\x28" + + BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(account_create_validate) { try { + BOOST_TEST_MESSAGE("Testing: account_create_validate"); + account_create_operation op; + + private_key_type priv_key = generate_private_key("temp_key"); + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.fee = ASSET("10.000 GOLOS"); + op.new_account_name = "bob"; + op.creator = STEEMIT_INIT_MINER_NAME; + op.owner = authority(1, priv_key.get_public_key(), 1); + op.active = authority(2, priv_key.get_public_key(), 2); + op.memo_key = priv_key.get_public_key(); + op.json_metadata = "{\"foo\":\"bar\"}"; + BOOST_CHECK_NO_THROW(op.validate()); + + BOOST_TEST_MESSAGE("--- failed when 'new_account_name' is empty"); + op.new_account_name = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "new_account_name")); + + BOOST_TEST_MESSAGE("--- failed when 'fee' not in GOLOS"); + op.new_account_name = "bob"; + op.fee = ASSET("10.000 GBG"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "fee")); + + BOOST_TEST_MESSAGE("--- failed when 'fee' is negative"); + op.fee = ASSET("-10.000 GOLOS"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "fee")); + + BOOST_TEST_MESSAGE("--- failed when 'json_metadata' is invalid"); + op.fee = ASSET("10.000 GOLOS"); + op.json_metadata = "[}"; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "json_metadata")); } FC_LOG_AND_RETHROW() @@ -53,11 +100,12 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test success with witness signature"); tx.sign(init_account_priv_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); tx.operations.clear(); @@ -66,18 +114,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(init_account_priv_key, db->get_chain_id()); tx.sign(init_account_priv_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); tx.signatures.clear(); tx.sign(init_account_priv_key, db->get_chain_id()); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the creator's authority"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); validate_database(); } FC_LOG_AND_RETHROW() @@ -110,7 +161,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(init_account_priv_key, db->get_chain_id()); tx.validate(); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); const account_object &acct = db->get_account("alice"); const account_authority_object &acct_auth = db->get("alice"); @@ -118,46 +169,46 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) auto vest_shares = gpo.total_vesting_shares; auto vests = gpo.total_vesting_fund_steem; - BOOST_REQUIRE(acct.name == "alice"); - BOOST_REQUIRE(acct_auth.owner == authority(1, priv_key.get_public_key(), 1)); - BOOST_REQUIRE(acct_auth.active == authority(2, priv_key.get_public_key(), 2)); - BOOST_REQUIRE(acct.memo_key == priv_key.get_public_key()); - BOOST_REQUIRE(acct.proxy == ""); - BOOST_REQUIRE(acct.created == db->head_block_time()); - BOOST_REQUIRE(acct.balance.amount.value == ASSET("0.000 GOLOS").amount.value); - BOOST_REQUIRE(acct.sbd_balance.amount.value == ASSET("0.000 GBG").amount.value); - BOOST_REQUIRE(acct.id._id == acct_auth.id._id); - - /* This is being moved out of consensus... - #ifndef IS_LOW_MEM - BOOST_REQUIRE( acct.json_metadata == op.json_metadata ); - #else - BOOST_REQUIRE( acct.json_metadata == "" ); - #endif - */ + BOOST_CHECK_EQUAL(acct.name, "alice"); + BOOST_CHECK_EQUAL(acct_auth.owner, authority(1, priv_key.get_public_key(), 1)); + BOOST_CHECK_EQUAL(acct_auth.active, authority(2, priv_key.get_public_key(), 2)); + BOOST_CHECK_EQUAL(acct.memo_key, priv_key.get_public_key()); + BOOST_CHECK_EQUAL(acct.proxy, ""); + BOOST_CHECK_EQUAL(acct.created, db->head_block_time()); + BOOST_CHECK_EQUAL(acct.balance.amount.value, ASSET("0.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(acct.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); + BOOST_CHECK_EQUAL(acct.id._id, acct_auth.id._id); + + auto meta = db->find(acct.name); + BOOST_CHECK(nullptr != meta); + if (nullptr != meta) { + BOOST_CHECK_EQUAL(to_string(meta->json_metadata), op.json_metadata); + } /// because init_witness has created vesting shares and blocks have been produced, 100 STEEM is worth less than 100 vesting shares due to rounding - BOOST_REQUIRE(acct.vesting_shares.amount.value == (op.fee * (vest_shares / vests)).amount.value); - BOOST_REQUIRE(acct.vesting_withdraw_rate.amount.value == ASSET("0.000000 GOLOS").amount.value); - BOOST_REQUIRE(acct.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE((init_starting_balance - ASSET("0.100 GOLOS")).amount.value == init.balance.amount.value); + BOOST_CHECK_EQUAL(acct.vesting_shares.amount.value, (op.fee * (vest_shares / vests)).amount.value); + BOOST_CHECK_EQUAL(acct.vesting_withdraw_rate.amount.value, ASSET("0.000000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(acct.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL((init_starting_balance - ASSET("0.100 GOLOS")).amount.value, init.balance.amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test failure of duplicate account creation"); - BOOST_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), fc::exception); - - BOOST_REQUIRE(acct.name == "alice"); - BOOST_REQUIRE(acct_auth.owner == authority(1, priv_key.get_public_key(), 1)); - BOOST_REQUIRE(acct_auth.active == authority(2, priv_key.get_public_key(), 2)); - BOOST_REQUIRE(acct.memo_key == priv_key.get_public_key()); - BOOST_REQUIRE(acct.proxy == ""); - BOOST_REQUIRE(acct.created == db->head_block_time()); - BOOST_REQUIRE(acct.balance.amount.value == ASSET("0.000 GOLOS ").amount.value); - BOOST_REQUIRE(acct.sbd_balance.amount.value == ASSET("0.000 GBG").amount.value); - BOOST_REQUIRE(acct.vesting_shares.amount.value == (op.fee * (vest_shares / vests)).amount.value); - BOOST_REQUIRE(acct.vesting_withdraw_rate.amount.value == ASSET("0.000000 GOLOS").amount.value); - BOOST_REQUIRE(acct.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE((init_starting_balance - ASSET("0.100 GOLOS")).amount.value == init.balance.amount.value); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(object_already_exist, "account", "alice"))); + + BOOST_CHECK_EQUAL(acct.name, "alice"); + BOOST_CHECK_EQUAL(acct_auth.owner, authority(1, priv_key.get_public_key(), 1)); + BOOST_CHECK_EQUAL(acct_auth.active, authority(2, priv_key.get_public_key(), 2)); + BOOST_CHECK_EQUAL(acct.memo_key, priv_key.get_public_key()); + BOOST_CHECK_EQUAL(acct.proxy, ""); + BOOST_CHECK_EQUAL(acct.created, db->head_block_time()); + BOOST_CHECK_EQUAL(acct.balance.amount.value, ASSET("0.000 GOLOS ").amount.value); + BOOST_CHECK_EQUAL(acct.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); + BOOST_CHECK_EQUAL(acct.vesting_shares.amount.value, (op.fee * (vest_shares / vests)).amount.value); + BOOST_CHECK_EQUAL(acct.vesting_withdraw_rate.amount.value, ASSET("0.000000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(acct.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL((init_starting_balance - ASSET("0.100 GOLOS")).amount.value, init.balance.amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test failure when creator cannot cover fee"); @@ -168,7 +219,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(init_account_priv_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, STEEMIT_INIT_MINER_NAME, "fund", op.fee.to_string()))); validate_database(); } FC_LOG_AND_RETHROW() @@ -185,20 +238,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.posting = authority(); op.posting->weight_threshold = 1; op.posting->add_authorities("abcdefghijklmnopq", 1); + BOOST_CHECK_NO_THROW(op.validate()); - try { - op.validate(); - - signed_transaction tx; - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_FAIL("An exception was not thrown for an invalid account name"); - } - catch (fc::exception &) { - } + signed_transaction tx; + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.operations.push_back(op); + tx.sign(alice_private_key, db->get_chain_id()); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "account", "abcdefghijklmnop"))); // droped 17-th symbol 'q' validate_database(); } @@ -226,31 +274,35 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_TEST_MESSAGE(" GOLOS when owner authority is not updated ---"); BOOST_TEST_MESSAGE("--- Test failure when no signature"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when wrong signature"); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when containing additional incorrect signature"); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when containing duplicate signatures"); tx.signatures.clear(); tx.sign(active_key, db->get_chain_id()); tx.sign(active_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test success on active key"); tx.signatures.clear(); tx.sign(active_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- Test success on owner key alone"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, database::skip_transaction_dupe_check); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check)); BOOST_TEST_MESSAGE(" GOLOS when owner authority is updated ---"); BOOST_TEST_MESSAGE("--- Test failure when updating the owner authority with an active key"); @@ -259,27 +311,31 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.owner = authority(1, active_key.get_public_key(), 1); tx.operations.push_back(op); tx.sign(active_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_owner_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_owner_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when owner key and active key are present"); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when incorrect signature"); tx.signatures.clear(); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_owner_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_owner_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when duplicate owner keys are present"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test success when updating the owner authority with an owner key"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); validate_database(); } @@ -306,23 +362,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); const account_object &acct = db->get_account("alice"); const account_authority_object &acct_auth = db->get("alice"); - BOOST_REQUIRE(acct.name == "alice"); - BOOST_REQUIRE(acct_auth.owner == authority(1, new_private_key.get_public_key(), 1)); - BOOST_REQUIRE(acct_auth.active == authority(2, new_private_key.get_public_key(), 2)); - BOOST_REQUIRE(acct.memo_key == new_private_key.get_public_key()); + BOOST_CHECK_EQUAL(acct.name, "alice"); + BOOST_CHECK_EQUAL(acct_auth.owner, authority(1, new_private_key.get_public_key(), 1)); + BOOST_CHECK_EQUAL(acct_auth.active, authority(2, new_private_key.get_public_key(), 2)); + BOOST_CHECK_EQUAL(acct.memo_key, new_private_key.get_public_key()); - /* This is being moved out of consensus - #ifndef IS_LOW_MEM - BOOST_REQUIRE( acct.json_metadata == "{\"bar\":\"foo\"}" ); - #else - BOOST_REQUIRE( acct.json_metadata == "" ); - #endif - */ + auto meta = db->find(acct.name); + BOOST_CHECK(nullptr != meta); + if (nullptr != meta) { + BOOST_CHECK_EQUAL(to_string(meta->json_metadata), op.json_metadata); + } validate_database(); @@ -332,7 +386,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.account = "bob"; tx.operations.push_back(op); tx.sign(new_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception) + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(missing_object, "authority", "bob")); validate_database(); @@ -345,7 +400,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.posting->add_authorities("dave", 1); tx.operations.push_back(op); tx.sign(new_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "account", "dave"))); validate_database(); } FC_LOG_AND_RETHROW() @@ -355,6 +412,52 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) try { BOOST_TEST_MESSAGE("Testing: comment_validate"); + comment_operation op; + op.author = "alice"; + op.permlink = "lorem"; + op.parent_author = ""; + op.parent_permlink = "ipsum"; + op.title = "Lorem Ipsum"; + op.body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + op.json_metadata = "{\"foo\":\"bar\"}"; + + BOOST_TEST_MESSAGE("--- success on valid operation"); + BOOST_CHECK_NO_THROW(op.validate()); + + BOOST_TEST_MESSAGE("--- failed when 'title' too large"); + op.title = std::string(256, ' '); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "title")); + + BOOST_TEST_MESSAGE("--- failed when 'body' is empty"); + op.title = "Lorem Ipsum"; + op.body = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "body")); + + BOOST_TEST_MESSAGE("--- failed when 'author' is empty"); + op.body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + op.author = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "author")); + + BOOST_TEST_MESSAGE("--- failed when 'permlink' too large"); + op.author = "alice"; + op.permlink = std::string(STEEMIT_MAX_PERMLINK_LENGTH, ' '); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "permlink")); + + BOOST_TEST_MESSAGE("--- failed when 'parent_permlink' too large"); + op.permlink = "lorem"; + op.parent_permlink = std::string(STEEMIT_MAX_PERMLINK_LENGTH, ' '); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "parent_permlink")); + + BOOST_TEST_MESSAGE("--- failed when 'json_metadata' is not valid json"); + op.parent_permlink = "ipsum"; + op.json_metadata = "{1:\"string\"}"; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "json_metadata")); validate_database(); } @@ -382,26 +485,30 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_posting_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_posting_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); tx.sign(alice_post_key, db->get_chain_id()); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test success with post signature"); tx.signatures.clear(); tx.sign(alice_post_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the creator's authority"); tx.signatures.clear(); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_posting_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_posting_auth, 0)); validate_database(); } @@ -415,6 +522,10 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) ACTORS((alice)(bob)(sam)) generate_blocks(60 / STEEMIT_BLOCK_INTERVAL); + BOOST_CHECK(db->has_index()); + + const auto& clu_idx = db->get_index().indices().get(); + comment_operation op; op.author = "alice"; op.permlink = "lorem"; @@ -430,30 +541,28 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_TEST_MESSAGE("--- Test Alice posting a root comment"); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); const comment_object &alice_comment = db->get_comment("alice", string("lorem")); - const comment_content_object& alice_content = db->get_comment_content(alice_comment.id); - - BOOST_REQUIRE(alice_comment.author == op.author); - BOOST_REQUIRE(to_string(alice_comment.permlink) == op.permlink); - BOOST_REQUIRE(to_string(alice_comment.parent_permlink) == op.parent_permlink); - BOOST_REQUIRE(alice_comment.last_update == db->head_block_time()); - BOOST_REQUIRE(alice_comment.created == db->head_block_time()); - BOOST_REQUIRE(alice_comment.net_rshares.value == 0); - BOOST_REQUIRE(alice_comment.abs_rshares.value == 0); - BOOST_REQUIRE(alice_comment.cashout_time == + const comment_content_object& alice_content = sn_plugin->get_comment_content(alice_comment.id); + + BOOST_CHECK_EQUAL(alice_comment.author, op.author); + BOOST_CHECK_EQUAL(to_string(alice_comment.permlink), op.permlink); + BOOST_CHECK_EQUAL(to_string(alice_comment.parent_permlink), op.parent_permlink); + + auto alice_clu_itr = clu_idx.find(alice_comment.id); + BOOST_CHECK(alice_clu_itr != clu_idx.end()); + BOOST_CHECK_EQUAL(alice_clu_itr->last_update, db->head_block_time()); + + BOOST_CHECK_EQUAL(alice_comment.created, db->head_block_time()); + BOOST_CHECK_EQUAL(alice_comment.net_rshares.value, 0); + BOOST_CHECK_EQUAL(alice_comment.abs_rshares.value, 0); + BOOST_CHECK_EQUAL(alice_comment.cashout_time, fc::time_point_sec(db->head_block_time() + fc::seconds(STEEMIT_CASHOUT_WINDOW_SECONDS))); -#ifndef IS_LOW_MEM - BOOST_REQUIRE( to_string( alice_content.title ) == op.title ); - BOOST_REQUIRE( to_string( alice_content.body ) == op.body ); - //BOOST_REQUIRE( alice_content.json_metadata == op.json_metadata ); -#else - BOOST_REQUIRE(to_string(alice_content.title) == ""); - BOOST_REQUIRE(to_string(alice_content.body) == ""); - //BOOST_REQUIRE( alice_content.json_metadata == "" ); -#endif + BOOST_CHECK_EQUAL( to_string( alice_content.title ), op.title ); + BOOST_CHECK_EQUAL( to_string( alice_content.body ), op.body ); + //BOOST_CHECK_EQUAL( alice_content.json_metadata, op.json_metadata ); validate_database(); @@ -467,7 +576,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "comment", make_comment_id("alice", "foobar")))); BOOST_TEST_MESSAGE("--- Test Bob posting a comment on Alice's comment"); op.parent_permlink = "lorem"; @@ -476,20 +587,24 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); const comment_object &bob_comment = db->get_comment("bob", string("ipsum")); - BOOST_REQUIRE(bob_comment.author == op.author); - BOOST_REQUIRE(to_string(bob_comment.permlink) == op.permlink); - BOOST_REQUIRE(bob_comment.parent_author == op.parent_author); - BOOST_REQUIRE(to_string(bob_comment.parent_permlink) == op.parent_permlink); - BOOST_REQUIRE(bob_comment.last_update == db->head_block_time()); - BOOST_REQUIRE(bob_comment.created == db->head_block_time()); - BOOST_REQUIRE(bob_comment.net_rshares.value == 0); - BOOST_REQUIRE(bob_comment.abs_rshares.value == 0); - BOOST_REQUIRE(bob_comment.cashout_time == bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(bob_comment.root_comment == alice_comment.id); + BOOST_CHECK_EQUAL(bob_comment.author, op.author); + BOOST_CHECK_EQUAL(to_string(bob_comment.permlink), op.permlink); + BOOST_CHECK_EQUAL(bob_comment.parent_author, op.parent_author); + BOOST_CHECK_EQUAL(to_string(bob_comment.parent_permlink), op.parent_permlink); + + auto bob_clu_itr = clu_idx.find(bob_comment.id); + BOOST_CHECK(bob_clu_itr != clu_idx.end()); + BOOST_CHECK_EQUAL(bob_clu_itr->last_update, db->head_block_time()); + + BOOST_CHECK_EQUAL(bob_comment.created, db->head_block_time()); + BOOST_CHECK_EQUAL(bob_comment.net_rshares.value, 0); + BOOST_CHECK_EQUAL(bob_comment.abs_rshares.value, 0); + BOOST_CHECK_EQUAL(bob_comment.cashout_time, bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK_EQUAL(bob_comment.root_comment, alice_comment.id); validate_database(); BOOST_TEST_MESSAGE("--- Test Sam posting a comment on Bob's comment"); @@ -503,20 +618,24 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.clear(); tx.operations.push_back(op); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); const comment_object &sam_comment = db->get_comment("sam", string("dolor")); - BOOST_REQUIRE(sam_comment.author == op.author); - BOOST_REQUIRE(to_string(sam_comment.permlink) == op.permlink); - BOOST_REQUIRE(sam_comment.parent_author == op.parent_author); - BOOST_REQUIRE(to_string(sam_comment.parent_permlink) == op.parent_permlink); - BOOST_REQUIRE(sam_comment.last_update == db->head_block_time()); - BOOST_REQUIRE(sam_comment.created == db->head_block_time()); - BOOST_REQUIRE(sam_comment.net_rshares.value == 0); - BOOST_REQUIRE(sam_comment.abs_rshares.value == 0); - BOOST_REQUIRE(sam_comment.cashout_time == sam_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(sam_comment.root_comment == alice_comment.id); + BOOST_CHECK_EQUAL(sam_comment.author, op.author); + BOOST_CHECK_EQUAL(to_string(sam_comment.permlink), op.permlink); + BOOST_CHECK_EQUAL(sam_comment.parent_author, op.parent_author); + BOOST_CHECK_EQUAL(to_string(sam_comment.parent_permlink), op.parent_permlink); + + auto sam_clu_itr = clu_idx.find(sam_comment.id); + BOOST_CHECK(sam_clu_itr != clu_idx.end()); + BOOST_CHECK_EQUAL(sam_clu_itr->last_update, db->head_block_time()); + + BOOST_CHECK_EQUAL(sam_comment.created, db->head_block_time()); + BOOST_CHECK_EQUAL(sam_comment.net_rshares.value, 0); + BOOST_CHECK_EQUAL(sam_comment.abs_rshares.value, 0); + BOOST_CHECK_EQUAL(sam_comment.cashout_time, sam_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK_EQUAL(sam_comment.root_comment, alice_comment.id); validate_database(); generate_blocks(60 * 5 / STEEMIT_BLOCK_INTERVAL + 1); @@ -553,15 +672,19 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); + + BOOST_CHECK_EQUAL(mod_sam_comment.author, op.author); + BOOST_CHECK_EQUAL(to_string(mod_sam_comment.permlink), op.permlink); + BOOST_CHECK_EQUAL(mod_sam_comment.parent_author, op.parent_author); + BOOST_CHECK_EQUAL(to_string(mod_sam_comment.parent_permlink), op.parent_permlink); - BOOST_REQUIRE(mod_sam_comment.author == op.author); - BOOST_REQUIRE(to_string(mod_sam_comment.permlink) == op.permlink); - BOOST_REQUIRE(mod_sam_comment.parent_author == op.parent_author); - BOOST_REQUIRE(to_string(mod_sam_comment.parent_permlink) == op.parent_permlink); - BOOST_REQUIRE(mod_sam_comment.last_update == db->head_block_time()); - BOOST_REQUIRE(mod_sam_comment.created == created); - BOOST_REQUIRE(mod_sam_comment.cashout_time == mod_sam_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + auto mod_sam_clu_itr = clu_idx.find(mod_sam_comment.id); + BOOST_CHECK(mod_sam_clu_itr != clu_idx.end()); + BOOST_CHECK_EQUAL(mod_sam_clu_itr->last_update, db->head_block_time()); + + BOOST_CHECK_EQUAL(mod_sam_comment.created, created); + BOOST_CHECK_EQUAL(mod_sam_comment.cashout_time, mod_sam_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); validate_database(); BOOST_TEST_MESSAGE("--- Test failure posting withing 1 minute"); @@ -574,7 +697,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.operations.push_back(op); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); generate_blocks(60 * 5 / STEEMIT_BLOCK_INTERVAL); @@ -584,12 +707,14 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.operations.push_back(op); tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(bandwidth_exception, golos::bandwidth_exception::post_bandwidth))); validate_database(); generate_block(); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); validate_database(); } FC_LOG_AND_RETHROW() @@ -599,6 +724,48 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) try { BOOST_TEST_MESSAGE("Testing: vote_validate"); + vote_operation op; + op.voter = "bob"; + op.author = "alice"; + op.permlink = "test"; + op.weight = 1000; + + BOOST_TEST_MESSAGE("failure when 'voter' is empty"); + op.voter = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(golos::invalid_parameter, "voter")); + + BOOST_TEST_MESSAGE("failure when 'author' is empty"); + op.voter = "bob"; + op.author = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(golos::invalid_parameter, "author")); + + BOOST_TEST_MESSAGE("failure when 'weight' is too small"); + op.author = "alice"; + op.weight = -STEEMIT_100_PERCENT-1; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(golos::invalid_parameter, "weight")); + + BOOST_TEST_MESSAGE("failure when 'weight' is too mush"); + op.weight = STEEMIT_100_PERCENT+1; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(golos::invalid_parameter, "weight")); + + BOOST_TEST_MESSAGE("success with positive 'weight'"); + op.weight = STEEMIT_100_PERCENT; + BOOST_CHECK_NO_THROW(op.validate()); + + BOOST_TEST_MESSAGE("success with negative 'weight'"); + op.weight = -STEEMIT_100_PERCENT; + BOOST_CHECK_NO_THROW(op.validate()); + + BOOST_TEST_MESSAGE("failure when 'perlink' invalid"); + op.weight = 1000; + op.permlink = std::string(STEEMIT_MAX_PERMLINK_LENGTH, ' '); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(golos::invalid_parameter, "permlink")); + validate_database(); } FC_LOG_AND_RETHROW() @@ -608,6 +775,14 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) try { BOOST_TEST_MESSAGE("Testing: vote_authorities"); + vote_operation op; + op.voter = "bob"; + op.author = "alice"; + op.permlink = "test"; + op.weight = 1000; + + CHECK_OP_AUTHS(op, account_name_set(), account_name_set(), account_name_set({"bob"})); + validate_database(); } FC_LOG_AND_RETHROW() @@ -642,7 +817,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(comment_op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- Testing voting on a non-existent comment"); @@ -657,19 +832,24 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "comment", make_comment_id("bob","foo")))); validate_database(); BOOST_TEST_MESSAGE("--- Testing voting with a weight of 0"); + op.author = "alice"; op.weight = (int16_t)0; tx.operations.clear(); tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(invalid_parameter, "weight"))); validate_database(); @@ -678,13 +858,12 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) auto old_voting_power = alice.voting_power; op.weight = STEEMIT_100_PERCENT; - op.author = "alice"; tx.operations.clear(); tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); auto &alice_comment = db->get_comment("alice", string("foo")); auto itr = vote_idx.find(std::make_tuple(alice_comment.id, alice.id)); @@ -692,18 +871,18 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) (db->get_dynamic_global_properties().vote_regeneration_per_day * STEEMIT_VOTE_REGENERATION_SECONDS) / (60 * 60 * 24); - BOOST_REQUIRE(alice.voting_power == old_voting_power - - ((old_voting_power + max_vote_denom - 1) / - max_vote_denom)); - BOOST_REQUIRE(alice.last_vote_time == db->head_block_time()); - BOOST_REQUIRE(alice_comment.net_rshares.value == alice.vesting_shares.amount.value * - (old_voting_power - alice.voting_power) / - STEEMIT_100_PERCENT); - BOOST_REQUIRE(alice_comment.cashout_time == alice_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(itr->rshares == alice.vesting_shares.amount.value * - (old_voting_power - alice.voting_power) / - STEEMIT_100_PERCENT); - BOOST_REQUIRE(itr != vote_idx.end()); + BOOST_CHECK_EQUAL(alice.voting_power, old_voting_power - + ((old_voting_power + max_vote_denom - 1) / + max_vote_denom)); + BOOST_CHECK_EQUAL(alice.last_vote_time, db->head_block_time()); + BOOST_CHECK_EQUAL(alice_comment.net_rshares.value, alice.vesting_shares.amount.value * + (old_voting_power - alice.voting_power) / + STEEMIT_100_PERCENT); + BOOST_CHECK_EQUAL(alice_comment.cashout_time, alice_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK_EQUAL(itr->rshares, alice.vesting_shares.amount.value * + (old_voting_power - alice.voting_power) / + STEEMIT_100_PERCENT); + BOOST_CHECK(itr != vote_idx.end()); validate_database(); BOOST_TEST_MESSAGE("--- Test reduced power for quick voting"); @@ -720,7 +899,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(comment_op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); op.weight = STEEMIT_100_PERCENT / 2; op.voter = "alice"; @@ -730,23 +909,23 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); const auto &bob_comment = db->get_comment("bob", string("foo")); itr = vote_idx.find(std::make_tuple(bob_comment.id, alice.id)); - BOOST_REQUIRE(db->get_account("alice").voting_power == + BOOST_CHECK_EQUAL(db->get_account("alice").voting_power, old_voting_power - ((old_voting_power + max_vote_denom - 1) * STEEMIT_100_PERCENT / (2 * max_vote_denom * STEEMIT_100_PERCENT))); - BOOST_REQUIRE(bob_comment.net_rshares.value == + BOOST_CHECK_EQUAL(bob_comment.net_rshares.value, alice.vesting_shares.amount.value * (old_voting_power - db->get_account("alice").voting_power) / STEEMIT_100_PERCENT); - BOOST_REQUIRE(bob_comment.cashout_time == bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(itr != vote_idx.end()); + BOOST_CHECK_EQUAL(bob_comment.cashout_time, bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK(itr != vote_idx.end()); validate_database(); BOOST_TEST_MESSAGE("--- Test payout time extension on vote"); @@ -768,21 +947,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); itr = vote_idx.find(std::make_tuple(new_alice_comment.id, new_bob.id)); - BOOST_REQUIRE(new_bob.voting_power == STEEMIT_100_PERCENT - + BOOST_CHECK_EQUAL(new_bob.voting_power, STEEMIT_100_PERCENT - ((STEEMIT_100_PERCENT + max_vote_denom - 1) / max_vote_denom)); - BOOST_REQUIRE(new_alice_comment.net_rshares.value == - old_abs_rshares + - new_bob.vesting_shares.amount.value * - (old_voting_power - new_bob.voting_power) / - STEEMIT_100_PERCENT); - BOOST_REQUIRE(new_alice_comment.cashout_time == new_alice_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(itr != vote_idx.end()); + BOOST_CHECK_EQUAL(new_alice_comment.net_rshares.value, + old_abs_rshares + + new_bob.vesting_shares.amount.value * + (old_voting_power - new_bob.voting_power) / + STEEMIT_100_PERCENT); + BOOST_CHECK_EQUAL(new_alice_comment.cashout_time, new_alice_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK(itr != vote_idx.end()); validate_database(); BOOST_TEST_MESSAGE("--- Test negative vote"); @@ -800,7 +979,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); itr = vote_idx.find(std::make_tuple(new_bob_comment.id, new_sam.id)); auto sam_weight /*= ( ( uint128_t( new_sam.vesting_shares.amount.value ) ) / 400 + 1 ).to_uint64();*/ @@ -809,14 +988,14 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) (2 * max_vote_denom))) / STEEMIT_100_PERCENT).to_uint64(); - BOOST_REQUIRE(new_sam.voting_power == STEEMIT_100_PERCENT - + BOOST_CHECK_EQUAL(new_sam.voting_power, STEEMIT_100_PERCENT - ((STEEMIT_100_PERCENT + max_vote_denom - 1) / (2 * max_vote_denom))); - BOOST_REQUIRE(new_bob_comment.net_rshares.value == (int64_t)(old_abs_rshares - sam_weight)); - BOOST_REQUIRE(new_bob_comment.abs_rshares.value == (int64_t)(old_abs_rshares + sam_weight)); - BOOST_REQUIRE(new_bob_comment.cashout_time == new_bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(itr != vote_idx.end()); + BOOST_CHECK_EQUAL(new_bob_comment.net_rshares.value, (int64_t)(old_abs_rshares - sam_weight)); + BOOST_CHECK_EQUAL(new_bob_comment.abs_rshares.value, (int64_t)(old_abs_rshares + sam_weight)); + BOOST_CHECK_EQUAL(new_bob_comment.cashout_time, new_bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK(itr != vote_idx.end()); validate_database(); BOOST_TEST_MESSAGE("--- Test nested voting on nested comments"); @@ -840,7 +1019,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(comment_op); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); auto old_rshares2 = db->get_comment("alice", string("foo")).children_rshares2; @@ -858,11 +1037,11 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) (fc::uint128_t(db->get_account("alice").vesting_shares.amount.value) * used_power) / STEEMIT_100_PERCENT).to_uint64(); - BOOST_REQUIRE( - db->get_comment("alice", string("foo")).children_rshares2 == + BOOST_CHECK_EQUAL( + db->get_comment("alice", string("foo")).children_rshares2, db->get_comment("sam", string("foo")).children_rshares2 + old_rshares2); - BOOST_REQUIRE( - db->get_comment("alice", string( "foo" )).cashout_time == + BOOST_CHECK_EQUAL( + db->get_comment("alice", string( "foo" )).cashout_time, db->get_comment("alice", string( "foo" )).created + STEEMIT_CASHOUT_WINDOW_SECONDS); validate_database(); @@ -890,20 +1069,20 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); alice_bob_vote = vote_idx.find(std::make_tuple(new_bob_comment.id, new_alice.id)); new_rshares = ( (fc::uint128_t(new_alice.vesting_shares.amount.value) * used_power) / STEEMIT_100_PERCENT).to_uint64(); - BOOST_REQUIRE(new_bob_comment.net_rshares == old_net_rshares - old_vote_rshares + new_rshares); - BOOST_REQUIRE(new_bob_comment.abs_rshares == old_abs_rshares + new_rshares); - BOOST_REQUIRE(new_bob_comment.cashout_time == new_bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(alice_bob_vote->rshares == (int64_t)new_rshares); - BOOST_REQUIRE(alice_bob_vote->last_update == db->head_block_time()); - BOOST_REQUIRE(alice_bob_vote->vote_percent == op.weight); - BOOST_REQUIRE(db->get_account("alice").voting_power == alice_voting_power); + BOOST_CHECK_EQUAL(new_bob_comment.net_rshares, old_net_rshares - old_vote_rshares + new_rshares); + BOOST_CHECK_EQUAL(new_bob_comment.abs_rshares, old_abs_rshares + new_rshares); + BOOST_CHECK_EQUAL(new_bob_comment.cashout_time, new_bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK_EQUAL(alice_bob_vote->rshares, (int64_t)new_rshares); + BOOST_CHECK_EQUAL(alice_bob_vote->last_update, db->head_block_time()); + BOOST_CHECK_EQUAL(alice_bob_vote->vote_percent, op.weight); + BOOST_CHECK_EQUAL(db->get_account("alice").voting_power, alice_voting_power); validate_database(); BOOST_TEST_MESSAGE("--- Test decreasing vote rshares"); @@ -924,20 +1103,20 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); alice_bob_vote = vote_idx.find(std::make_tuple(new_bob_comment.id, new_alice.id)); new_rshares = ( (fc::uint128_t(new_alice.vesting_shares.amount.value) * used_power) / STEEMIT_100_PERCENT).to_uint64(); - BOOST_REQUIRE(new_bob_comment.net_rshares == old_net_rshares - old_vote_rshares - new_rshares); - BOOST_REQUIRE(new_bob_comment.abs_rshares == old_abs_rshares + new_rshares); - BOOST_REQUIRE(new_bob_comment.cashout_time == new_bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(alice_bob_vote->rshares == -1 * (int64_t)new_rshares); - BOOST_REQUIRE(alice_bob_vote->last_update == db->head_block_time()); - BOOST_REQUIRE(alice_bob_vote->vote_percent == op.weight); - BOOST_REQUIRE(db->get_account("alice").voting_power == alice_voting_power); + BOOST_CHECK_EQUAL(new_bob_comment.net_rshares, old_net_rshares - old_vote_rshares - new_rshares); + BOOST_CHECK_EQUAL(new_bob_comment.abs_rshares, old_abs_rshares + new_rshares); + BOOST_CHECK_EQUAL(new_bob_comment.cashout_time, new_bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK_EQUAL(alice_bob_vote->rshares, -1 * (int64_t)new_rshares); + BOOST_CHECK_EQUAL(alice_bob_vote->last_update, db->head_block_time()); + BOOST_CHECK_EQUAL(alice_bob_vote->vote_percent, op.weight); + BOOST_CHECK_EQUAL(db->get_account("alice").voting_power, alice_voting_power); validate_database(); BOOST_TEST_MESSAGE("--- Test changing a vote to 0 weight (aka: removing a vote)"); @@ -953,16 +1132,16 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); alice_bob_vote = vote_idx.find(std::make_tuple(new_bob_comment.id, new_alice.id)); - BOOST_REQUIRE(new_bob_comment.net_rshares == old_net_rshares - old_vote_rshares); - BOOST_REQUIRE(new_bob_comment.abs_rshares == old_abs_rshares); - BOOST_REQUIRE(new_bob_comment.cashout_time == new_bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(alice_bob_vote->rshares == 0); - BOOST_REQUIRE(alice_bob_vote->last_update == db->head_block_time()); - BOOST_REQUIRE(alice_bob_vote->vote_percent == op.weight); - BOOST_REQUIRE(db->get_account("alice").voting_power == alice_voting_power); + BOOST_CHECK_EQUAL(new_bob_comment.net_rshares, old_net_rshares - old_vote_rshares); + BOOST_CHECK_EQUAL(new_bob_comment.abs_rshares, old_abs_rshares); + BOOST_CHECK_EQUAL(new_bob_comment.cashout_time, new_bob_comment.created + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK_EQUAL(alice_bob_vote->rshares, 0); + BOOST_CHECK_EQUAL(alice_bob_vote->last_update, db->head_block_time()); + BOOST_CHECK_EQUAL(alice_bob_vote->vote_percent, op.weight); + BOOST_CHECK_EQUAL(db->get_account("alice").voting_power, alice_voting_power); validate_database(); BOOST_TEST_MESSAGE("--- Test failure when increasing rshares within lockout period"); @@ -977,7 +1156,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cannot_vote_within_last_minute_before_payout))); validate_database(); BOOST_TEST_MESSAGE("--- Test success when reducing rshares within lockout period"); @@ -987,7 +1168,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); validate_database(); BOOST_TEST_MESSAGE("--- Test failure with a new vote within lockout period"); @@ -998,7 +1179,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cannot_vote_within_last_minute_before_payout))); validate_database(); } } @@ -1009,6 +1192,43 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) try { BOOST_TEST_MESSAGE("Testing: transfer_validate"); + transfer_operation op; + op.from = "alice"; + op.to = "bob"; + op.amount = ASSET("10.000 GOLOS"); + op.memo = "memo string"; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + BOOST_CHECK_NO_THROW(op.validate()); + + BOOST_TEST_MESSAGE("--- failure when 'from' is empty"); + op.from = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "from")); + + BOOST_TEST_MESSAGE("--- failure when 'to' is empty"); + op.from = "alice"; + op.to = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "to")); + + BOOST_TEST_MESSAGE("--- failure when 'amount' is negative"); + op.to = "bob"; + op.amount = ASSET("-10.000 GOLOS"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "amount")); + + BOOST_TEST_MESSAGE("--- failure when 'amount' in GESTS"); + op.amount = ASSET("10.000000 GESTS"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "amount")); + + BOOST_TEST_MESSAGE("--- failure when 'memo' too large"); + op.amount = ASSET("10.000 GOLOS"); + op.memo = std::string(STEEMIT_MAX_MEMO_SIZE, ' '); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "memo")); + validate_database(); } FC_LOG_AND_RETHROW() @@ -1032,28 +1252,32 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the account's authority"); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test success with witness signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); validate_database(); } @@ -1078,7 +1302,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(update_op); tx.sign(corp_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); tx.operations.clear(); tx.signatures.clear(); @@ -1092,22 +1316,26 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.sign(alice_private_key, db->get_chain_id()); signature_type alice_sig = tx.signatures.back(); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); + tx.sign(bob_private_key, db->get_chain_id()); signature_type bob_sig = tx.signatures.back(); tx.sign(sam_private_key, db->get_chain_id()); signature_type sam_sig = tx.signatures.back(); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); tx.signatures.clear(); tx.signatures.push_back(alice_sig); tx.signatures.push_back(bob_sig); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); tx.signatures.clear(); tx.signatures.push_back(alice_sig); tx.signatures.push_back(sam_sig); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_transaction, 0)); } FC_LOG_AND_RETHROW() } @@ -1119,8 +1347,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) ACTORS((alice)(bob)) fund("alice", 10000); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("10.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == ASSET(" 0.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("10.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET(" 0.000 GOLOS").amount.value); signed_transaction tx; transfer_operation op; @@ -1133,12 +1361,10 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(alice.balance.amount.value == - ASSET("5.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == - ASSET("5.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("5.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("5.000 GOLOS").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Generating a block"); @@ -1147,20 +1373,36 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) const auto &new_alice = db->get_account("alice"); const auto &new_bob = db->get_account("bob"); - BOOST_REQUIRE(new_alice.balance.amount.value == ASSET("5.000 GOLOS").amount.value); - BOOST_REQUIRE(new_bob.balance.amount.value == ASSET("5.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_alice.balance.amount.value, ASSET("5.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_bob.balance.amount.value, ASSET("5.000 GOLOS").amount.value); + validate_database(); + + BOOST_TEST_MESSAGE("--- Test invalid amount (less digits after dot)"); + tx.signatures.clear(); + tx.operations.clear(); + op.amount = ASSET("2.00 GOLOS"); + tx.operations.push_back(op); + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.sign(alice_private_key, db->get_chain_id()); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(invalid_parameter, "amount"))); + + BOOST_CHECK_EQUAL(new_alice.balance.amount.value, ASSET("5.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_bob.balance.amount.value, ASSET("5.000 GOLOS").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test emptying an account"); tx.signatures.clear(); tx.operations.clear(); + op.amount = ASSET("5.000 GOLOS"); tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, database::skip_transaction_dupe_check); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check)); - BOOST_REQUIRE(new_alice.balance.amount.value == ASSET("0.000 GOLOS").amount.value); - BOOST_REQUIRE(new_bob.balance.amount.value == ASSET("10.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_alice.balance.amount.value, ASSET("0.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_bob.balance.amount.value, ASSET("10.000 GOLOS").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test transferring non-existent funds"); @@ -1169,12 +1411,25 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "alice", "fund", "5.000 GOLOS"))); - BOOST_REQUIRE(new_alice.balance.amount.value == ASSET("0.000 GOLOS").amount.value); - BOOST_REQUIRE(new_bob.balance.amount.value == ASSET("10.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_alice.balance.amount.value, ASSET("0.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_bob.balance.amount.value, ASSET("10.000 GOLOS").amount.value); validate_database(); + BOOST_TEST_MESSAGE("--- Test transferring to non-existent account"); + tx.signatures.clear(); + tx.operations.clear(); + op.to = "sam"; + tx.operations.push_back(op); + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.sign(alice_private_key, db->get_chain_id()); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "account", "sam"))); + } FC_LOG_AND_RETHROW() } @@ -1183,6 +1438,36 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) try { BOOST_TEST_MESSAGE("Testing: transfer_to_vesting_validate"); + transfer_to_vesting_operation op; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.from = "alice"; + op.to = ""; + op.amount = ASSET("5.000 GOLOS"); + BOOST_CHECK_NO_THROW(op.validate()); + + BOOST_TEST_MESSAGE("--- failed when 'from' is empty"); + op.from = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "from")); + + BOOST_TEST_MESSAGE("--- failed when 'amount' is negative"); + op.from = "alice"; + op.amount = ASSET("-2.000 GOLOS"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "amount")); + + BOOST_TEST_MESSAGE("--- failed when 'amount' have invalid symbol"); + op.amount = ASSET("2.000000 GESTS"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "amount")); + + BOOST_TEST_MESSAGE("--- failed when 'to' is invalid"); + op.amount = ASSET("5.000 GOLOS"); + op.to = "a"; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "to")); + validate_database(); } FC_LOG_AND_RETHROW() @@ -1205,28 +1490,32 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the account's authority"); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test success with from signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); validate_database(); } @@ -1242,7 +1531,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) const auto &gpo = db->get_dynamic_global_properties(); - BOOST_REQUIRE(alice.balance == ASSET("10.000 GOLOS")); + BOOST_CHECK_EQUAL(alice.balance, ASSET("10.000 GOLOS")); auto shares = asset(gpo.total_vesting_shares.amount, VESTS_SYMBOL); auto vests = asset(gpo.total_vesting_fund_steem.amount, STEEM_SYMBOL); @@ -1258,17 +1547,17 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); auto new_vest = op.amount * (shares / vests); shares += new_vest; vests += op.amount; alice_shares += new_vest; - BOOST_REQUIRE(alice.balance.amount.value == ASSET("2.500 GOLOS").amount.value); - BOOST_REQUIRE(alice.vesting_shares.amount.value == alice_shares.amount.value); - BOOST_REQUIRE(gpo.total_vesting_fund_steem.amount.value == vests.amount.value); - BOOST_REQUIRE(gpo.total_vesting_shares.amount.value == shares.amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("2.500 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.vesting_shares.amount.value, alice_shares.amount.value); + BOOST_CHECK_EQUAL(gpo.total_vesting_fund_steem.amount.value, vests.amount.value); + BOOST_CHECK_EQUAL(gpo.total_vesting_shares.amount.value, shares.amount.value); validate_database(); op.to = "bob"; @@ -1278,29 +1567,31 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); new_vest = asset((op.amount * (shares / vests)).amount, VESTS_SYMBOL); shares += new_vest; vests += op.amount; bob_shares += new_vest; - BOOST_REQUIRE(alice.balance.amount.value == ASSET("0.500 GOLOS").amount.value); - BOOST_REQUIRE(alice.vesting_shares.amount.value == alice_shares.amount.value); - BOOST_REQUIRE(bob.balance.amount.value == ASSET("0.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.vesting_shares.amount.value == bob_shares.amount.value); - BOOST_REQUIRE(gpo.total_vesting_fund_steem.amount.value == vests.amount.value); - BOOST_REQUIRE(gpo.total_vesting_shares.amount.value == shares.amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("0.500 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.vesting_shares.amount.value, alice_shares.amount.value); + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("0.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(bob.vesting_shares.amount.value, bob_shares.amount.value); + BOOST_CHECK_EQUAL(gpo.total_vesting_fund_steem.amount.value, vests.amount.value); + BOOST_CHECK_EQUAL(gpo.total_vesting_shares.amount.value, shares.amount.value); validate_database(); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "alice", "fund", "2.000 GOLOS"))); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("0.500 GOLOS").amount.value); - BOOST_REQUIRE(alice.vesting_shares.amount.value == alice_shares.amount.value); - BOOST_REQUIRE(bob.balance.amount.value == ASSET("0.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.vesting_shares.amount.value == bob_shares.amount.value); - BOOST_REQUIRE(gpo.total_vesting_fund_steem.amount.value == vests.amount.value); - BOOST_REQUIRE(gpo.total_vesting_shares.amount.value == shares.amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("0.500 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.vesting_shares.amount.value, alice_shares.amount.value); + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("0.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(bob.vesting_shares.amount.value, bob_shares.amount.value); + BOOST_CHECK_EQUAL(gpo.total_vesting_fund_steem.amount.value, vests.amount.value); + BOOST_CHECK_EQUAL(gpo.total_vesting_shares.amount.value, shares.amount.value); validate_database(); } FC_LOG_AND_RETHROW() @@ -1310,6 +1601,29 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) try { BOOST_TEST_MESSAGE("Testing: withdraw_vesting_validate"); + withdraw_vesting_operation op; + + BOOST_TEST_MESSAGE("--- success with valid parameters"); + op.account = "alice"; + op.vesting_shares = ASSET("0.001000 GESTS"); + BOOST_CHECK_NO_THROW(op.validate()); + + BOOST_TEST_MESSAGE("--- failed when 'account' is empty"); + op.account = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "account")); + + BOOST_TEST_MESSAGE("--- failed when 'vesting_shares' not in GESTS"); + op.account = "alice"; + op.vesting_shares = ASSET("1.000 GOLOS"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "vesting_shares")); + + BOOST_TEST_MESSAGE("--- failed when 'vesting_shares' is negative"); + op.vesting_shares = ASSET("-1.000000 GESTS"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "vesting_shares")); + validate_database(); } FC_LOG_AND_RETHROW() @@ -1332,26 +1646,30 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); BOOST_TEST_MESSAGE("--- Test failure when no signature."); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test success with account signature"); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, database::skip_transaction_dupe_check); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check)); BOOST_TEST_MESSAGE("--- Test failure with duplicate signature"); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with additional incorrect signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with incorrect signature"); tx.signatures.clear(); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); validate_database(); } @@ -1381,14 +1699,14 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(alice.vesting_shares.amount.value == old_vesting_shares.amount.value); - BOOST_REQUIRE(alice.vesting_withdraw_rate.amount.value == + BOOST_CHECK_EQUAL(alice.vesting_shares.amount.value, old_vesting_shares.amount.value); + BOOST_CHECK_EQUAL(alice.vesting_withdraw_rate.amount.value, (old_vesting_shares.amount / (STEEMIT_VESTING_WITHDRAW_INTERVALS * 2)).value); - BOOST_REQUIRE(alice.to_withdraw.value == op.vesting_shares.amount.value); - BOOST_REQUIRE(alice.next_vesting_withdrawal == db->head_block_time() + STEEMIT_VESTING_WITHDRAW_INTERVAL_SECONDS); + BOOST_CHECK_EQUAL(alice.to_withdraw.value, op.vesting_shares.amount.value); + BOOST_CHECK_EQUAL(alice.next_vesting_withdrawal, db->head_block_time() + STEEMIT_VESTING_WITHDRAW_INTERVAL_SECONDS); validate_database(); BOOST_TEST_MESSAGE("--- Test changing vesting withdrawal"); @@ -1399,14 +1717,14 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(alice.vesting_shares.amount.value == old_vesting_shares.amount.value); - BOOST_REQUIRE(alice.vesting_withdraw_rate.amount.value == + BOOST_CHECK_EQUAL(alice.vesting_shares.amount.value, old_vesting_shares.amount.value); + BOOST_CHECK_EQUAL(alice.vesting_withdraw_rate.amount.value, (old_vesting_shares.amount / (STEEMIT_VESTING_WITHDRAW_INTERVALS * 3)).value); - BOOST_REQUIRE(alice.to_withdraw.value == op.vesting_shares.amount.value); - BOOST_REQUIRE(alice.next_vesting_withdrawal == + BOOST_CHECK_EQUAL(alice.to_withdraw.value, op.vesting_shares.amount.value); + BOOST_CHECK_EQUAL(alice.next_vesting_withdrawal, db->head_block_time() + STEEMIT_VESTING_WITHDRAW_INTERVAL_SECONDS); validate_database(); @@ -1419,13 +1737,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "alice", "having vesting shares", asset(alice.vesting_shares.amount * 2, VESTS_SYMBOL).to_string()))); - BOOST_REQUIRE(alice.vesting_shares.amount.value == old_vesting_shares.amount.value); - BOOST_REQUIRE(alice.vesting_withdraw_rate.amount.value == + BOOST_CHECK_EQUAL(alice.vesting_shares.amount.value, old_vesting_shares.amount.value); + BOOST_CHECK_EQUAL(alice.vesting_withdraw_rate.amount.value, (old_vesting_shares.amount / (STEEMIT_VESTING_WITHDRAW_INTERVALS * 3)).value); - BOOST_REQUIRE(alice.next_vesting_withdrawal == + BOOST_CHECK_EQUAL(alice.next_vesting_withdrawal, db->head_block_time() + STEEMIT_VESTING_WITHDRAW_INTERVAL_SECONDS); validate_database(); @@ -1438,19 +1758,19 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(alice.vesting_shares.amount.value == old_vesting_shares.amount.value); - BOOST_REQUIRE(alice.vesting_withdraw_rate.amount.value == 0); - BOOST_REQUIRE(alice.to_withdraw.value == 0); - BOOST_REQUIRE(alice.next_vesting_withdrawal == fc::time_point_sec::maximum()); + BOOST_CHECK_EQUAL(alice.vesting_shares.amount.value, old_vesting_shares.amount.value); + BOOST_CHECK_EQUAL(alice.vesting_withdraw_rate.amount.value, 0); + BOOST_CHECK_EQUAL(alice.to_withdraw.value, 0); + BOOST_CHECK_EQUAL(alice.next_vesting_withdrawal, fc::time_point_sec::maximum()); BOOST_TEST_MESSAGE("--- Test cancelling a withdraw when below the account creation fee"); op.vesting_shares = alice.vesting_shares; tx.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); generate_block(); } @@ -1480,74 +1800,61 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(db->get_account("alice").vesting_withdraw_rate == + BOOST_CHECK_EQUAL(db->get_account("alice").vesting_withdraw_rate, ASSET("0.000000 GESTS")); validate_database(); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(witness_update_validate) { try { BOOST_TEST_MESSAGE("Testing: withness_update_validate"); + BOOST_TEST_MESSAGE("--- success on valid parameters"); + witness_update_operation op; + op.owner = "bob"; + op.url = "http://localhost"; + op.fee = ASSET_GOLOS(100); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, url, "-"); + CHECK_PARAM_VALID(op, url, string(STEEMIT_MAX_WITNESS_URL_LENGTH, ' ')); + CHECK_PARAM_VALID(op, url, u8"тест"); + CHECK_PARAM_VALID(op, fee, ASSET_GOLOS(0)); + + BOOST_TEST_MESSAGE("--- failure when owner is empty"); + CHECK_PARAM_INVALID(op, owner, ""); + + BOOST_TEST_MESSAGE("--- failure when url is empty or too long or have invalid utf8"); + CHECK_PARAM_INVALID(op, url, ""); + // CHECK_PARAM_INVALID(op, url, string(1+STEEMIT_MAX_WITNESS_URL_LENGTH, ' ')); + CHECK_PARAM_INVALID(op, url, BAD_UTF8_STRING); + + BOOST_TEST_MESSAGE("--- failure when fee in not GOLOS or negative"); + CHECK_PARAM_INVALID(op, fee, ASSET_GBG(1)); + CHECK_PARAM_INVALID(op, fee, ASSET_GESTS(1)); + CHECK_PARAM_INVALID(op, fee, ASSET_GOLOS(-1)); + + // TODO: chain_properties_17 validate_database(); } FC_LOG_AND_RETHROW() } - BOOST_AUTO_TEST_CASE(witness_update_authorities) { - try { - BOOST_TEST_MESSAGE("Testing: witness_update_authorities"); - - ACTORS((alice)(bob)); - fund("alice", 10000); - - private_key_type signing_key = generate_private_key("new_key"); - - witness_update_operation op; - op.owner = "alice"; - op.url = "foo.bar"; - op.fee = ASSET("1.000 GOLOS"); - op.block_signing_key = signing_key.get_public_key(); - - signed_transaction tx; - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.operations.push_back(op); - - BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); - - BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the account's authority"); - tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + BOOST_AUTO_TEST_CASE(witness_update_authorities) { try { + BOOST_TEST_MESSAGE("Testing: witness_update_authorities"); + witness_update_operation op; + op.owner = "bob"; + op.url = "http://localhost"; + // op.fee = ASSET_GOLOS(1); + // op.block_signing_key = signing_key.get_public_key(); - BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); - tx.signatures.clear(); - tx.sign(alice_private_key, db->get_chain_id()); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); - - BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); - tx.signatures.clear(); - tx.sign(alice_private_key, db->get_chain_id()); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); - - BOOST_TEST_MESSAGE("--- Test success with witness signature"); - tx.signatures.clear(); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"bob"}), account_name_set()); - tx.signatures.clear(); - tx.sign(signing_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); - validate_database(); - } - FC_LOG_AND_RETHROW() - } + } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE(witness_update_apply) { try { @@ -1555,11 +1862,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) ACTORS((alice)) fund("alice", 10000); - private_key_type signing_key = generate_private_key("new_key"); BOOST_TEST_MESSAGE("--- Test upgrading an account to a witness"); - signed_transaction tx; tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); @@ -1576,74 +1881,87 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op1.owner = op.owner; op1.props = op.props; tx.operations.push_back(op1); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - const witness_object &alice_witness = db->get_witness("alice"); - - BOOST_REQUIRE(alice_witness.owner == "alice"); - BOOST_REQUIRE(alice_witness.created == db->head_block_time()); - BOOST_REQUIRE(to_string(alice_witness.url) == op.url); - BOOST_REQUIRE(alice_witness.signing_key == op.block_signing_key); - BOOST_REQUIRE(alice_witness.props.account_creation_fee == op.props.account_creation_fee); - BOOST_REQUIRE(alice_witness.props.maximum_block_size == op.props.maximum_block_size); - BOOST_REQUIRE(alice_witness.total_missed == 0); - BOOST_REQUIRE(alice_witness.last_aslot == 0); - BOOST_REQUIRE(alice_witness.last_confirmed_block_num == 0); - BOOST_REQUIRE(alice_witness.pow_worker == 0); - BOOST_REQUIRE(alice_witness.votes.value == 0); - BOOST_REQUIRE(alice_witness.virtual_last_update == 0); - BOOST_REQUIRE(alice_witness.virtual_position == 0); - BOOST_REQUIRE(alice_witness.virtual_scheduled_time == fc::uint128_t::max_value()); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("10.000 GOLOS").amount.value); // No fee + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); + + const witness_object& w = db->get_witness("alice"); + BOOST_CHECK_EQUAL(w.owner, "alice"); + BOOST_CHECK_EQUAL(w.created, db->head_block_time()); + BOOST_CHECK_EQUAL(to_string(w.url), op.url); + BOOST_CHECK_EQUAL(w.signing_key, op.block_signing_key); + BOOST_CHECK_EQUAL(w.props.account_creation_fee, op.props.account_creation_fee); + BOOST_CHECK_EQUAL(w.props.maximum_block_size, op.props.maximum_block_size); + BOOST_CHECK_EQUAL(w.total_missed, 0); + BOOST_CHECK_EQUAL(w.last_aslot, 0); + BOOST_CHECK_EQUAL(w.last_confirmed_block_num, 0); + BOOST_CHECK_EQUAL(w.pow_worker, 0); + BOOST_CHECK_EQUAL(w.votes.value, 0); + BOOST_CHECK_EQUAL(w.virtual_last_update, 0); + BOOST_CHECK_EQUAL(w.virtual_position, 0); + BOOST_CHECK_EQUAL(w.virtual_scheduled_time, fc::uint128_t::max_value()); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("10.000 GOLOS").amount.value); // No fee validate_database(); BOOST_TEST_MESSAGE("--- Test updating a witness"); - - tx.signatures.clear(); - tx.operations.clear(); + tx.clear(); op.url = "bar.foo"; tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - - db->push_transaction(tx, 0); - - BOOST_REQUIRE(alice_witness.owner == "alice"); - BOOST_REQUIRE(alice_witness.created == db->head_block_time()); - BOOST_REQUIRE(to_string(alice_witness.url) == "bar.foo"); - BOOST_REQUIRE(alice_witness.signing_key == op.block_signing_key); - BOOST_REQUIRE(alice_witness.props.account_creation_fee == op.props.account_creation_fee); - BOOST_REQUIRE(alice_witness.props.maximum_block_size == op.props.maximum_block_size); - BOOST_REQUIRE(alice_witness.total_missed == 0); - BOOST_REQUIRE(alice_witness.last_aslot == 0); - BOOST_REQUIRE(alice_witness.last_confirmed_block_num == 0); - BOOST_REQUIRE(alice_witness.pow_worker == 0); - BOOST_REQUIRE(alice_witness.votes.value == 0); - BOOST_REQUIRE(alice_witness.virtual_last_update == 0); - BOOST_REQUIRE(alice_witness.virtual_position == 0); - BOOST_REQUIRE(alice_witness.virtual_scheduled_time == fc::uint128_t::max_value()); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("10.000 GOLOS").amount.value); - validate_database(); - - BOOST_TEST_MESSAGE("--- Test failure when upgrading a non-existent account"); - - tx.signatures.clear(); - tx.operations.clear(); - op.owner = "bob"; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); + + BOOST_CHECK_EQUAL(w.owner, "alice"); + BOOST_CHECK_EQUAL(w.created, db->head_block_time()); + BOOST_CHECK_EQUAL(to_string(w.url), "bar.foo"); + BOOST_CHECK_EQUAL(w.signing_key, op.block_signing_key); + BOOST_CHECK_EQUAL(w.props.account_creation_fee, op.props.account_creation_fee); + BOOST_CHECK_EQUAL(w.props.maximum_block_size, op.props.maximum_block_size); + BOOST_CHECK_EQUAL(w.total_missed, 0); + BOOST_CHECK_EQUAL(w.last_aslot, 0); + BOOST_CHECK_EQUAL(w.last_confirmed_block_num, 0); + BOOST_CHECK_EQUAL(w.pow_worker, 0); + BOOST_CHECK_EQUAL(w.votes.value, 0); + BOOST_CHECK_EQUAL(w.virtual_last_update, 0); + BOOST_CHECK_EQUAL(w.virtual_position, 0); + BOOST_CHECK_EQUAL(w.virtual_scheduled_time, fc::uint128_t::max_value()); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("10.000 GOLOS").amount.value); validate_database(); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(chain_properties_update_validate) { try { + BOOST_TEST_MESSAGE("!NOT READY! Testing: chain_properties_update_validate"); + // TODO + } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_CASE(chain_properties_update_authorities) { try { + BOOST_TEST_MESSAGE("!NOT READY! Testing: chain_properties_update_authorities"); + // TODO + } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_CASE(chain_properties_update_apply) { try { + BOOST_TEST_MESSAGE("!NOT READY! Testing: chain_properties_update_apply"); + // TODO + } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_CASE(account_witness_vote_validate) { try { BOOST_TEST_MESSAGE("Testing: account_witness_vote_validate"); + account_witness_vote_operation op; - validate_database(); + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.account = "bob"; + op.witness = "alice"; + CHECK_OP_VALID(op); + + BOOST_TEST_MESSAGE("--- failed when 'account' is empty"); + CHECK_PARAM_INVALID(op, account, ""); + + BOOST_TEST_MESSAGE("--- failed when 'witness' is empty"); + CHECK_PARAM_INVALID(op, witness, ""); + + validate_database(); } FC_LOG_AND_RETHROW() } @@ -1667,34 +1985,39 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the account's authority"); tx.sign(bob_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); tx.signatures.clear(); tx.sign(bob_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); tx.signatures.clear(); tx.sign(bob_private_key, db->get_chain_id()); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test success with witness signature"); tx.signatures.clear(); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- Test failure with proxy signature"); proxy("bob", "sam"); tx.signatures.clear(); tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); validate_database(); } @@ -1727,10 +2050,10 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(sam_witness.votes == alice.vesting_shares.amount); - BOOST_REQUIRE( + BOOST_CHECK_EQUAL(sam_witness.votes, alice.vesting_shares.amount); + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, alice.id)) != witness_vote_idx.end()); validate_database(); @@ -1742,17 +2065,20 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - BOOST_REQUIRE(sam_witness.votes.value == 0); - BOOST_REQUIRE( + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); + BOOST_CHECK_EQUAL(sam_witness.votes.value, 0); + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, alice.id)) == witness_vote_idx.end()); BOOST_TEST_MESSAGE("--- Test failure when attempting to revoke a non-existent vote"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), fc::exception); - BOOST_REQUIRE(sam_witness.votes.value == 0); - BOOST_REQUIRE( + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::witness_vote_does_not_exist))); + + BOOST_CHECK_EQUAL(sam_witness.votes.value, 0); + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, alice.id)) == witness_vote_idx.end()); @@ -1765,13 +2091,13 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(sam_witness.votes == (bob.proxied_vsf_votes_total() + bob.vesting_shares.amount)); - BOOST_REQUIRE( + BOOST_CHECK_EQUAL(sam_witness.votes, (bob.proxied_vsf_votes_total() + bob.vesting_shares.amount)); + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, bob.id)) != witness_vote_idx.end()); - BOOST_REQUIRE( + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, alice.id)) == witness_vote_idx.end()); @@ -1781,13 +2107,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.account = "alice"; tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cannot_vote_when_route_are_set))); - BOOST_REQUIRE(sam_witness.votes == (bob.proxied_vsf_votes_total() + bob.vesting_shares.amount)); - BOOST_REQUIRE( + BOOST_CHECK_EQUAL(sam_witness.votes, (bob.proxied_vsf_votes_total() + bob.vesting_shares.amount)); + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, bob.id)) != witness_vote_idx.end()); - BOOST_REQUIRE( + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, alice.id)) == witness_vote_idx.end()); @@ -1799,13 +2127,13 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(sam_witness.votes.value == 0); - BOOST_REQUIRE( + BOOST_CHECK_EQUAL(sam_witness.votes.value, 0); + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, bob.id)) == witness_vote_idx.end()); - BOOST_REQUIRE( + BOOST_CHECK( witness_vote_idx.find(std::make_tuple(sam_witness.id, alice.id)) == witness_vote_idx.end()); @@ -1816,8 +2144,10 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.approve = true; tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "witness", "dave"))); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); validate_database(); BOOST_TEST_MESSAGE("--- Test failure when voting for an account that is not a witness"); @@ -1826,8 +2156,10 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.witness = "alice"; tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "witness", "alice"))); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); validate_database(); } FC_LOG_AND_RETHROW() @@ -1836,6 +2168,22 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(account_witness_proxy_validate) { try { BOOST_TEST_MESSAGE("Testing: account_witness_proxy_validate"); + account_witness_proxy_operation op; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.account = "bob"; + op.proxy = "alice"; + CHECK_OP_VALID(op); + + BOOST_TEST_MESSAGE("--- failed when 'account' is empty"); + CHECK_PARAM_INVALID(op, account, ""); + + BOOST_TEST_MESSAGE("--- success when 'proxy' is empty"); + CHECK_PARAM_VALID(op, proxy, ""); + + BOOST_TEST_MESSAGE("--- failed when 'proxy' not valid"); + CHECK_PARAM_INVALID(op, proxy, "a"); + CHECK_PARAM_INVALID(op, proxy, "bob"); validate_database(); } @@ -1858,33 +2206,38 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the account's authority"); tx.sign(bob_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); tx.signatures.clear(); tx.sign(bob_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); tx.signatures.clear(); tx.sign(bob_private_key, db->get_chain_id()); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test success with witness signature"); tx.signatures.clear(); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- Test failure with proxy signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); validate_database(); } @@ -1917,12 +2270,12 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(bob.proxy == "alice"); - BOOST_REQUIRE(bob.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(alice.proxy == STEEMIT_PROXY_TO_SELF_ACCOUNT); - BOOST_REQUIRE(alice.proxied_vsf_votes_total() == + BOOST_CHECK_EQUAL(bob.proxy, "alice"); + BOOST_CHECK_EQUAL(bob.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(alice.proxy, STEEMIT_PROXY_TO_SELF_ACCOUNT); + BOOST_CHECK_EQUAL(alice.proxied_vsf_votes_total(), bob.vesting_shares.amount); validate_database(); @@ -1935,23 +2288,25 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(bob.proxy == "sam"); - BOOST_REQUIRE(bob.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(alice.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(sam.proxy == STEEMIT_PROXY_TO_SELF_ACCOUNT); - BOOST_REQUIRE(sam.proxied_vsf_votes_total().value == bob.vesting_shares.amount); + BOOST_CHECK_EQUAL(bob.proxy, "sam"); + BOOST_CHECK_EQUAL(bob.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(alice.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(sam.proxy, STEEMIT_PROXY_TO_SELF_ACCOUNT); + BOOST_CHECK_EQUAL(sam.proxied_vsf_votes_total().value, bob.vesting_shares.amount); validate_database(); BOOST_TEST_MESSAGE("--- Test failure when changing proxy to existing proxy"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::proxy_must_change))); - BOOST_REQUIRE(bob.proxy == "sam"); - BOOST_REQUIRE(bob.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(sam.proxy == STEEMIT_PROXY_TO_SELF_ACCOUNT); - BOOST_REQUIRE(sam.proxied_vsf_votes_total() == bob.vesting_shares.amount); + BOOST_CHECK_EQUAL(bob.proxy, "sam"); + BOOST_CHECK_EQUAL(bob.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(sam.proxy, STEEMIT_PROXY_TO_SELF_ACCOUNT); + BOOST_CHECK_EQUAL(sam.proxied_vsf_votes_total(), bob.vesting_shares.amount); validate_database(); BOOST_TEST_MESSAGE("--- Test adding a grandparent proxy"); @@ -1964,14 +2319,14 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(bob.proxy == "sam"); - BOOST_REQUIRE(bob.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(sam.proxy == "dave"); - BOOST_REQUIRE(sam.proxied_vsf_votes_total() == bob.vesting_shares.amount); - BOOST_REQUIRE(dave.proxy == STEEMIT_PROXY_TO_SELF_ACCOUNT); - BOOST_REQUIRE(dave.proxied_vsf_votes_total() == + BOOST_CHECK_EQUAL(bob.proxy, "sam"); + BOOST_CHECK_EQUAL(bob.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(sam.proxy, "dave"); + BOOST_CHECK_EQUAL(sam.proxied_vsf_votes_total(), bob.vesting_shares.amount); + BOOST_CHECK_EQUAL(dave.proxy, STEEMIT_PROXY_TO_SELF_ACCOUNT); + BOOST_CHECK_EQUAL(dave.proxied_vsf_votes_total(), (sam.vesting_shares + bob.vesting_shares).amount); validate_database(); @@ -1986,17 +2341,17 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(alice.proxy == "sam"); - BOOST_REQUIRE(alice.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(bob.proxy == "sam"); - BOOST_REQUIRE(bob.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(sam.proxy == "dave"); - BOOST_REQUIRE(sam.proxied_vsf_votes_total() == + BOOST_CHECK_EQUAL(alice.proxy, "sam"); + BOOST_CHECK_EQUAL(alice.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(bob.proxy, "sam"); + BOOST_CHECK_EQUAL(bob.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(sam.proxy, "dave"); + BOOST_CHECK_EQUAL(sam.proxied_vsf_votes_total(), (bob.vesting_shares + alice.vesting_shares).amount); - BOOST_REQUIRE(dave.proxy == STEEMIT_PROXY_TO_SELF_ACCOUNT); - BOOST_REQUIRE(dave.proxied_vsf_votes_total() == + BOOST_CHECK_EQUAL(dave.proxy, STEEMIT_PROXY_TO_SELF_ACCOUNT); + BOOST_CHECK_EQUAL(dave.proxied_vsf_votes_total(), (sam.vesting_shares + bob.vesting_shares + alice.vesting_shares).amount); validate_database(); @@ -2011,16 +2366,16 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(alice.proxy == "sam"); - BOOST_REQUIRE(alice.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(bob.proxy == STEEMIT_PROXY_TO_SELF_ACCOUNT); - BOOST_REQUIRE(bob.proxied_vsf_votes_total().value == 0); - BOOST_REQUIRE(sam.proxy == "dave"); - BOOST_REQUIRE(sam.proxied_vsf_votes_total() == alice.vesting_shares.amount); - BOOST_REQUIRE(dave.proxy == STEEMIT_PROXY_TO_SELF_ACCOUNT); - BOOST_REQUIRE(dave.proxied_vsf_votes_total() == + BOOST_CHECK_EQUAL(alice.proxy, "sam"); + BOOST_CHECK_EQUAL(alice.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(bob.proxy, STEEMIT_PROXY_TO_SELF_ACCOUNT); + BOOST_CHECK_EQUAL(bob.proxied_vsf_votes_total().value, 0); + BOOST_CHECK_EQUAL(sam.proxy, "dave"); + BOOST_CHECK_EQUAL(sam.proxied_vsf_votes_total(), alice.vesting_shares.amount); + BOOST_CHECK_EQUAL(dave.proxy, STEEMIT_PROXY_TO_SELF_ACCOUNT); + BOOST_CHECK_EQUAL(dave.proxied_vsf_votes_total(), (sam.vesting_shares + alice.vesting_shares).amount); validate_database(); @@ -2033,7 +2388,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(vote); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); tx.operations.clear(); tx.signatures.clear(); @@ -2042,9 +2397,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(db->get_witness(STEEMIT_INIT_MINER_NAME).votes == + BOOST_CHECK_EQUAL(db->get_witness(STEEMIT_INIT_MINER_NAME).votes, (alice.vesting_shares + bob.vesting_shares).amount); validate_database(); @@ -2055,9 +2410,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(db->get_witness(STEEMIT_INIT_MINER_NAME).votes == bob.vesting_shares.amount); + BOOST_CHECK_EQUAL(db->get_witness(STEEMIT_INIT_MINER_NAME).votes, bob.vesting_shares.amount); validate_database(); } FC_LOG_AND_RETHROW() @@ -2067,82 +2422,88 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) custom_operation op; op.required_auths.insert("alice"); op.required_auths.insert("bob"); - - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - expected.insert("alice"); - expected.insert("bob"); - op.get_required_active_authorities(auths); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"alice","bob"}), account_name_set()); } BOOST_AUTO_TEST_CASE(custom_json_authorities) { custom_json_operation op; op.required_auths.insert("alice"); op.required_posting_auths.insert("bob"); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"alice"}), account_name_set({"bob"})); + } - flat_set auths; - flat_set expected; + BOOST_AUTO_TEST_CASE(custom_json_validate) { + custom_json_operation op; - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "required_auths")); - expected.insert("alice"); - op.get_required_active_authorities(auths); - BOOST_REQUIRE(auths == expected); + op.required_auths.insert("alice"); + op.id = "id"; + op.json = "{}"; + CHECK_OP_VALID(op); - auths.clear(); - expected.clear(); - expected.insert("bob"); - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); + CHECK_PARAM_INVALID(op, id, std::string(33, 's')); + CHECK_PARAM_INVALID(op, json, "{]"); } BOOST_AUTO_TEST_CASE(custom_binary_authorities) { ACTORS((alice)) + auto alice_authority = db->get("alice").posting; + custom_binary_operation op; op.required_owner_auths.insert("alice"); op.required_active_auths.insert("bob"); op.required_posting_auths.insert("sam"); - op.required_auths.push_back(db->get("alice").posting); + op.required_auths.push_back(alice_authority); - flat_set acc_auths; - flat_set acc_expected; + CHECK_OP_AUTHS(op, account_name_set({"alice"}), account_name_set({"bob"}), account_name_set({"sam"})); + + vector expected = {alice_authority}; vector auths; - vector expected; + op.get_required_authorities(auths); + BOOST_CHECK_EQUAL(auths, expected); + } - acc_expected.insert("alice"); - op.get_required_owner_authorities(acc_auths); - BOOST_REQUIRE(acc_auths == acc_expected); + BOOST_AUTO_TEST_CASE(custom_binary_validate) { + custom_binary_operation op; - acc_auths.clear(); - acc_expected.clear(); - acc_expected.insert("bob"); - op.get_required_active_authorities(acc_auths); - BOOST_REQUIRE(acc_auths == acc_expected); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "required_owner_auths")); - acc_auths.clear(); - acc_expected.clear(); - acc_expected.insert("sam"); - op.get_required_posting_authorities(acc_auths); - BOOST_REQUIRE(acc_auths == acc_expected); + op.required_active_auths.insert("alice"); + op.id = "id"; + CHECK_OP_VALID(op); - expected.push_back(db->get("alice").posting); - op.get_required_authorities(auths); - BOOST_REQUIRE(auths == expected); + CHECK_PARAM_INVALID(op, id, std::string(33, 's')); + + authority auth; + auth.add_authority("a", 1); + op.required_auths.push_back(auth); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "required_auths")); } BOOST_AUTO_TEST_CASE(feed_publish_validate) { try { BOOST_TEST_MESSAGE("Testing: feed_publish_validate"); + feed_publish_operation op; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.publisher = "alice"; + op.exchange_rate = price(ASSET("1.000 GOLOS"), ASSET("1.000 GBG")); + CHECK_OP_VALID(op); + + BOOST_TEST_MESSAGE("--- failed when 'publisher' is empty"); + CHECK_PARAM_INVALID(op, publisher, ""); + + BOOST_TEST_MESSAGE("--- failed when 'exchange_rate' not for GOLOS/GBG market"); + CHECK_PARAM_INVALID_LOGIC(op, exchange_rate, price(ASSET("1.000000 GESTS"), ASSET("1.000 GBG")), + price_feed_must_be_for_golos_gbg_market); + + BOOST_TEST_MESSAGE("--- failed when 'exchange_rate' is invalid"); + CHECK_PARAM_INVALID(op, exchange_rate, price(ASSET("0.000 GOLOS"), ASSET("1.000 GBG"))); } FC_LOG_AND_RETHROW() } @@ -2164,28 +2525,32 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); BOOST_TEST_MESSAGE("--- Test failure when no signature."); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure with incorrect signature"); tx.signatures.clear(); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure with duplicate signature"); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with additional incorrect signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test success with witness account signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, database::skip_transaction_dupe_check); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check)); validate_database(); } @@ -2210,12 +2575,12 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); witness_object &alice_witness = const_cast< witness_object & >( db->get_witness("alice")); - BOOST_REQUIRE(alice_witness.sbd_exchange_rate == op.exchange_rate); - BOOST_REQUIRE(alice_witness.last_sbd_exchange_update == db->head_block_time()); + BOOST_CHECK_EQUAL(alice_witness.sbd_exchange_rate, op.exchange_rate); + BOOST_CHECK_EQUAL(alice_witness.last_sbd_exchange_update, db->head_block_time()); validate_database(); BOOST_TEST_MESSAGE("--- Test failure publishing to non-existent witness"); @@ -2223,9 +2588,12 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.clear(); tx.signatures.clear(); op.publisher = "bob"; + tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + // In this case missing_object thrown at authority checking step + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(missing_object, "authority", "bob")); validate_database(); BOOST_TEST_MESSAGE("--- Test updating price feed"); @@ -2237,12 +2605,12 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); alice_witness = const_cast< witness_object & >( db->get_witness("alice")); - BOOST_REQUIRE(std::abs(alice_witness.sbd_exchange_rate.to_real() - - op.exchange_rate.to_real()) < 0.0000005); - BOOST_REQUIRE(alice_witness.last_sbd_exchange_update == db->head_block_time()); + BOOST_CHECK_LT(std::abs(alice_witness.sbd_exchange_rate.to_real() - + op.exchange_rate.to_real()), 0.0000005); + BOOST_CHECK_EQUAL(alice_witness.last_sbd_exchange_update, db->head_block_time()); validate_database(); } FC_LOG_AND_RETHROW() @@ -2251,6 +2619,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(convert_validate) { try { BOOST_TEST_MESSAGE("Testing: convert_validate"); + convert_operation op; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.owner = "alice"; + op.amount = ASSET("10.000 GBG"); + CHECK_OP_VALID(op); + + BOOST_TEST_MESSAGE("--- failed when 'owner' is empty"); + CHECK_PARAM_INVALID(op, owner, ""); + + BOOST_TEST_MESSAGE("--- failed when 'amount' is invalid"); + CHECK_PARAM_INVALID(op, amount, ASSET("10.000000 GESTS")); + CHECK_PARAM_INVALID(op, amount, ASSET("-10.000 GBG")); + + validate_database(); } FC_LOG_AND_RETHROW() } @@ -2278,28 +2661,32 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the account's authority"); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test success with owner signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); validate_database(); } @@ -2315,7 +2702,6 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) convert_operation op; signed_transaction tx; - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); const auto &convert_request_idx = db->get_index().indices().get(); @@ -2327,15 +2713,18 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) const auto &new_alice = db->get_account("alice"); const auto &new_bob = db->get_account("bob"); - BOOST_TEST_MESSAGE("--- Test failure when account does not have the required GOLOS"); + BOOST_TEST_MESSAGE("--- Test failure when account does not have the required GOLOS (invalid parameter, only GBG)"); op.owner = "bob"; op.amount = ASSET("5.000 GOLOS"); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + // Convert operation only available for GBG + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(invalid_parameter, "amount"))); - BOOST_REQUIRE(new_bob.balance.amount.value == ASSET("3.000 GOLOS").amount.value); - BOOST_REQUIRE(new_bob.sbd_balance.amount.value == ASSET("7.000 GBG").amount.value); + BOOST_CHECK_EQUAL(new_bob.balance.amount.value, ASSET("3.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_bob.sbd_balance.amount.value, ASSET("7.000 GBG").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test failure when account does not have the required GBG"); @@ -2343,12 +2732,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.amount = ASSET("5.000 GBG"); tx.operations.clear(); tx.signatures.clear(); + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "alice", "fund", "5.000 GBG"))); - BOOST_REQUIRE(new_alice.balance.amount.value == ASSET("7.500 GOLOS").amount.value); - BOOST_REQUIRE(new_alice.sbd_balance.amount.value == ASSET("2.500 GBG").amount.value); + BOOST_CHECK_EQUAL(new_alice.balance.amount.value, ASSET("7.500 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_alice.sbd_balance.amount.value, ASSET("2.500 GBG").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test failure when account does not exist"); @@ -2357,7 +2749,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(missing_object, "authority", "sam")); BOOST_TEST_MESSAGE("--- Test success converting GBG to GOLOS"); op.owner = "bob"; @@ -2367,37 +2760,39 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(new_bob.balance.amount.value == ASSET("3.000 GOLOS").amount.value); - BOOST_REQUIRE(new_bob.sbd_balance.amount.value == ASSET("4.000 GBG").amount.value); + BOOST_CHECK_EQUAL(new_bob.balance.amount.value, ASSET("3.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_bob.sbd_balance.amount.value, ASSET("4.000 GBG").amount.value); auto convert_request = convert_request_idx.find(std::make_tuple(op.owner, op.requestid)); - BOOST_REQUIRE(convert_request != convert_request_idx.end()); - BOOST_REQUIRE(convert_request->owner == op.owner); - BOOST_REQUIRE(convert_request->requestid == op.requestid); - BOOST_REQUIRE(convert_request->amount.amount.value == op.amount.amount.value); - //BOOST_REQUIRE( convert_request->premium == 100000 ); - BOOST_REQUIRE(convert_request->conversion_date == db->head_block_time() + STEEMIT_CONVERSION_DELAY); + BOOST_CHECK(convert_request != convert_request_idx.end()); + BOOST_CHECK_EQUAL(convert_request->owner, op.owner); + BOOST_CHECK_EQUAL(convert_request->requestid, op.requestid); + BOOST_CHECK_EQUAL(convert_request->amount.amount.value, op.amount.amount.value); + //BOOST_CHECK_EQUAL( convert_request->premium, 100000 ); + BOOST_CHECK_EQUAL(convert_request->conversion_date, db->head_block_time() + STEEMIT_CONVERSION_DELAY); BOOST_TEST_MESSAGE("--- Test failure from repeated id"); - op.amount = ASSET("2.000 GOLOS"); + op.amount = ASSET("2.000 GBG"); tx.operations.clear(); tx.signatures.clear(); tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + tx.sign(bob_private_key, db->get_chain_id()); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(object_already_exist, "convert_request", make_convert_request_id("bob", 0)))); - BOOST_REQUIRE(new_bob.balance.amount.value == ASSET("3.000 GOLOS").amount.value); - BOOST_REQUIRE(new_bob.sbd_balance.amount.value == ASSET("4.000 GBG").amount.value); + BOOST_CHECK_EQUAL(new_bob.balance.amount.value, ASSET("3.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(new_bob.sbd_balance.amount.value, ASSET("4.000 GBG").amount.value); convert_request = convert_request_idx.find(std::make_tuple(op.owner, op.requestid)); - BOOST_REQUIRE(convert_request != convert_request_idx.end()); - BOOST_REQUIRE(convert_request->owner == op.owner); - BOOST_REQUIRE(convert_request->requestid == op.requestid); - BOOST_REQUIRE(convert_request->amount.amount.value == ASSET("3.000 GBG").amount.value); - //BOOST_REQUIRE( convert_request->premium == 100000 ); - BOOST_REQUIRE(convert_request->conversion_date == db->head_block_time() + STEEMIT_CONVERSION_DELAY); + BOOST_CHECK(convert_request != convert_request_idx.end()); + BOOST_CHECK_EQUAL(convert_request->owner, op.owner); + BOOST_CHECK_EQUAL(convert_request->requestid, op.requestid); + BOOST_CHECK_EQUAL(convert_request->amount.amount.value, ASSET("3.000 GBG").amount.value); + //BOOST_CHECK_EQUAL( convert_request->premium, 100000 ); + BOOST_CHECK_EQUAL(convert_request->conversion_date, db->head_block_time() + STEEMIT_CONVERSION_DELAY); validate_database(); } FC_LOG_AND_RETHROW() @@ -2406,6 +2801,30 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(limit_order_create_validate) { try { BOOST_TEST_MESSAGE("Testing: limit_order_create_validate"); + limit_order_create_operation op; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.owner = "alice"; + op.amount_to_sell = ASSET("1.000 GOLOS"); + op.min_to_receive = ASSET("1.000 GBG"); + CHECK_OP_VALID(op); + + BOOST_TEST_MESSAGE("--- failed when 'owner' is empty"); + CHECK_PARAM_INVALID(op, owner, ""); + + BOOST_TEST_MESSAGE("--- failed when 'amount_to_sell' is negative"); + CHECK_PARAM_VALIDATION_FAIL(op, amount_to_sell, ASSET_GOLOS(-1), + CHECK_ERROR(invalid_parameter, "price")); + + BOOST_TEST_MESSAGE("--- failed when symbol not GBG or GOLOS"); + CHECK_PARAM_INVALID_LOGIC(op, min_to_receive, ASSET_GESTS(1), + limit_order_must_be_for_golos_gbg_market); + + BOOST_TEST_MESSAGE("--- failed when 'min_to_receive' is negative"); + op.amount_to_sell = ASSET("1.000 GOLOS"); + op.min_to_receive = ASSET("-1.000 GBG"); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "price")); } FC_LOG_AND_RETHROW() } @@ -2428,7 +2847,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); BOOST_TEST_MESSAGE("--- Test failure when no signature."); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test success with account signature"); tx.sign(alice_private_key, db->get_chain_id()); @@ -2436,18 +2856,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_TEST_MESSAGE("--- Test failure with duplicate signature"); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with additional incorrect signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with incorrect signature"); tx.signatures.clear(); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); validate_database(); } @@ -2479,13 +2902,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "bob", "fund", "10.000 GOLOS"))); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("bob", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(bob.balance.amount.value == ASSET("0.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == ASSET("100.0000 GBG").amount.value); + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("0.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("100.0000 GBG").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test failure when amount to receive is 0"); @@ -2496,13 +2921,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(invalid_parameter, "price"))); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("alice", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("1000.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == ASSET("0.000 GBG").amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("1000.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test failure when amount to sell is 0"); @@ -2513,13 +2940,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(invalid_parameter, "price"))); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("alice", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("1000.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == ASSET("0.000 GBG").amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("1000.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test success creating limit order that will not be filled"); @@ -2530,19 +2959,19 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); auto limit_order = limit_order_idx.find(std::make_tuple("alice", op.orderid)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == op.owner); - BOOST_REQUIRE(limit_order->orderid == op.orderid); - BOOST_REQUIRE(limit_order->for_sale == op.amount_to_sell.amount); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, op.owner); + BOOST_CHECK_EQUAL(limit_order->orderid, op.orderid); + BOOST_CHECK_EQUAL(limit_order->for_sale, op.amount_to_sell.amount); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(op.amount_to_sell / op.min_to_receive)); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == ASSET("0.000 GBG").amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test failure creating limit order with duplicate id"); @@ -2552,18 +2981,20 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(object_already_exist, "limit_order", make_limit_order_id("alice", 1)))); limit_order = limit_order_idx.find(std::make_tuple("alice", op.orderid)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == op.owner); - BOOST_REQUIRE(limit_order->orderid == op.orderid); - BOOST_REQUIRE(limit_order->for_sale == 10000); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, op.owner); + BOOST_CHECK_EQUAL(limit_order->orderid, op.orderid); + BOOST_CHECK_EQUAL(limit_order->for_sale, 10000); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(ASSET("10.000 GOLOS"), op.min_to_receive)); - BOOST_REQUIRE(limit_order->get_market() == std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == ASSET("0.000 GBG").amount.value); + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test sucess killing an order that will not be filled"); @@ -2574,13 +3005,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cancelling_not_filled_order))); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("alice", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == ASSET("0.000 GBG").amount.value); + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); BOOST_TEST_MESSAGE("--- Test having a partial match to limit order"); @@ -2596,38 +3029,38 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); auto recent_ops = get_last_operations(1); auto fill_order_op = recent_ops[0].get(); limit_order = limit_order_idx.find(std::make_tuple("alice", 1)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == "alice"); - BOOST_REQUIRE(limit_order->orderid == op.orderid); - BOOST_REQUIRE(limit_order->for_sale == 5000); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, "alice"); + BOOST_CHECK_EQUAL(limit_order->orderid, op.orderid); + BOOST_CHECK_EQUAL(limit_order->for_sale, 5000); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(ASSET("10.000 GOLOS"), ASSET("15.000 GBG"))); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("bob", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("7.500 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("5.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("992.500 GBG").amount.value); - BOOST_REQUIRE(fill_order_op.open_owner == "alice"); - BOOST_REQUIRE(fill_order_op.open_orderid == 1); - BOOST_REQUIRE(fill_order_op.open_pays.amount.value == + BOOST_CHECK_EQUAL(fill_order_op.open_owner, "alice"); + BOOST_CHECK_EQUAL(fill_order_op.open_orderid, 1); + BOOST_CHECK_EQUAL(fill_order_op.open_pays.amount.value, ASSET("5.000 GOLOS").amount.value); - BOOST_REQUIRE(fill_order_op.current_owner == "bob"); - BOOST_REQUIRE(fill_order_op.current_orderid == 1); - BOOST_REQUIRE(fill_order_op.current_pays.amount.value == + BOOST_CHECK_EQUAL(fill_order_op.current_owner, "bob"); + BOOST_CHECK_EQUAL(fill_order_op.current_orderid, 1); + BOOST_CHECK_EQUAL(fill_order_op.current_pays.amount.value, ASSET("7.500 GBG").amount.value); validate_database(); @@ -2639,26 +3072,26 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); limit_order = limit_order_idx.find(std::make_tuple("bob", 1)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == "bob"); - BOOST_REQUIRE(limit_order->orderid == 1); - BOOST_REQUIRE(limit_order->for_sale.value == 7500); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, "bob"); + BOOST_CHECK_EQUAL(limit_order->orderid, 1); + BOOST_CHECK_EQUAL(limit_order->for_sale.value, 7500); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(ASSET("15.000 GBG"), ASSET("10.000 GOLOS"))); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("alice", 1)) == + BOOST_CHECK(limit_order_idx.find(std::make_tuple("alice", 1)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("15.000 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("10.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("977.500 GBG").amount.value); validate_database(); @@ -2672,19 +3105,19 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + GOLOS_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("alice", 3)) == + BOOST_CHECK(limit_order_idx.find(std::make_tuple("alice", 3)) == limit_order_idx.end()); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("bob", 1)) == + BOOST_CHECK(limit_order_idx.find(std::make_tuple("bob", 1)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("985.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("22.500 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("15.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("977.500 GBG").amount.value); validate_database(); @@ -2698,7 +3131,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); op.owner = "bob"; op.orderid = 4; @@ -2708,26 +3141,26 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); limit_order = limit_order_idx.find(std::make_tuple("bob", 4)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("alice", 4)) == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK(limit_order_idx.find(std::make_tuple("alice", 4)) == limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == "bob"); - BOOST_REQUIRE(limit_order->orderid == 4); - BOOST_REQUIRE(limit_order->for_sale.value == 1000); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK_EQUAL(limit_order->seller, "bob"); + BOOST_CHECK_EQUAL(limit_order->orderid, 4); + BOOST_CHECK_EQUAL(limit_order->for_sale.value, 1000); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(ASSET("12.000 GBG"), ASSET("10.000 GOLOS"))); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("975.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("33.500 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("25.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("965.500 GBG").amount.value); validate_database(); @@ -2738,7 +3171,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(can); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- Test filling limit order with better order when partial order is worse."); @@ -2750,7 +3183,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); op.owner = "bob"; op.orderid = 5; @@ -2760,32 +3193,70 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); limit_order = limit_order_idx.find(std::make_tuple("alice", 5)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("bob", 5)) == - limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == "alice"); - BOOST_REQUIRE(limit_order->orderid == 5); - BOOST_REQUIRE(limit_order->for_sale.value == 9091); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK(limit_order_idx.find(std::make_tuple("bob", 5)) == limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, "alice"); + BOOST_CHECK_EQUAL(limit_order->orderid, 5); + BOOST_CHECK_EQUAL(limit_order->for_sale.value, 9091); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(ASSET("20.000 GOLOS"), ASSET("22.000 GBG"))); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("955.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("45.500 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("35.909 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("954.500 GBG").amount.value); validate_database(); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(limit_order_create2_validate) { + try { + BOOST_TEST_MESSAGE("Testing: limit_order_create2_validate"); + limit_order_create2_operation op; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.owner = "alice"; + op.amount_to_sell = ASSET("1.000 GOLOS"); + op.exchange_rate = price(ASSET("1.000 GOLOS"), ASSET("1.000 GBG")); + BOOST_CHECK_NO_THROW(op.validate()); + + BOOST_TEST_MESSAGE("--- failed when 'owner' is empty"); + op.owner = ""; + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "owner")); + + BOOST_TEST_MESSAGE("--- failed when 'exchange_rate' is invalid"); + op.owner = "alice"; + op.exchange_rate = price(ASSET("1.000 GOLOS"), ASSET("1.000 GOLOS")); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "exchange_rate")); + + BOOST_TEST_MESSAGE("--- failed when symbol not GBG or GOLOS"); + op.amount_to_sell = ASSET("1.000000 GESTS"); + op.exchange_rate = price(ASSET("1.000000 GESTS"), ASSET("1.000 GBG")); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(logic_exception, logic_exception::limit_order_must_be_for_golos_gbg_market)); + + BOOST_TEST_MESSAGE("--- failed when zero amount to sell"); + op.amount_to_sell = ASSET("0.000 GOLOS"); + op.exchange_rate = price(ASSET("1.000 GOLOS"), ASSET("1.000 GBG")); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "amount_to_sell")); + + validate_database(); + } + FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_CASE(limit_order_create2_authorities) { try { BOOST_TEST_MESSAGE("Testing: limit_order_create2_authorities"); @@ -2804,26 +3275,30 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); BOOST_TEST_MESSAGE("--- Test failure when no signature."); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test success with account signature"); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, database::skip_transaction_dupe_check); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check)); BOOST_TEST_MESSAGE("--- Test failure with duplicate signature"); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with additional incorrect signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with incorrect signature"); tx.signatures.clear(); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); validate_database(); } @@ -2856,14 +3331,16 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration( db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "bob", "fund", "10.000 GOLOS"))); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("bob", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("0.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("100.0000 GBG").amount.value); validate_database(); @@ -2875,14 +3352,16 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(invalid_parameter, "exchange_rate"))); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("alice", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("1000.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); @@ -2894,14 +3373,16 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(invalid_parameter, "amount_to_sell"))); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("alice", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("1000.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); @@ -2913,19 +3394,19 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); auto limit_order = limit_order_idx.find(std::make_tuple("alice", op.orderid)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == op.owner); - BOOST_REQUIRE(limit_order->orderid == op.orderid); - BOOST_REQUIRE(limit_order->for_sale == op.amount_to_sell.amount); - BOOST_REQUIRE(limit_order->sell_price == op.exchange_rate); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, op.owner); + BOOST_CHECK_EQUAL(limit_order->orderid, op.orderid); + BOOST_CHECK_EQUAL(limit_order->for_sale, op.amount_to_sell.amount); + BOOST_CHECK_EQUAL(limit_order->sell_price, op.exchange_rate); + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); @@ -2936,19 +3417,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(object_already_exist, "limit_order", make_limit_order_id("alice", 1)))); limit_order = limit_order_idx.find(std::make_tuple("alice", op.orderid)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == op.owner); - BOOST_REQUIRE(limit_order->orderid == op.orderid); - BOOST_REQUIRE(limit_order->for_sale == 10000); - BOOST_REQUIRE(limit_order->sell_price == op.exchange_rate); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, op.owner); + BOOST_CHECK_EQUAL(limit_order->orderid, op.orderid); + BOOST_CHECK_EQUAL(limit_order->for_sale, 10000); + BOOST_CHECK_EQUAL(limit_order->sell_price, op.exchange_rate); + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); @@ -2960,14 +3443,16 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cancelling_not_filled_order))); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("alice", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); validate_database(); @@ -2984,38 +3469,38 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); auto recent_ops = get_last_operations(1); auto fill_order_op = recent_ops[0].get(); limit_order = limit_order_idx.find(std::make_tuple("alice", 1)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == "alice"); - BOOST_REQUIRE(limit_order->orderid == op.orderid); - BOOST_REQUIRE(limit_order->for_sale == 5000); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, "alice"); + BOOST_CHECK_EQUAL(limit_order->orderid, op.orderid); + BOOST_CHECK_EQUAL(limit_order->for_sale, 5000); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(ASSET("2.000 GOLOS"), ASSET("3.000 GBG"))); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE( + BOOST_CHECK( limit_order_idx.find(std::make_tuple("bob", op.orderid)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("7.500 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("5.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("992.500 GBG").amount.value); - BOOST_REQUIRE(fill_order_op.open_owner == "alice"); - BOOST_REQUIRE(fill_order_op.open_orderid == 1); - BOOST_REQUIRE(fill_order_op.open_pays.amount.value == + BOOST_CHECK_EQUAL(fill_order_op.open_owner, "alice"); + BOOST_CHECK_EQUAL(fill_order_op.open_orderid, 1); + BOOST_CHECK_EQUAL(fill_order_op.open_pays.amount.value, ASSET("5.000 GOLOS").amount.value); - BOOST_REQUIRE(fill_order_op.current_owner == "bob"); - BOOST_REQUIRE(fill_order_op.current_orderid == 1); - BOOST_REQUIRE(fill_order_op.current_pays.amount.value == + BOOST_CHECK_EQUAL(fill_order_op.current_owner, "bob"); + BOOST_CHECK_EQUAL(fill_order_op.current_orderid, 1); + BOOST_CHECK_EQUAL(fill_order_op.current_pays.amount.value, ASSET("7.500 GBG").amount.value); validate_database(); @@ -3027,26 +3512,26 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); limit_order = limit_order_idx.find(std::make_tuple("bob", 1)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == "bob"); - BOOST_REQUIRE(limit_order->orderid == 1); - BOOST_REQUIRE(limit_order->for_sale.value == 7500); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK_EQUAL(limit_order->seller, "bob"); + BOOST_CHECK_EQUAL(limit_order->orderid, 1); + BOOST_CHECK_EQUAL(limit_order->for_sale.value, 7500); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(ASSET("3.000 GBG"), ASSET("2.000 GOLOS"))); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("alice", 1)) == + BOOST_CHECK(limit_order_idx.find(std::make_tuple("alice", 1)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("990.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("15.000 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("10.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("977.500 GBG").amount.value); validate_database(); @@ -3060,19 +3545,19 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("alice", 3)) == + BOOST_CHECK(limit_order_idx.find(std::make_tuple("alice", 3)) == limit_order_idx.end()); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("bob", 1)) == + BOOST_CHECK(limit_order_idx.find(std::make_tuple("bob", 1)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("985.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("22.500 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("15.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("977.500 GBG").amount.value); validate_database(); @@ -3086,7 +3571,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); op.owner = "bob"; op.orderid = 4; @@ -3096,25 +3581,25 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); limit_order = limit_order_idx.find(std::make_tuple("bob", 4)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("alice", 4)) == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK(limit_order_idx.find(std::make_tuple("alice", 4)) == limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == "bob"); - BOOST_REQUIRE(limit_order->orderid == 4); - BOOST_REQUIRE(limit_order->for_sale.value == 1000); - BOOST_REQUIRE(limit_order->sell_price == op.exchange_rate); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->seller, "bob"); + BOOST_CHECK_EQUAL(limit_order->orderid, 4); + BOOST_CHECK_EQUAL(limit_order->for_sale.value, 1000); + BOOST_CHECK_EQUAL(limit_order->sell_price, op.exchange_rate); + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("975.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("33.500 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("25.000 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("965.500 GBG").amount.value); validate_database(); @@ -3125,7 +3610,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(can); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- Test filling limit order with better order when partial order is worse."); @@ -3137,7 +3622,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); op.owner = "bob"; op.orderid = 5; @@ -3147,26 +3632,26 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); limit_order = limit_order_idx.find(std::make_tuple("alice", 5)); - BOOST_REQUIRE(limit_order != limit_order_idx.end()); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("bob", 5)) == + BOOST_CHECK(limit_order != limit_order_idx.end()); + BOOST_CHECK(limit_order_idx.find(std::make_tuple("bob", 5)) == limit_order_idx.end()); - BOOST_REQUIRE(limit_order->seller == "alice"); - BOOST_REQUIRE(limit_order->orderid == 5); - BOOST_REQUIRE(limit_order->for_sale.value == 9091); - BOOST_REQUIRE(limit_order->sell_price == + BOOST_CHECK_EQUAL(limit_order->seller, "alice"); + BOOST_CHECK_EQUAL(limit_order->orderid, 5); + BOOST_CHECK_EQUAL(limit_order->for_sale.value, 9091); + BOOST_CHECK_EQUAL(limit_order->sell_price, price(ASSET("1.000 GOLOS"), ASSET("1.100 GBG"))); - BOOST_REQUIRE(limit_order->get_market() == + BOOST_CHECK_EQUAL(limit_order->get_market(), std::make_pair(SBD_SYMBOL, STEEM_SYMBOL)); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("955.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("45.500 GBG").amount.value); - BOOST_REQUIRE(bob.balance.amount.value == + BOOST_CHECK_EQUAL(bob.balance.amount.value, ASSET("35.909 GOLOS").amount.value); - BOOST_REQUIRE(bob.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(bob.sbd_balance.amount.value, ASSET("954.500 GBG").amount.value); validate_database(); } @@ -3176,6 +3661,17 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(limit_order_cancel_validate) { try { BOOST_TEST_MESSAGE("Testing: limit_order_cancel_validate"); + limit_order_cancel_operation op; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.owner = "alice"; + op.orderid = 1; + CHECK_OP_VALID(op); + + BOOST_TEST_MESSAGE("--- failure when owner is empty"); + CHECK_PARAM_INVALID(op, owner, ""); + + validate_database(); } FC_LOG_AND_RETHROW() } @@ -3198,7 +3694,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration( db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); limit_order_cancel_operation op; op.owner = "alice"; @@ -3209,26 +3705,30 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); BOOST_TEST_MESSAGE("--- Test failure when no signature."); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); BOOST_TEST_MESSAGE("--- Test success with account signature"); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, database::skip_transaction_dupe_check); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check)); BOOST_TEST_MESSAGE("--- Test failure with duplicate signature"); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_duplicate_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_duplicate_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with additional incorrect signature"); tx.signatures.clear(); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_irrelevant_sig, 0)); BOOST_TEST_MESSAGE("--- Test failure with incorrect signature"); tx.signatures.clear(); tx.sign(alice_post_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, database::skip_transaction_dupe_check), tx_missing_active_auth); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, database::skip_transaction_dupe_check), + CHECK_ERROR(tx_missing_active_auth, 0)); validate_database(); } @@ -3255,7 +3755,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.set_expiration( db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "limit_order", make_limit_order_id("alice", 5)))); BOOST_TEST_MESSAGE("--- Test cancel order"); @@ -3268,22 +3770,22 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.signatures.clear(); tx.operations.push_back(create); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("alice", 5)) != + BOOST_CHECK(limit_order_idx.find(std::make_tuple("alice", 5)) != limit_order_idx.end()); tx.operations.clear(); tx.signatures.clear(); tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(limit_order_idx.find(std::make_tuple("alice", 5)) == + BOOST_CHECK(limit_order_idx.find(std::make_tuple("alice", 5)) == limit_order_idx.end()); - BOOST_REQUIRE(alice.balance.amount.value == + BOOST_CHECK_EQUAL(alice.balance.amount.value, ASSET("10.000 GOLOS").amount.value); - BOOST_REQUIRE(alice.sbd_balance.amount.value == + BOOST_CHECK_EQUAL(alice.sbd_balance.amount.value, ASSET("0.000 GBG").amount.value); } FC_LOG_AND_RETHROW() @@ -3310,15 +3812,144 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) FC_LOG_AND_RETHROW() } +//------------------------------------------------------------- + BOOST_AUTO_TEST_SUITE(recovery) + +#define TRIVIAL_AUTHORITY authority(0, "acc0", 1) +#define IMPOSSIBLE_AUTHORITY authority(3, "acc1", 1, "acc2", 1) +#define INVALID_AUTHORITY authority(2, "good1", 1, "2bad", 1) + + BOOST_AUTO_TEST_SUITE(request_account_recovery) + + BOOST_AUTO_TEST_CASE(request_account_recovery_validate) { try { + BOOST_TEST_MESSAGE("Testing: request_account_recovery_validate"); + request_account_recovery_operation op; + op.recovery_account = "alice"; + op.account_to_recover = "bob"; + op.new_owner_authority = authority(1, "sam", 1); + BOOST_TEST_MESSAGE("--- success on valid params"); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, new_owner_authority, authority(3, "one", 1, "two", 2)); + CHECK_PARAM_VALID(op, new_owner_authority, authority()); + CHECK_PARAM_VALID(op, new_owner_authority, TRIVIAL_AUTHORITY); + CHECK_PARAM_VALID(op, new_owner_authority, IMPOSSIBLE_AUTHORITY); + + BOOST_TEST_MESSAGE("--- fail when recovery_account or account_to_recover invalid"); + CHECK_PARAM_INVALID(op, recovery_account, ""); + CHECK_PARAM_INVALID(op, account_to_recover, ""); + + BOOST_TEST_MESSAGE("--- fail when new_owner_authority invalid"); + // TODO: create separate test for authority::validate() + CHECK_PARAM_INVALID(op, new_owner_authority, authority(1, "no", 1)); + CHECK_PARAM_INVALID(op, new_owner_authority, authority(2, "good", 1, "no", 1)); + CHECK_PARAM_INVALID(op, new_owner_authority, INVALID_AUTHORITY); + + } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_CASE(request_account_recovery_authorities) { try { + BOOST_TEST_MESSAGE("Testing: request_account_recovery_authorities"); + request_account_recovery_operation op; + op.recovery_account = "alice"; + op.account_to_recover = "bob"; + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"alice"}), account_name_set()); + } FC_LOG_AND_RETHROW() } + + // tested in account_recovery and change_recovery_account test cases + // BOOST_AUTO_TEST_CASE(request_account_recovery_apply) { try { + // } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_SUITE_END() // request_account_recovery + + + BOOST_AUTO_TEST_SUITE(recover_account) + + BOOST_AUTO_TEST_CASE(recover_account_validate) { try { + BOOST_TEST_MESSAGE("Testing: recover_account_validate"); + recover_account_operation op; + op.account_to_recover = "bob"; + op.new_owner_authority = authority(1, "alice", 1); + op.recent_owner_authority = authority(1, "sam", 1); + // TODO: key-based authorities + BOOST_TEST_MESSAGE("--- success on valid params"); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, recent_owner_authority, authority()); + CHECK_PARAM_VALID(op, recent_owner_authority, TRIVIAL_AUTHORITY); + + BOOST_TEST_MESSAGE("--- fail when account_to_recover is invalid"); + CHECK_PARAM_INVALID(op, account_to_recover, ""); + + BOOST_TEST_MESSAGE("--- fail when new_owner_authority is bad"); + CHECK_PARAM_INVALID_LOGIC(op, new_owner_authority, authority(1, "sam", 1), cannot_set_recent_recovery); + CHECK_PARAM_INVALID(op, new_owner_authority, authority()); + CHECK_PARAM_INVALID(op, new_owner_authority, TRIVIAL_AUTHORITY); + CHECK_PARAM_INVALID(op, new_owner_authority, IMPOSSIBLE_AUTHORITY); + CHECK_PARAM_INVALID(op, new_owner_authority, INVALID_AUTHORITY); + + BOOST_TEST_MESSAGE("--- fail when recent_owner_authority is bad"); + CHECK_PARAM_INVALID(op, recent_owner_authority, IMPOSSIBLE_AUTHORITY); + CHECK_PARAM_INVALID(op, recent_owner_authority, INVALID_AUTHORITY); + + } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_CASE(recover_account_authorities) { try { + BOOST_TEST_MESSAGE("Testing: recover_account_authorities"); + recover_account_operation op; + op.account_to_recover = "alice"; + op.new_owner_authority = authority(1, "bob", 1); + op.recent_owner_authority = authority(1, "sam", 1); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set(), account_name_set()); + const auto bob_and_sam = vector{op.new_owner_authority, op.recent_owner_authority}; + vector req; + op.get_required_authorities(req); + BOOST_CHECK_EQUAL(bob_and_sam, req); + // TODO: maybe here can be some more complex checks (like multisig) + } FC_LOG_AND_RETHROW() } + + // tested in account_recovery and change_recovery_account test cases + // BOOST_AUTO_TEST_CASE(recover_account_apply) { try { + // } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_SUITE_END() // recover_account + + + BOOST_AUTO_TEST_SUITE(change_recovery_account) + + BOOST_AUTO_TEST_CASE(change_recovery_account_validate) { try { + BOOST_TEST_MESSAGE("Testing: change_recovery_account_validate"); + change_recovery_account_operation op; + op.account_to_recover = "alice"; + op.new_recovery_account = "bob"; + BOOST_TEST_MESSAGE("--- success on valid params"); + CHECK_OP_VALID(op); + + BOOST_TEST_MESSAGE("--- fail when account_to_recover or new_recovery_account is invalid"); + CHECK_PARAM_INVALID(op, account_to_recover, ""); + CHECK_PARAM_INVALID(op, new_recovery_account, ""); + + } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_CASE(change_recovery_account_authorities) { try { + BOOST_TEST_MESSAGE("Testing: change_recovery_account_authorities"); + change_recovery_account_operation op; + op.account_to_recover = "alice"; + op.new_recovery_account = "bob"; + CHECK_OP_AUTHS(op, account_name_set({"alice"}), account_name_set(), account_name_set()); + } FC_LOG_AND_RETHROW() } + + // tested in change_recovery_account test cases + // BOOST_AUTO_TEST_CASE(change_recovery_account_apply) { try { + // } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_SUITE_END() // change_recovery_account + + BOOST_AUTO_TEST_CASE(account_recovery) { try { BOOST_TEST_MESSAGE("Testing: account recovery"); - ACTORS((alice)); fund("alice", 1000000); - BOOST_TEST_MESSAGE("Creating account bob with alice"); - + BOOST_TEST_MESSAGE("--- Creating account bob with alice"); account_create_operation acc_create; acc_create.fee = ASSET("10.000 GOLOS"); acc_create.creator = "alice"; @@ -3329,345 +3960,270 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) acc_create.memo_key = generate_private_key("bob_memo").get_public_key(); acc_create.json_metadata = ""; - signed_transaction tx; - tx.operations.push_back(acc_create); - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - const auto &bob_auth = db->get("bob"); - BOOST_REQUIRE(bob_auth.owner == acc_create.owner); - - - BOOST_TEST_MESSAGE("Changing bob's owner authority"); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, acc_create)); + const auto& bob_auth = db->get("bob"); + BOOST_CHECK_EQUAL(bob_auth.owner, acc_create.owner); + BOOST_TEST_MESSAGE("--- Changing bob's owner authority"); account_update_operation acc_update; acc_update.account = "bob"; acc_update.owner = authority(1, generate_private_key("bad_key").get_public_key(), 1); acc_update.memo_key = acc_create.memo_key; acc_update.json_metadata = ""; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, generate_private_key("bob_owner"), acc_update)); + BOOST_CHECK_EQUAL(bob_auth.owner, *acc_update.owner); - tx.operations.clear(); - tx.signatures.clear(); - - tx.operations.push_back(acc_update); - tx.sign(generate_private_key("bob_owner"), db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(bob_auth.owner == *acc_update.owner); - - - BOOST_TEST_MESSAGE("Creating recover request for bob with alice"); - + BOOST_TEST_MESSAGE("--- Creating recover request for bob with alice"); request_account_recovery_operation request; request.recovery_account = "alice"; request.account_to_recover = "bob"; request.new_owner_authority = authority(1, generate_private_key("new_key").get_public_key(), 1); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, request)); + BOOST_CHECK_EQUAL(bob_auth.owner, *acc_update.owner); - tx.operations.clear(); - tx.signatures.clear(); - - tx.operations.push_back(request); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(bob_auth.owner == *acc_update.owner); - - - BOOST_TEST_MESSAGE("Recovering bob's account with original owner auth and new secret"); - + BOOST_TEST_MESSAGE("--- Recovering bob's account with original owner auth and new secret"); generate_blocks(db->head_block_time() + STEEMIT_OWNER_UPDATE_LIMIT); - recover_account_operation recover; recover.account_to_recover = "bob"; recover.new_owner_authority = request.new_owner_authority; recover.recent_owner_authority = acc_create.owner; - tx.operations.clear(); - tx.signatures.clear(); - + tx.clear(); tx.operations.push_back(recover); tx.sign(generate_private_key("bob_owner"), db->get_chain_id()); tx.sign(generate_private_key("new_key"), db->get_chain_id()); - db->push_transaction(tx, 0); - const auto &owner1 = db->get("bob").owner; + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - BOOST_REQUIRE(owner1 == recover.new_owner_authority); - - - BOOST_TEST_MESSAGE("Creating new recover request for a bogus key"); + const auto& owner1 = db->get("bob").owner; + BOOST_CHECK_EQUAL(owner1, recover.new_owner_authority); + BOOST_TEST_MESSAGE("--- Creating new recover request for a bogus key"); request.new_owner_authority = authority(1, generate_private_key("foo bar").get_public_key(), 1); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, request)); - tx.operations.clear(); - tx.signatures.clear(); - - tx.operations.push_back(request); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - - BOOST_TEST_MESSAGE("Testing failure when bob does not have new authority"); - - generate_blocks(db->head_block_time() + STEEMIT_OWNER_UPDATE_LIMIT + - fc::seconds(STEEMIT_BLOCK_INTERVAL)); - + BOOST_TEST_MESSAGE("--- Testing failure when bob does not have new authority"); + generate_blocks(db->head_block_time() + STEEMIT_OWNER_UPDATE_LIMIT + fc::seconds(STEEMIT_BLOCK_INTERVAL)); recover.new_owner_authority = authority(1, generate_private_key("idontknow").get_public_key(), 1); - tx.operations.clear(); - tx.signatures.clear(); - + tx.clear(); tx.operations.push_back(recover); tx.sign(generate_private_key("bob_owner"), db->get_chain_id()); tx.sign(generate_private_key("idontknow"), db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - const auto &owner2 = db->get("bob").owner; - BOOST_REQUIRE(owner2 == - authority(1, generate_private_key("new_key").get_public_key(), 1)); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::authority_does_not_match_request))); + const auto& owner2 = db->get("bob").owner; + BOOST_CHECK_EQUAL(owner2, authority(1, generate_private_key("new_key").get_public_key(), 1)); - BOOST_TEST_MESSAGE("Testing failure when bob does not have old authority"); - + BOOST_TEST_MESSAGE("--- Testing failure when bob does not have old authority"); recover.recent_owner_authority = authority(1, generate_private_key("idontknow").get_public_key(), 1); recover.new_owner_authority = authority(1, generate_private_key("foo bar").get_public_key(), 1); - tx.operations.clear(); - tx.signatures.clear(); - + tx.clear(); tx.operations.push_back(recover); tx.sign(generate_private_key("foo bar"), db->get_chain_id()); tx.sign(generate_private_key("idontknow"), db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - const auto &owner3 = db->get("bob").owner; - BOOST_REQUIRE(owner3 == - authority(1, generate_private_key("new_key").get_public_key(), 1)); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::no_recent_authority_in_history))); + const auto& owner3 = db->get("bob").owner; + BOOST_CHECK_EQUAL(owner3, authority(1, generate_private_key("new_key").get_public_key(), 1)); - BOOST_TEST_MESSAGE("Testing using the same old owner auth again for recovery"); - + BOOST_TEST_MESSAGE("--- Testing using the same old owner auth again for recovery"); recover.recent_owner_authority = authority(1, generate_private_key("bob_owner").get_public_key(), 1); recover.new_owner_authority = authority(1, generate_private_key("foo bar").get_public_key(), 1); - tx.operations.clear(); - tx.signatures.clear(); - + tx.clear(); tx.operations.push_back(recover); tx.sign(generate_private_key("bob_owner"), db->get_chain_id()); tx.sign(generate_private_key("foo bar"), db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - const auto &owner4 = db->get("bob").owner; - BOOST_REQUIRE(owner4 == recover.new_owner_authority); - - BOOST_TEST_MESSAGE("Creating a recovery request that will expire"); + const auto& owner4 = db->get("bob").owner; + BOOST_CHECK_EQUAL(owner4, recover.new_owner_authority); + BOOST_TEST_MESSAGE("--- Creating a recovery request that will expire"); request.new_owner_authority = authority(1, generate_private_key("expire").get_public_key(), 1); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, request)); - tx.operations.clear(); - tx.signatures.clear(); - - tx.operations.push_back(request); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - const auto &request_idx = db->get_index().indices(); + const auto& request_idx = db->get_index().indices(); auto req_itr = request_idx.begin(); - - BOOST_REQUIRE(req_itr->account_to_recover == "bob"); - BOOST_REQUIRE(req_itr->new_owner_authority == - authority(1, generate_private_key("expire").get_public_key(), 1)); - BOOST_REQUIRE(req_itr->expires == db->head_block_time() + - STEEMIT_ACCOUNT_RECOVERY_REQUEST_EXPIRATION_PERIOD); + BOOST_CHECK_EQUAL(req_itr->account_to_recover, "bob"); + BOOST_CHECK_EQUAL(req_itr->new_owner_authority, + authority(1, generate_private_key("expire").get_public_key(), 1)); + BOOST_CHECK_EQUAL(req_itr->expires, + db->head_block_time() + STEEMIT_ACCOUNT_RECOVERY_REQUEST_EXPIRATION_PERIOD); auto expires = req_itr->expires; ++req_itr; - BOOST_REQUIRE(req_itr == request_idx.end()); - - generate_blocks(time_point_sec( - expires - STEEMIT_BLOCK_INTERVAL), true); - - const auto &new_request_idx = db->get_index().indices(); - BOOST_REQUIRE(new_request_idx.begin() != new_request_idx.end()); + BOOST_CHECK(req_itr == request_idx.end()); + generate_blocks(time_point_sec(expires - STEEMIT_BLOCK_INTERVAL), true); + const auto& new_request_idx = db->get_index().indices(); + BOOST_CHECK(new_request_idx.begin() != new_request_idx.end()); generate_block(); - - BOOST_REQUIRE(new_request_idx.begin() == new_request_idx.end()); + BOOST_CHECK(new_request_idx.begin() == new_request_idx.end()); recover.new_owner_authority = authority(1, generate_private_key("expire").get_public_key(), 1); recover.recent_owner_authority = authority(1, generate_private_key("bob_owner").get_public_key(), 1); - - tx.operations.clear(); - tx.signatures.clear(); - + tx.clear(); tx.operations.push_back(recover); - tx.set_expiration(db->head_block_time()); + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(generate_private_key("expire"), db->get_chain_id()); tx.sign(generate_private_key("bob_owner"), db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - const auto &owner5 = db->get("bob").owner; - BOOST_REQUIRE(owner5 == - authority(1, generate_private_key("foo bar").get_public_key(), 1)); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::no_active_recovery_request))); - BOOST_TEST_MESSAGE("Expiring owner authority history"); + const auto& owner5 = db->get("bob").owner; + BOOST_CHECK_EQUAL(owner5, authority(1, generate_private_key("foo bar").get_public_key(), 1)); + BOOST_TEST_MESSAGE("--- Expiring owner authority history"); acc_update.owner = authority(1, generate_private_key("new_key").get_public_key(), 1); - - tx.operations.clear(); - tx.signatures.clear(); - - tx.operations.push_back(acc_update); - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(generate_private_key("foo bar"), db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, generate_private_key("foo bar"), acc_update)); generate_blocks(db->head_block_time() + - (STEEMIT_OWNER_AUTH_RECOVERY_PERIOD - - STEEMIT_ACCOUNT_RECOVERY_REQUEST_EXPIRATION_PERIOD)); + (STEEMIT_OWNER_AUTH_RECOVERY_PERIOD - STEEMIT_ACCOUNT_RECOVERY_REQUEST_EXPIRATION_PERIOD)); generate_block(); - request.new_owner_authority = authority(1, generate_private_key("last key").get_public_key(), 1); - - tx.operations.clear(); - tx.signatures.clear(); - - tx.operations.push_back(request); - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, request)); recover.new_owner_authority = request.new_owner_authority; recover.recent_owner_authority = authority(1, generate_private_key("bob_owner").get_public_key(), 1); - - tx.operations.clear(); - tx.signatures.clear(); - + tx.clear(); tx.operations.push_back(recover); - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(generate_private_key("bob_owner"), db->get_chain_id()); tx.sign(generate_private_key("last key"), db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - const auto &owner6 = db->get("bob").owner; - BOOST_REQUIRE(owner6 == - authority(1, generate_private_key("new_key").get_public_key(), 1)); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::no_recent_authority_in_history))); - recover.recent_owner_authority = authority(1, generate_private_key("foo bar").get_public_key(), 1); - - tx.operations.clear(); - tx.signatures.clear(); + const auto& owner6 = db->get("bob").owner; + BOOST_CHECK_EQUAL(owner6, authority(1, generate_private_key("new_key").get_public_key(), 1)); + recover.recent_owner_authority = authority(1, generate_private_key("foo bar").get_public_key(), 1); + tx.clear(); tx.operations.push_back(recover); - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(generate_private_key("foo bar"), db->get_chain_id()); tx.sign(generate_private_key("last key"), db->get_chain_id()); - db->push_transaction(tx, 0); - const auto &owner7 = db->get("bob").owner; - BOOST_REQUIRE(owner7 == - authority(1, generate_private_key("last key").get_public_key(), 1)); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); + + const auto& owner7 = db->get("bob").owner; + BOOST_CHECK_EQUAL(owner7, authority(1, generate_private_key("last key").get_public_key(), 1)); } FC_LOG_AND_RETHROW() } - BOOST_AUTO_TEST_CASE(change_recovery_account) { + BOOST_AUTO_TEST_CASE(change_recovery_account_apply) { try { + using fc::ecc::private_key; BOOST_TEST_MESSAGE("Testing change_recovery_account_operation"); + ACTORS((alice)(sam)) - ACTORS((alice)(bob)(sam)(tyler)) - - auto change_recovery_account = [&](const std::string &account_to_recover, const std::string &new_recovery_account) { + auto change_recovery_account = [&](const string& account_to_recover, const string& new_recovery_account) { change_recovery_account_operation op; op.account_to_recover = account_to_recover; op.new_recovery_account = new_recovery_account; - signed_transaction tx; - tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + - STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + push_tx_with_ops(tx, alice_private_key, op); }; - auto recover_account = [&](const std::string &account_to_recover, const fc::ecc::private_key &new_owner_key, const fc::ecc::private_key &recent_owner_key) { + auto recover_account = [&]( + const string& account_to_recover, + const private_key& new_owner_key, + const private_key& recent_owner_key + ) { recover_account_operation op; op.account_to_recover = account_to_recover; op.new_owner_authority = authority(1, public_key_type(new_owner_key.get_public_key()), 1); op.recent_owner_authority = authority(1, public_key_type(recent_owner_key.get_public_key()), 1); - signed_transaction tx; tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + - STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); tx.sign(recent_owner_key, db->get_chain_id()); // only Alice -> throw - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), CHECK_ERROR(tx_missing_other_auth, 0)); tx.signatures.clear(); tx.sign(new_owner_key, db->get_chain_id()); // only Sam -> throw - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx, 0), CHECK_ERROR(tx_missing_other_auth, 0)); tx.sign(recent_owner_key, db->get_chain_id()); // Alice+Sam -> OK db->push_transaction(tx, 0); }; - auto request_account_recovery = [&](const std::string &recovery_account, const fc::ecc::private_key &recovery_account_key, const std::string &account_to_recover, const public_key_type &new_owner_key) { + auto request_account_recovery = [&]( + const string& recovery_account, + const private_key& recovery_account_key, + const string& account_to_recover, + const public_key_type& new_owner_key + ) { request_account_recovery_operation op; op.recovery_account = recovery_account; op.account_to_recover = account_to_recover; op.new_owner_authority = authority(1, new_owner_key, 1); - signed_transaction tx; - tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + - STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(recovery_account_key, db->get_chain_id()); - db->push_transaction(tx, 0); + push_tx_with_ops(tx, recovery_account_key, op); }; - auto change_owner = [&](const std::string &account, const fc::ecc::private_key &old_private_key, const public_key_type &new_public_key) { + auto change_owner = [&]( + const string& account, + const private_key& old_private_key, + const public_key_type& new_public_key + ) { account_update_operation op; op.account = account; op.owner = authority(1, new_public_key, 1); - signed_transaction tx; - tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + - STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(old_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, old_private_key, op)); }; - // if either/both users do not exist, we shouldn't allow it - STEEMIT_REQUIRE_THROW(change_recovery_account("alice", "nobody"), fc::exception); - STEEMIT_REQUIRE_THROW(change_recovery_account("haxer", "sam"), fc::exception); - STEEMIT_REQUIRE_THROW(change_recovery_account("haxer", "nobody"), fc::exception); - change_recovery_account("alice", "sam"); + BOOST_TEST_MESSAGE("--- if either/both users do not exist, we shouldn't allow it"); + GOLOS_CHECK_ERROR_PROPS(change_recovery_account("alice", "nobody"), + CHECK_ERROR(tx_invalid_operation, 0, CHECK_ERROR(missing_object, "account", "nobody"))); + GOLOS_CHECK_ERROR_PROPS(change_recovery_account("haxer", "sam"), + CHECK_ERROR(missing_object, "authority", "haxer")); + GOLOS_CHECK_ERROR_PROPS(change_recovery_account("haxer", "nobody"), + CHECK_ERROR(missing_object, "authority", "haxer")); + BOOST_CHECK_NO_THROW(change_recovery_account("alice", "sam")); fc::ecc::private_key alice_priv1 = fc::ecc::private_key::regenerate(fc::sha256::hash("alice_k1")); fc::ecc::private_key alice_priv2 = fc::ecc::private_key::regenerate(fc::sha256::hash("alice_k2")); public_key_type alice_pub1 = public_key_type(alice_priv1.get_public_key()); generate_blocks( - db->head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD - - fc::seconds(STEEMIT_BLOCK_INTERVAL), true); - // cannot request account recovery until recovery account is approved - STEEMIT_REQUIRE_THROW(request_account_recovery("sam", sam_private_key, "alice", alice_pub1), fc::exception); + db->head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD - fc::seconds(STEEMIT_BLOCK_INTERVAL), + true); + + BOOST_TEST_MESSAGE("--- cannot request account recovery until recovery account is approved"); + GOLOS_CHECK_ERROR_PROPS(request_account_recovery("sam", sam_private_key, "alice", alice_pub1), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cannot_recover_if_not_partner))); generate_blocks(1); - // cannot finish account recovery until requested - STEEMIT_REQUIRE_THROW(recover_account("alice", alice_priv1, alice_private_key), fc::exception); + + BOOST_TEST_MESSAGE("--- cannot finish account recovery until requested"); + GOLOS_CHECK_ERROR_PROPS(recover_account("alice", alice_priv1, alice_private_key), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::no_active_recovery_request))); // do the request - request_account_recovery("sam", sam_private_key, "alice", alice_pub1); - // can't recover with the current owner key - STEEMIT_REQUIRE_THROW(recover_account("alice", alice_priv1, alice_private_key), fc::exception); - // unless we change it! - change_owner("alice", alice_private_key, public_key_type(alice_priv2.get_public_key())); - recover_account("alice", alice_priv1, alice_private_key); + BOOST_CHECK_NO_THROW(request_account_recovery("sam", sam_private_key, "alice", alice_pub1)); + + BOOST_TEST_MESSAGE("--- can't recover with the current owner key"); + GOLOS_CHECK_ERROR_PROPS(recover_account("alice", alice_priv1, alice_private_key), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::no_recent_authority_in_history))); + + BOOST_TEST_MESSAGE("--- success after we change it!"); + BOOST_CHECK_NO_THROW(change_owner("alice", alice_private_key, public_key_type(alice_priv2.get_public_key()))); + BOOST_CHECK_NO_THROW(recover_account("alice", alice_priv1, alice_private_key)); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() // recovery //#define CALCULATE_NONCES @@ -3928,6 +4484,46 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_SUITE(prove_authority) + // Technically it can be called, but will fail early due disabled challenge_authority_operation + BOOST_AUTO_TEST_CASE(prove_authority_validate) { try { + BOOST_TEST_MESSAGE("Testing: prove_authority_validate"); + prove_authority_operation op; + op.challenged = "bob"; + CHECK_OP_VALID(op); + CHECK_PARAM_INVALID(op, challenged, ""); + } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_CASE(prove_authority_authorities) { try { + BOOST_TEST_MESSAGE("Testing: prove_authority_authorities"); + prove_authority_operation op; + op.challenged = "bob"; + op.require_owner = true; + CHECK_OP_AUTHS(op, account_name_set({"bob"}), account_name_set(), account_name_set()); + op.require_owner = false; + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"bob"}), account_name_set()); + } FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_CASE(prove_authority_apply) { try { + BOOST_TEST_MESSAGE("Testing: prove_authority_apply"); + ACTOR(bob) + prove_authority_operation op; + op.challenged = "bob"; + signed_transaction tx; + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::account_is_not_challeneged))); + validate_database(); + } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() // prove_authority + + +//------------------------------------------------------------- + BOOST_AUTO_TEST_SUITE(escrow) + + BOOST_AUTO_TEST_SUITE(escrow_transfer) + BOOST_AUTO_TEST_CASE(escrow_transfer_validate) { try { BOOST_TEST_MESSAGE("Testing: escrow_transfer_validate"); @@ -3943,54 +4539,40 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.json_meta = ""; op.ratification_deadline = db->head_block_time() + 100; op.escrow_expiration = db->head_block_time() + 200; - - BOOST_TEST_MESSAGE("--- failure when sbd symbol != SBD"); - op.sbd_amount.symbol = STEEM_SYMBOL; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - BOOST_TEST_MESSAGE("--- failure when steem symbol != STEEM"); - op.sbd_amount.symbol = SBD_SYMBOL; - op.steem_amount.symbol = SBD_SYMBOL; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - BOOST_TEST_MESSAGE("--- failure when fee symbol != SBD and fee symbol != STEEM"); - op.steem_amount.symbol = STEEM_SYMBOL; - op.fee.symbol = VESTS_SYMBOL; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + BOOST_TEST_MESSAGE("--- success on valid parameters"); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, sbd_amount, ASSET_GBG(0)); + CHECK_PARAM_VALID(op, steem_amount, ASSET_GOLOS(0)); + CHECK_PARAM_VALID(op, fee, ASSET_GOLOS(0)); + CHECK_PARAM_VALID(op, fee, ASSET_GBG(0)); + CHECK_PARAM_VALID(op, fee, ASSET_GBG(1)); + + BOOST_TEST_MESSAGE("--- failure when asset symbols invalid"); + CHECK_PARAM_INVALID(op, sbd_amount, ASSET_GOLOS(1)); + CHECK_PARAM_INVALID(op, sbd_amount, ASSET_GESTS(1)); + CHECK_PARAM_INVALID(op, sbd_amount, ASSET("1.00 BAD")); + CHECK_PARAM_INVALID(op, steem_amount, ASSET_GBG(1)); + CHECK_PARAM_INVALID(op, steem_amount, ASSET_GESTS(1)); + CHECK_PARAM_INVALID(op, steem_amount, ASSET("1.00 BAD")); + CHECK_PARAM_INVALID(op, fee, ASSET_GESTS(1)); + CHECK_PARAM_INVALID(op, fee, ASSET("1.00 BAD")); BOOST_TEST_MESSAGE("--- failure when sbd == 0 and steem == 0"); - op.fee.symbol = STEEM_SYMBOL; op.sbd_amount.amount = 0; - op.steem_amount.amount = 0; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - BOOST_TEST_MESSAGE("--- failure when sbd < 0"); - op.sbd_amount.amount = -100; - op.steem_amount.amount = 1000; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + CHECK_PARAM_INVALID_LOGIC(op, steem_amount, ASSET_GOLOS(0), escrow_no_amount_set); - BOOST_TEST_MESSAGE("--- failure when steem < 0"); - op.sbd_amount.amount = 1000; - op.steem_amount.amount = -100; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - BOOST_TEST_MESSAGE("--- failure when fee < 0"); - op.steem_amount.amount = 1000; - op.fee.amount = -100; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + BOOST_TEST_MESSAGE("--- failure when asset amount < 0"); + op.sbd_amount.amount = 0; + CHECK_PARAM_INVALID(op, fee, ASSET_GBG(-1)); + CHECK_PARAM_INVALID(op, fee, ASSET_GOLOS(-1)); + CHECK_PARAM_INVALID(op, sbd_amount, ASSET_GBG(-1)); + CHECK_PARAM_INVALID(op, steem_amount, ASSET_GOLOS(-1)); BOOST_TEST_MESSAGE("--- failure when ratification deadline == escrow expiration"); - op.fee.amount = 100; - op.ratification_deadline = op.escrow_expiration; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + CHECK_PARAM_INVALID_LOGIC(op, ratification_deadline, op.escrow_expiration, escrow_wrong_time_limits); BOOST_TEST_MESSAGE("--- failure when ratification deadline > escrow expiration"); - op.ratification_deadline = op.escrow_expiration + 100; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - BOOST_TEST_MESSAGE("--- success"); - op.ratification_deadline = op.escrow_expiration - 100; - op.validate(); + CHECK_PARAM_INVALID_LOGIC(op, ratification_deadline, op.escrow_expiration + 1, escrow_wrong_time_limits); } FC_LOG_AND_RETHROW() } @@ -3998,7 +4580,6 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(escrow_transfer_authorities) { try { BOOST_TEST_MESSAGE("Testing: escrow_transfer_authorities"); - escrow_transfer_operation op; op.from = "alice"; op.to = "bob"; @@ -4010,19 +4591,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.json_meta = ""; op.ratification_deadline = db->head_block_time() + 100; op.escrow_expiration = db->head_block_time() + 200; - - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_active_authorities(auths); - expected.insert("alice"); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"alice"}), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -4030,9 +4599,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(escrow_transfer_apply) { try { BOOST_TEST_MESSAGE("Testing: escrow_transfer_apply"); - ACTORS((alice)(bob)(sam)) - fund("alice", 10000); escrow_transfer_operation op; @@ -4049,106 +4616,94 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_TEST_MESSAGE("--- failure when from cannot cover sbd amount"); signed_transaction tx; - tx.operations.push_back(op); - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "alice", "fund", "1.000 GBG"))); BOOST_TEST_MESSAGE("--- falure when from cannot cover amount + fee"); op.sbd_amount.amount = 0; op.steem_amount.amount = 10000; - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "alice", "fund", "10.100 GOLOS"))); BOOST_TEST_MESSAGE("--- failure when ratification deadline is in the past"); op.steem_amount.amount = 1000; op.ratification_deadline = db->head_block_time() - 200; - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_time_in_past))); BOOST_TEST_MESSAGE("--- failure when expiration is in the past"); op.escrow_expiration = db->head_block_time() - 100; - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_time_in_past))); BOOST_TEST_MESSAGE("--- success"); op.ratification_deadline = db->head_block_time() + 100; op.escrow_expiration = db->head_block_time() + 200; - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - auto alice_steem_balance = alice.balance - op.steem_amount - op.fee; auto alice_sbd_balance = alice.sbd_balance - op.sbd_amount; auto bob_steem_balance = bob.balance; auto bob_sbd_balance = bob.sbd_balance; auto sam_steem_balance = sam.balance; auto sam_sbd_balance = sam.sbd_balance; - - db->push_transaction(tx, 0); - - const auto &escrow = db->get_escrow(op.from, op.escrow_id); - - BOOST_REQUIRE(escrow.escrow_id == op.escrow_id); - BOOST_REQUIRE(escrow.from == op.from); - BOOST_REQUIRE(escrow.to == op.to); - BOOST_REQUIRE(escrow.agent == op.agent); - BOOST_REQUIRE( - escrow.ratification_deadline == op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == op.sbd_amount); - BOOST_REQUIRE(escrow.steem_balance == op.steem_amount); - BOOST_REQUIRE(escrow.pending_fee == op.fee); - BOOST_REQUIRE(!escrow.to_approved); - BOOST_REQUIRE(!escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); - BOOST_REQUIRE(alice.balance == alice_steem_balance); - BOOST_REQUIRE(alice.sbd_balance == alice_sbd_balance); - BOOST_REQUIRE(bob.balance == bob_steem_balance); - BOOST_REQUIRE(bob.sbd_balance == bob_sbd_balance); - BOOST_REQUIRE(sam.balance == sam_steem_balance); - BOOST_REQUIRE(sam.sbd_balance == sam_sbd_balance); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + + const auto& escrow = db->get_escrow(op.from, op.escrow_id); + BOOST_CHECK_EQUAL(escrow.escrow_id, op.escrow_id); + BOOST_CHECK_EQUAL(escrow.from, op.from); + BOOST_CHECK_EQUAL(escrow.to, op.to); + BOOST_CHECK_EQUAL(escrow.agent, op.agent); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, op.sbd_amount); + BOOST_CHECK_EQUAL(escrow.steem_balance, op.steem_amount); + BOOST_CHECK_EQUAL(escrow.pending_fee, op.fee); + BOOST_CHECK(!escrow.to_approved); + BOOST_CHECK(!escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); + BOOST_CHECK_EQUAL(alice.balance, alice_steem_balance); + BOOST_CHECK_EQUAL(alice.sbd_balance, alice_sbd_balance); + BOOST_CHECK_EQUAL(bob.balance, bob_steem_balance); + BOOST_CHECK_EQUAL(bob.sbd_balance, bob_sbd_balance); + BOOST_CHECK_EQUAL(sam.balance, sam_steem_balance); + BOOST_CHECK_EQUAL(sam.sbd_balance, sam_sbd_balance); validate_database(); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() // escrow_transfer + + + BOOST_AUTO_TEST_SUITE(escrow_approve) BOOST_AUTO_TEST_CASE(escrow_approve_validate) { try { BOOST_TEST_MESSAGE("Testing: escrow_approve_validate"); - escrow_approve_operation op; - op.from = "alice"; op.to = "bob"; op.agent = "sam"; op.who = "bob"; op.escrow_id = 0; op.approve = true; + BOOST_TEST_MESSAGE("--- success when 'who' is 'to'"); + CHECK_PARAM_VALID(op, who, op.to); - BOOST_TEST_MESSAGE("--- failure when who is not to or agent"); - op.who = "dave"; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + BOOST_TEST_MESSAGE("--- success when 'who' is 'agent'"); + CHECK_PARAM_VALID(op, who, op.agent); - BOOST_TEST_MESSAGE("--- success when who is to"); - op.who = op.to; - op.validate(); + BOOST_TEST_MESSAGE("--- failure when 'who' is not 'to' or 'agent'"); + CHECK_PARAM_INVALID(op, who, "dave"); - BOOST_TEST_MESSAGE("--- success when who is agent"); - op.who = op.agent; - op.validate(); + BOOST_TEST_MESSAGE("--- failure when invalid account"); + CHECK_PARAM_INVALID(op, from, ""); + CHECK_PARAM_INVALID(op, to, ""); + CHECK_PARAM_INVALID(op, agent, ""); + CHECK_PARAM_INVALID(op, who, ""); } FC_LOG_AND_RETHROW() } @@ -4156,36 +4711,16 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(escrow_approve_authorities) { try { BOOST_TEST_MESSAGE("Testing: escrow_approve_authorities"); - escrow_approve_operation op; - op.from = "alice"; op.to = "bob"; op.agent = "sam"; op.who = "bob"; op.escrow_id = 0; op.approve = true; - - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_active_authorities(auths); - expected.insert("bob"); - BOOST_REQUIRE(auths == expected); - - expected.clear(); - auths.clear(); - + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({op.who}), account_name_set()); op.who = "sam"; - op.get_required_active_authorities(auths); - expected.insert("sam"); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({op.who}), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -4207,13 +4742,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) et_op.escrow_expiration = db->head_block_time() + 200; signed_transaction tx; - tx.operations.push_back(et_op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - tx.operations.clear(); - tx.signatures.clear(); - + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, et_op)); BOOST_TEST_MESSAGE("---failure when to does not match escrow"); escrow_approve_operation op; @@ -4222,234 +4751,166 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.agent = "sam"; op.who = "dave"; op.approve = true; - - tx.operations.push_back(op); - tx.sign(dave_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, dave_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_bad_to))); BOOST_TEST_MESSAGE("--- failure when agent does not match escrow"); op.to = "bob"; op.agent = "dave"; - - tx.operations.clear(); - tx.signatures.clear(); - - tx.operations.push_back(op); - tx.sign(dave_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, dave_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_bad_agent))); BOOST_TEST_MESSAGE("--- success approving to"); op.agent = "sam"; op.who = "bob"; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); - tx.operations.clear(); - tx.signatures.clear(); - - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - auto &escrow = db->get_escrow(op.from, op.escrow_id); - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(escrow.steem_balance == ASSET("1.000 GOLOS")); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.100 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(!escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); - + { + auto& escrow = db->get_escrow(op.from, op.escrow_id); + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, ASSET("0.000 GBG")); + BOOST_CHECK_EQUAL(escrow.steem_balance, ASSET("1.000 GOLOS")); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.100 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(!escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); + } BOOST_TEST_MESSAGE("--- failure on repeat approval"); - tx.signatures.clear(); - - tx.set_expiration(db->head_block_time() + STEEMIT_BLOCK_INTERVAL); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(escrow.steem_balance == ASSET("1.000 GOLOS")); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.100 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(!escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); - + generate_block(); // avoid tx_duplicate_transaction + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::account_already_approved_escrow))); BOOST_TEST_MESSAGE("--- failure trying to repeal after approval"); - tx.signatures.clear(); - tx.operations.clear(); - op.approve = false; + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::account_already_approved_escrow))); - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == - et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(escrow.steem_balance == ASSET("1.000 GOLOS")); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.100 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(!escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); - + { + auto& escrow = db->get_escrow(op.from, op.escrow_id); + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, ASSET("0.000 GBG")); + BOOST_CHECK_EQUAL(escrow.steem_balance, ASSET("1.000 GOLOS")); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.100 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(!escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); + } BOOST_TEST_MESSAGE("--- success refunding from because of repeal"); - tx.signatures.clear(); - tx.operations.clear(); - op.who = op.agent; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - STEEMIT_REQUIRE_THROW(db->get_escrow(op.from, op.escrow_id), fc::exception); - BOOST_REQUIRE(alice.balance == ASSET("10.000 GOLOS")); + GOLOS_CHECK_ERROR_PROPS(db->get_escrow(op.from, op.escrow_id), + CHECK_ERROR(missing_object, "escrow", make_escrow_id(op.from, op.escrow_id))); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("10.000 GOLOS")); validate_database(); - BOOST_TEST_MESSAGE("--- test automatic refund when escrow is not ratified before deadline"); - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(et_op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, et_op)); - generate_blocks( - et_op.ratification_deadline + STEEMIT_BLOCK_INTERVAL, true); + generate_blocks(et_op.ratification_deadline + STEEMIT_BLOCK_INTERVAL, true); - STEEMIT_REQUIRE_THROW(db->get_escrow(op.from, op.escrow_id), fc::exception); - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("10.000 GOLOS")); + GOLOS_CHECK_ERROR_PROPS(db->get_escrow(op.from, op.escrow_id), + CHECK_ERROR(missing_object, "escrow", make_escrow_id(op.from, op.escrow_id))); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("10.000 GOLOS")); validate_database(); - BOOST_TEST_MESSAGE("--- test ratification expiration when escrow is only approved by to"); - tx.operations.clear(); - tx.signatures.clear(); et_op.ratification_deadline = db->head_block_time() + 100; et_op.escrow_expiration = db->head_block_time() + 200; - tx.operations.push_back(et_op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, et_op)); - tx.operations.clear(); - tx.signatures.clear(); op.who = op.to; op.approve = true; - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); - generate_blocks( - et_op.ratification_deadline + STEEMIT_BLOCK_INTERVAL, true); + generate_blocks(et_op.ratification_deadline + STEEMIT_BLOCK_INTERVAL, true); - STEEMIT_REQUIRE_THROW(db->get_escrow(op.from, op.escrow_id), fc::exception); - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("10.000 GOLOS")); + GOLOS_CHECK_ERROR_PROPS(db->get_escrow(op.from, op.escrow_id), + CHECK_ERROR(missing_object, "escrow", make_escrow_id(op.from, op.escrow_id))); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("10.000 GOLOS")); validate_database(); - BOOST_TEST_MESSAGE("--- test ratification expiration when escrow is only approved by agent"); - tx.operations.clear(); - tx.signatures.clear(); et_op.ratification_deadline = db->head_block_time() + 100; et_op.escrow_expiration = db->head_block_time() + 200; - tx.operations.push_back(et_op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, et_op)); - tx.operations.clear(); - tx.signatures.clear(); op.who = op.agent; - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); generate_blocks(et_op.ratification_deadline + STEEMIT_BLOCK_INTERVAL, true); - STEEMIT_REQUIRE_THROW(db->get_escrow(op.from, op.escrow_id), fc::exception); - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("10.000 GOLOS")); + GOLOS_CHECK_ERROR_PROPS(db->get_escrow(op.from, op.escrow_id), + CHECK_ERROR(missing_object, "escrow", make_escrow_id(op.from, op.escrow_id))); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("10.000 GOLOS")); validate_database(); - BOOST_TEST_MESSAGE("--- success approving escrow"); - tx.operations.clear(); - tx.signatures.clear(); et_op.ratification_deadline = db->head_block_time() + 100; et_op.escrow_expiration = db->head_block_time() + 200; - tx.operations.push_back(et_op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, et_op)); - tx.operations.clear(); - tx.signatures.clear(); op.who = op.to; - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); - tx.operations.clear(); - tx.signatures.clear(); op.who = op.agent; - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); { - const auto &escrow = db->get_escrow(op.from, op.escrow_id); - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(escrow.steem_balance == ASSET("1.000 GOLOS")); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); + const auto& escrow = db->get_escrow(op.from, op.escrow_id); + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, ASSET("0.000 GBG")); + BOOST_CHECK_EQUAL(escrow.steem_balance, ASSET("1.000 GOLOS")); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.000 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); } - BOOST_REQUIRE(db->get_account("sam").balance == et_op.fee); + BOOST_CHECK_EQUAL(db->get_account("sam").balance, et_op.fee); validate_database(); - BOOST_TEST_MESSAGE("--- ratification expiration does not remove an approved escrow"); generate_blocks(et_op.ratification_deadline + STEEMIT_BLOCK_INTERVAL, true); { - const auto &escrow = db->get_escrow(op.from, op.escrow_id); - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(escrow.steem_balance == ASSET("1.000 GOLOS")); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); + const auto& escrow = db->get_escrow(op.from, op.escrow_id); + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, ASSET("0.000 GBG")); + BOOST_CHECK_EQUAL(escrow.steem_balance, ASSET("1.000 GOLOS")); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.000 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); } - BOOST_REQUIRE(db->get_account("sam").balance == et_op.fee); + BOOST_CHECK_EQUAL(db->get_account("sam").balance, et_op.fee); validate_database(); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() // escrow_approve + + + BOOST_AUTO_TEST_SUITE(escrow_dispute) BOOST_AUTO_TEST_CASE(escrow_dispute_validate) { try { @@ -4457,19 +4918,22 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) escrow_dispute_operation op; op.from = "alice"; op.to = "bob"; - op.agent = "alice"; + op.agent = "dave"; op.who = "alice"; - BOOST_TEST_MESSAGE("failure when who is not from or to"); - op.who = "sam"; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + BOOST_TEST_MESSAGE("--- success on valid params"); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, who, "bob"); - BOOST_TEST_MESSAGE("success"); - op.who = "alice"; - op.validate(); + BOOST_TEST_MESSAGE("--- failure when who is not from or to"); + CHECK_PARAM_INVALID(op, who, "dave"); + CHECK_PARAM_INVALID(op, who, "sam"); - op.who = "bob"; - op.validate(); + BOOST_TEST_MESSAGE("--- failure when account invalid"); + CHECK_PARAM_INVALID(op, from, ""); + CHECK_PARAM_INVALID(op, to, ""); + CHECK_PARAM_INVALID(op, agent, ""); + CHECK_PARAM_INVALID(op, who, ""); } FC_LOG_AND_RETHROW() } @@ -4481,26 +4945,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.from = "alice"; op.to = "bob"; op.who = "alice"; - - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_active_authorities(auths); - expected.insert("alice"); - BOOST_REQUIRE(auths == expected); - - auths.clear(); - expected.clear(); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({op.who}), account_name_set()); op.who = "bob"; - op.get_required_active_authorities(auths); - expected.insert("bob"); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({op.who}), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -4508,7 +4955,6 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(escrow_dispute_apply) { try { BOOST_TEST_MESSAGE("Testing: escrow_dispute_apply"); - ACTORS((alice)(bob)(sam)(dave)) fund("alice", 10000); @@ -4529,13 +4975,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) ea_b_op.approve = true; signed_transaction tx; - tx.operations.push_back(et_op); - tx.operations.push_back(ea_b_op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); + sign_tx_with_ops(tx, alice_private_key, et_op, ea_b_op); tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- failure when escrow has not been approved"); escrow_dispute_operation op; @@ -4543,25 +4985,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.to = "bob"; op.agent = "sam"; op.who = "bob"; - - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - - const auto &escrow = db->get_escrow(et_op.from, et_op.escrow_id); - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == et_op.sbd_amount); - BOOST_REQUIRE(escrow.steem_balance == et_op.steem_amount); - BOOST_REQUIRE(escrow.pending_fee == et_op.fee); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(!escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_must_be_approved_first))); + + const auto& escrow = db->get_escrow(et_op.from, et_op.escrow_id); + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, et_op.sbd_amount); + BOOST_CHECK_EQUAL(escrow.steem_balance, et_op.steem_amount); + BOOST_CHECK_EQUAL(escrow.pending_fee, et_op.fee); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(!escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); BOOST_TEST_MESSAGE("--- failure when to does not match escrow"); escrow_approve_operation ea_s_op; @@ -4570,81 +5008,66 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) ea_s_op.agent = "sam"; ea_s_op.who = "sam"; ea_s_op.approve = true; - - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(ea_s_op); - tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, ea_s_op)); op.to = "dave"; op.who = "alice"; - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == et_op.sbd_amount); - BOOST_REQUIRE(escrow.steem_balance == et_op.steem_amount); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_bad_to))); + + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, et_op.sbd_amount); + BOOST_CHECK_EQUAL(escrow.steem_balance, et_op.steem_amount); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.000 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); BOOST_TEST_MESSAGE("--- failure when agent does not match escrow"); op.to = "bob"; op.who = "alice"; op.agent = "dave"; - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == et_op.sbd_amount); - BOOST_REQUIRE(escrow.steem_balance == et_op.steem_amount); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_bad_agent))); + + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, et_op.sbd_amount); + BOOST_CHECK_EQUAL(escrow.steem_balance, et_op.steem_amount); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.000 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); BOOST_TEST_MESSAGE("--- failure when escrow is expired"); generate_blocks(2); - tx.operations.clear(); - tx.signatures.clear(); op.agent = "sam"; - tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cannot_dispute_expired_escrow))); { - const auto &escrow = db->get_escrow(et_op.from, et_op.escrow_id); - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == et_op.sbd_amount); - BOOST_REQUIRE(escrow.steem_balance == et_op.steem_amount); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(escrow.agent_approved); - BOOST_REQUIRE(!escrow.disputed); + const auto& escrow = db->get_escrow(et_op.from, et_op.escrow_id); + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, et_op.sbd_amount); + BOOST_CHECK_EQUAL(escrow.steem_balance, et_op.steem_amount); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.000 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(escrow.agent_approved); + BOOST_CHECK(!escrow.disputed); } - BOOST_TEST_MESSAGE("--- success disputing escrow"); et_op.escrow_id = 1; et_op.ratification_deadline = db->head_block_time() + STEEMIT_BLOCK_INTERVAL; @@ -4652,62 +5075,54 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) ea_b_op.escrow_id = et_op.escrow_id; ea_s_op.escrow_id = et_op.escrow_id; - tx.operations.clear(); - tx.signatures.clear(); - tx.operations.push_back(et_op); - tx.operations.push_back(ea_b_op); - tx.operations.push_back(ea_s_op); - tx.sign(alice_private_key, db->get_chain_id()); + sign_tx_with_ops(tx, alice_private_key, et_op, ea_b_op, ea_s_op); tx.sign(bob_private_key, db->get_chain_id()); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); - tx.operations.clear(); - tx.signatures.clear(); op.escrow_id = et_op.escrow_id; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); { - const auto &escrow = db->get_escrow(et_op.from, et_op.escrow_id); - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == et_op.sbd_amount); - BOOST_REQUIRE(escrow.steem_balance == et_op.steem_amount); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(escrow.agent_approved); - BOOST_REQUIRE(escrow.disputed); + const auto& escrow = db->get_escrow(et_op.from, et_op.escrow_id); + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, et_op.sbd_amount); + BOOST_CHECK_EQUAL(escrow.steem_balance, et_op.steem_amount); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.000 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(escrow.agent_approved); + BOOST_CHECK(escrow.disputed); } - BOOST_TEST_MESSAGE("--- failure when escrow is already under dispute"); - tx.operations.clear(); - tx.signatures.clear(); op.who = "bob"; - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_already_disputed))); { - const auto &escrow = db->get_escrow(et_op.from, et_op.escrow_id); - BOOST_REQUIRE(escrow.to == "bob"); - BOOST_REQUIRE(escrow.agent == "sam"); - BOOST_REQUIRE(escrow.ratification_deadline == et_op.ratification_deadline); - BOOST_REQUIRE(escrow.escrow_expiration == et_op.escrow_expiration); - BOOST_REQUIRE(escrow.sbd_balance == et_op.sbd_amount); - BOOST_REQUIRE(escrow.steem_balance == et_op.steem_amount); - BOOST_REQUIRE(escrow.pending_fee == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(escrow.to_approved); - BOOST_REQUIRE(escrow.agent_approved); - BOOST_REQUIRE(escrow.disputed); + const auto& escrow = db->get_escrow(et_op.from, et_op.escrow_id); + BOOST_CHECK_EQUAL(escrow.to, "bob"); + BOOST_CHECK_EQUAL(escrow.agent, "sam"); + BOOST_CHECK_EQUAL(escrow.ratification_deadline, et_op.ratification_deadline); + BOOST_CHECK_EQUAL(escrow.escrow_expiration, et_op.escrow_expiration); + BOOST_CHECK_EQUAL(escrow.sbd_balance, et_op.sbd_amount); + BOOST_CHECK_EQUAL(escrow.steem_balance, et_op.steem_amount); + BOOST_CHECK_EQUAL(escrow.pending_fee, ASSET("0.000 GOLOS")); + BOOST_CHECK(escrow.to_approved); + BOOST_CHECK(escrow.agent_approved); + BOOST_CHECK(escrow.disputed); } } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() // escrow_dispute + + + BOOST_AUTO_TEST_SUITE(escrow_release) BOOST_AUTO_TEST_CASE(escrow_release_validate) { try { @@ -4715,41 +5130,44 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) escrow_release_operation op; op.from = "alice"; op.to = "bob"; - op.who = "alice"; op.agent = "sam"; + op.who = "alice"; op.receiver = "bob"; + op.steem_amount = ASSET_GOLOS(1); + op.sbd_amount = ASSET_GBG(0); + BOOST_TEST_MESSAGE("--- success"); + CHECK_OP_VALID(op); + BOOST_TEST_MESSAGE("--- failure when invalid account"); + CHECK_PARAM_INVALID(op, from, ""); + CHECK_PARAM_INVALID(op, to, ""); + CHECK_PARAM_INVALID(op, agent, ""); + CHECK_PARAM_INVALID(op, who, ""); + CHECK_PARAM_INVALID(op, receiver, ""); - BOOST_TEST_MESSAGE("--- failure when steem < 0"); - op.steem_amount.amount = -1; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + BOOST_TEST_MESSAGE("--- failure when who not from or to or agent"); + CHECK_PARAM_INVALID(op, who, "dave"); + BOOST_TEST_MESSAGE("--- failure when receiver not from or to"); + CHECK_PARAM_INVALID(op, receiver, "sam"); + CHECK_PARAM_INVALID(op, receiver, "dave"); - BOOST_TEST_MESSAGE("--- failure when sbd < 0"); - op.steem_amount.amount = 0; - op.sbd_amount.amount = -1; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + BOOST_TEST_MESSAGE("--- failure when steem < 0"); + CHECK_PARAM_INVALID(op, steem_amount, ASSET_GOLOS(-1)); + BOOST_TEST_MESSAGE("--- failure when sbd < 0"); + CHECK_PARAM_INVALID(op, sbd_amount, ASSET_GBG(-1)); BOOST_TEST_MESSAGE("--- failure when steem == 0 and sbd == 0"); - op.sbd_amount.amount = 0; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - + CHECK_PARAM_INVALID_LOGIC(op, steem_amount, ASSET_GOLOS(0), escrow_no_amount_set); BOOST_TEST_MESSAGE("--- failure when sbd is not sbd symbol"); - op.sbd_amount = ASSET("1.000 GOLOS"); - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - + CHECK_PARAM_INVALID(op, sbd_amount, ASSET_GOLOS(1)); + CHECK_PARAM_INVALID(op, sbd_amount, ASSET_GESTS(1)); BOOST_TEST_MESSAGE("--- failure when steem is not steem symbol"); - op.sbd_amount.symbol = SBD_SYMBOL; - op.steem_amount = ASSET("1.000 GBG"); - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - - BOOST_TEST_MESSAGE("--- success"); - op.steem_amount.symbol = STEEM_SYMBOL; - op.validate(); + CHECK_PARAM_INVALID(op, steem_amount, ASSET_GBG(0)); + CHECK_PARAM_INVALID(op, steem_amount, ASSET_GESTS(0)); } FC_LOG_AND_RETHROW() } @@ -4761,33 +5179,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.from = "alice"; op.to = "bob"; op.who = "alice"; - - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - expected.insert("alice"); - op.get_required_active_authorities(auths); - BOOST_REQUIRE(auths == expected); - + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({op.who}), account_name_set()); op.who = "bob"; - auths.clear(); - expected.clear(); - expected.insert("bob"); - op.get_required_active_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.who = "sam"; - auths.clear(); - expected.clear(); - expected.insert("sam"); - op.get_required_active_authorities(auths); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({op.who}), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -4795,7 +5189,6 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(escrow_release_apply) { try { BOOST_TEST_MESSAGE("Testing: escrow_release_apply"); - ACTORS((alice)(bob)(sam)(dave)) fund("alice", 10000); @@ -4809,13 +5202,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) et_op.escrow_expiration = db->head_block_time() + 2 * STEEMIT_BLOCK_INTERVAL; signed_transaction tx; - tx.operations.push_back(et_op); - - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, et_op)); BOOST_TEST_MESSAGE("--- failure releasing funds prior to approval"); escrow_release_operation op; @@ -4825,453 +5212,279 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.who = et_op.from; op.receiver = et_op.to; op.steem_amount = ASSET("0.100 GOLOS"); - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::escrow_must_be_approved_first))); escrow_approve_operation ea_b_op; ea_b_op.from = "alice"; ea_b_op.to = "bob"; ea_b_op.agent = "sam"; ea_b_op.who = "bob"; - escrow_approve_operation ea_s_op; ea_s_op.from = "alice"; ea_s_op.to = "bob"; ea_s_op.agent = "sam"; ea_s_op.who = "sam"; - - tx.clear(); - tx.operations.push_back(ea_b_op); - tx.operations.push_back(ea_s_op); - tx.sign(bob_private_key, db->get_chain_id()); + sign_tx_with_ops(tx, bob_private_key, ea_b_op, ea_s_op); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); BOOST_TEST_MESSAGE("--- failure when 'agent' attempts to release non-disputed escrow to 'to'"); op.who = et_op.agent; - tx.clear(); - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, sam_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::only_from_to_can_release_non_disputed))); BOOST_TEST_MESSAGE("--- failure when 'agent' attempts to release non-disputed escrow to 'from' "); op.receiver = et_op.from; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, sam_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::only_from_to_can_release_non_disputed))); BOOST_TEST_MESSAGE("--- failure when 'agent' attempt to release non-disputed escrow to not 'to' or 'from'"); op.receiver = "dave"; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- failure when other attempts to release non-disputed escrow to 'to'"); op.receiver = et_op.to; op.who = "dave"; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(dave_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "who")); BOOST_TEST_MESSAGE("--- failure when other attempts to release non-disputed escrow to 'from' "); op.receiver = et_op.from; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(dave_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "who")); BOOST_TEST_MESSAGE("--- failure when other attempt to release non-disputed escrow to not 'to' or 'from'"); op.receiver = "dave"; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(dave_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "who")); BOOST_TEST_MESSAGE("--- failure when 'to' attemtps to release non-disputed escrow to 'to'"); op.receiver = et_op.to; op.who = et_op.to; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::to_can_release_only_to_from))); BOOST_TEST_MESSAGE("--- failure when 'to' attempts to release non-dispured escrow to 'agent' "); op.receiver = et_op.agent; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- failure when 'to' attempts to release non-disputed escrow to not 'from'"); op.receiver = "dave"; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- success release non-disputed escrow to 'to' from 'from'"); op.receiver = et_op.from; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_escrow(op.from, op.escrow_id).steem_balance == - ASSET("0.900 GOLOS")); - BOOST_REQUIRE( - db->get_account("alice").balance == ASSET("9.000 GOLOS")); - + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); + BOOST_CHECK_EQUAL(db->get_escrow(op.from, op.escrow_id).steem_balance, ASSET("0.900 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("9.000 GOLOS")); BOOST_TEST_MESSAGE("--- failure when 'from' attempts to release non-disputed escrow to 'from'"); op.receiver = et_op.from; op.who = et_op.from; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::from_can_release_only_to_to))); BOOST_TEST_MESSAGE("--- failure when 'from' attempts to release non-disputed escrow to 'agent'"); op.receiver = et_op.agent; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- failure when 'from' attempts to release non-disputed escrow to not 'from'"); op.receiver = "dave"; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- success release non-disputed escrow to 'from' from 'to'"); op.receiver = et_op.to; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_escrow(op.from, op.escrow_id).steem_balance == ASSET("0.800 GOLOS")); - BOOST_REQUIRE(db->get_account("bob").balance == ASSET("0.100 GOLOS")); - + BOOST_CHECK_EQUAL(db->get_escrow(op.from, op.escrow_id).steem_balance, ASSET("0.800 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("bob").balance, ASSET("0.100 GOLOS")); BOOST_TEST_MESSAGE("--- failure when releasing more sbd than available"); op.steem_amount = ASSET("1.000 GOLOS"); - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::release_amount_exceeds_escrow_balance))); BOOST_TEST_MESSAGE("--- failure when releasing less steem than available"); op.steem_amount = ASSET("0.000 GOLOS"); op.sbd_amount = ASSET("1.000 GBG"); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::release_amount_exceeds_escrow_balance))); - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - - + generate_block(); BOOST_TEST_MESSAGE("--- failure when 'to' attempts to release disputed escrow"); escrow_dispute_operation ed_op; ed_op.from = "alice"; ed_op.to = "bob"; ed_op.agent = "sam"; ed_op.who = "alice"; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, ed_op)); - tx.clear(); - tx.operations.push_back(ed_op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - tx.clear(); op.from = et_op.from; op.receiver = et_op.from; op.who = et_op.to; op.steem_amount = ASSET("0.100 GOLOS"); op.sbd_amount = ASSET("0.000 GBG"); - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::only_agent_can_release_disputed))); BOOST_TEST_MESSAGE("--- failure when 'from' attempts to release disputed escrow"); - tx.clear(); op.receiver = et_op.to; op.who = et_op.from; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::only_agent_can_release_disputed))); BOOST_TEST_MESSAGE("--- failure when releasing disputed escrow to an account not 'to' or 'from'"); - tx.clear(); op.who = et_op.agent; op.receiver = "dave"; - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- failure when agent does not match escrow"); - tx.clear(); op.who = "dave"; op.receiver = et_op.from; - tx.operations.push_back(op); - tx.sign(dave_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "who")); BOOST_TEST_MESSAGE("--- success releasing disputed escrow with agent to 'to'"); - tx.clear(); op.receiver = et_op.to; op.who = et_op.agent; - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("bob").balance == ASSET("0.200 GOLOS")); - BOOST_REQUIRE( - db->get_escrow(et_op.from, et_op.escrow_id).steem_balance == - ASSET("0.700 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("bob").balance, ASSET("0.200 GOLOS")); + BOOST_CHECK_EQUAL(db->get_escrow(et_op.from, et_op.escrow_id).steem_balance, ASSET("0.700 GOLOS")); BOOST_TEST_MESSAGE("--- success releasing disputed escrow with agent to 'from'"); - tx.clear(); op.receiver = et_op.from; op.who = et_op.agent; - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("9.100 GOLOS")); - BOOST_REQUIRE( - db->get_escrow(et_op.from, et_op.escrow_id).steem_balance == - ASSET("0.600 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("9.100 GOLOS")); + BOOST_CHECK_EQUAL(db->get_escrow(et_op.from, et_op.escrow_id).steem_balance, ASSET("0.600 GOLOS")); BOOST_TEST_MESSAGE("--- failure when 'to' attempts to release disputed expired escrow"); generate_blocks(2); - - tx.clear(); op.receiver = et_op.from; op.who = et_op.to; - tx.operations.push_back(op); - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::only_agent_can_release_disputed))); BOOST_TEST_MESSAGE("--- failure when 'from' attempts to release disputed expired escrow"); - tx.clear(); op.receiver = et_op.to; op.who = et_op.from; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::only_agent_can_release_disputed))); BOOST_TEST_MESSAGE("--- success releasing disputed expired escrow with agent"); - tx.clear(); op.receiver = et_op.from; op.who = et_op.agent; - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("9.200 GOLOS")); - BOOST_REQUIRE( - db->get_escrow(et_op.from, et_op.escrow_id).steem_balance == - ASSET("0.500 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("9.200 GOLOS")); + BOOST_CHECK_EQUAL(db->get_escrow(et_op.from, et_op.escrow_id).steem_balance, ASSET("0.500 GOLOS")); BOOST_TEST_MESSAGE("--- success deleting escrow when balances are both zero"); - tx.clear(); op.steem_amount = ASSET("0.500 GOLOS"); - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("9.700 GOLOS")); - STEEMIT_REQUIRE_THROW(db->get_escrow(et_op.from, et_op.escrow_id), fc::exception); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("9.700 GOLOS")); + GOLOS_CHECK_ERROR_PROPS(db->get_escrow(et_op.from, et_op.escrow_id), + CHECK_ERROR(missing_object, "escrow", make_escrow_id(et_op.from, et_op.escrow_id))); - tx.clear(); - et_op.ratification_deadline = - db->head_block_time() + STEEMIT_BLOCK_INTERVAL; - et_op.escrow_expiration = - db->head_block_time() + 2 * STEEMIT_BLOCK_INTERVAL; - tx.operations.push_back(et_op); - tx.operations.push_back(ea_b_op); - tx.operations.push_back(ea_s_op); - tx.set_expiration( - db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); + et_op.ratification_deadline = db->head_block_time() + STEEMIT_BLOCK_INTERVAL; + et_op.escrow_expiration = db->head_block_time() + 2 * STEEMIT_BLOCK_INTERVAL; + sign_tx_with_ops(tx, alice_private_key, et_op, ea_b_op, ea_s_op); tx.sign(bob_private_key, db->get_chain_id()); tx.sign(sam_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(db->push_transaction(tx, 0)); generate_blocks(2); - BOOST_TEST_MESSAGE("--- failure when 'agent' attempts to release non-disputed expired escrow to 'to'"); - tx.clear(); op.receiver = et_op.to; op.who = et_op.agent; op.steem_amount = ASSET("0.100 GOLOS"); - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, sam_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::only_from_to_can_release_non_disputed))); BOOST_TEST_MESSAGE("--- failure when 'agent' attempts to release non-disputed expired escrow to 'from'"); - tx.clear(); op.receiver = et_op.from; - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, sam_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::only_from_to_can_release_non_disputed))); BOOST_TEST_MESSAGE("--- failure when 'agent' attempt to release non-disputed expired escrow to not 'to' or 'from'"); - tx.clear(); op.receiver = "dave"; - tx.operations.push_back(op); - tx.sign(sam_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- failure when 'to' attempts to release non-dispured expired escrow to 'agent'"); - tx.clear(); op.who = et_op.to; op.receiver = et_op.agent; - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- failure when 'to' attempts to release non-disputed expired escrow to not 'from' or 'to'"); - tx.clear(); op.receiver = "dave"; - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- success release non-disputed expired escrow to 'to' from 'to'"); - tx.clear(); op.receiver = et_op.to; - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("bob").balance == ASSET("0.300 GOLOS")); - BOOST_REQUIRE( - db->get_escrow(et_op.from, et_op.escrow_id).steem_balance == - ASSET("0.900 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("bob").balance, ASSET("0.300 GOLOS")); + BOOST_CHECK_EQUAL(db->get_escrow(et_op.from, et_op.escrow_id).steem_balance, ASSET("0.900 GOLOS")); BOOST_TEST_MESSAGE("--- success release non-disputed expired escrow to 'from' from 'to'"); - tx.clear(); op.receiver = et_op.from; - tx.operations.push_back(op); - tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("8.700 GOLOS")); - BOOST_REQUIRE(db->get_escrow(et_op.from, et_op.escrow_id).steem_balance == ASSET("0.800 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("8.700 GOLOS")); + BOOST_CHECK_EQUAL(db->get_escrow(et_op.from, et_op.escrow_id).steem_balance, ASSET("0.800 GOLOS")); BOOST_TEST_MESSAGE("--- failure when 'from' attempts to release non-disputed expired escrow to 'agent'"); - tx.clear(); op.who = et_op.from; op.receiver = et_op.agent; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- failure when 'from' attempts to release non-disputed expired escrow to not 'from' or 'to'"); - tx.clear(); op.receiver = "dave"; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), CHECK_ERROR(invalid_parameter, "receiver")); BOOST_TEST_MESSAGE("--- success release non-disputed expired escrow to 'to' from 'from'"); - tx.clear(); op.receiver = et_op.to; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE( - db->get_account("bob").balance == ASSET("0.400 GOLOS")); - BOOST_REQUIRE( - db->get_escrow(et_op.from, et_op.escrow_id).steem_balance == - ASSET("0.700 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("bob").balance, ASSET("0.400 GOLOS")); + BOOST_CHECK_EQUAL(db->get_escrow(et_op.from, et_op.escrow_id).steem_balance, ASSET("0.700 GOLOS")); BOOST_TEST_MESSAGE("--- success release non-disputed expired escrow to 'from' from 'from'"); - tx.clear(); op.receiver = et_op.from; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE( - db->get_account("alice").balance == ASSET("8.800 GOLOS")); - BOOST_REQUIRE( - db->get_escrow(et_op.from, et_op.escrow_id).steem_balance == - ASSET("0.600 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("8.800 GOLOS")); + BOOST_CHECK_EQUAL(db->get_escrow(et_op.from, et_op.escrow_id).steem_balance, ASSET("0.600 GOLOS")); BOOST_TEST_MESSAGE("--- success deleting escrow when balances are zero on non-disputed escrow"); - tx.clear(); op.steem_amount = ASSET("0.600 GOLOS"); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); - BOOST_REQUIRE( - db->get_account("alice").balance == ASSET("9.400 GOLOS")); - STEEMIT_REQUIRE_THROW(db->get_escrow(et_op.from, et_op.escrow_id), fc::exception); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("9.400 GOLOS")); + GOLOS_CHECK_ERROR_PROPS(db->get_escrow(et_op.from, et_op.escrow_id), + CHECK_ERROR(missing_object, "escrow", make_escrow_id(et_op.from, et_op.escrow_id))); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() // escrow_release + BOOST_AUTO_TEST_SUITE_END() // escrow + +//------------------------------------------------------------- + + BOOST_AUTO_TEST_SUITE(transfer_to_savings) BOOST_AUTO_TEST_CASE(transfer_to_savings_validate) { try { @@ -5282,37 +5495,27 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.to = "alice"; op.amount = ASSET("1.000 GOLOS"); + BOOST_TEST_MESSAGE("--- success with valid parameters"); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, amount, ASSET_GBG(1)); + CHECK_PARAM_VALID(op, memo, string(STEEMIT_MAX_MEMO_SIZE-1, ' ')); // valid is < MAX_SIZE + CHECK_PARAM_VALID(op, memo, u8"тест"); - BOOST_TEST_MESSAGE("failure when 'from' is empty"); - op.from = ""; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - - BOOST_TEST_MESSAGE("failure when 'to' is empty"); - op.from = "alice"; - op.to = ""; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + BOOST_TEST_MESSAGE("--- failure when 'from' or `to` is empty"); + CHECK_PARAM_INVALID(op, from, ""); + CHECK_PARAM_INVALID(op, to, ""); + BOOST_TEST_MESSAGE("--- failure when amount is GESTS"); + CHECK_PARAM_INVALID(op, amount, ASSET("1.000 GESTS")); // unsupported asset + CHECK_PARAM_INVALID(op, amount, ASSET_GESTS(1)); // gests - BOOST_TEST_MESSAGE("sucess when 'to' is not empty"); - op.to = "bob"; - op.validate(); - + BOOST_TEST_MESSAGE("--- failure when amount is negative"); + CHECK_PARAM_INVALID(op, amount, ASSET_GOLOS(-1)); + CHECK_PARAM_INVALID(op, amount, ASSET_GBG(-1)); - BOOST_TEST_MESSAGE("failure when amount is VESTS"); - op.to = "alice"; - op.amount = ASSET("1.000 VESTS"); - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - - BOOST_TEST_MESSAGE("success when amount is SBD"); - op.amount = ASSET("1.000 GBG"); - op.validate(); - - - BOOST_TEST_MESSAGE("success when amount is STEEM"); - op.amount = ASSET("1.000 GOLOS"); - op.validate(); + BOOST_TEST_MESSAGE("--- failure when memo invalid"); + CHECK_PARAM_INVALID(op, memo, string(STEEMIT_MAX_MEMO_SIZE, ' ')); + CHECK_PARAM_INVALID(op, memo, BAD_UTF8_STRING); } FC_LOG_AND_RETHROW() } @@ -5320,31 +5523,11 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(transfer_to_savings_authorities) { try { BOOST_TEST_MESSAGE("Testing: transfer_to_savings_authorities"); - transfer_to_savings_operation op; op.from = "alice"; op.to = "alice"; op.amount = ASSET("1.000 GOLOS"); - - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_active_authorities(auths); - expected.insert("alice"); - BOOST_REQUIRE(auths == expected); - - auths.clear(); - expected.clear(); - op.from = "bob"; - op.get_required_active_authorities(auths); - expected.insert("bob"); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"alice"}), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -5355,12 +5538,10 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) ACTORS((alice)(bob)); generate_block(); - fund("alice", ASSET("10.000 GOLOS")); fund("alice", ASSET("10.000 GBG")); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("10.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").sbd_balance == ASSET("10.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("10.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").sbd_balance, ASSET("10.000 GBG")); transfer_to_savings_operation op; signed_transaction tx; @@ -5369,79 +5550,62 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.from = "alice"; op.to = "alice"; op.amount = ASSET("20.000 GOLOS"); - - tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "alice", "fund", "20.000 GOLOS"))); validate_database(); - BOOST_TEST_MESSAGE("--- failure when transferring to non-existent account"); op.to = "sam"; op.amount = ASSET("1.000 GOLOS"); - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "account", "sam"))); validate_database(); - BOOST_TEST_MESSAGE("--- success transferring STEEM to self"); op.to = "alice"; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("9.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").savings_balance == ASSET("1.000 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("9.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_balance, ASSET("1.000 GOLOS")); validate_database(); - BOOST_TEST_MESSAGE("--- success transferring SBD to self"); op.amount = ASSET("1.000 GBG"); - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").sbd_balance == ASSET("9.000 GBG")); - BOOST_REQUIRE(db->get_account("alice").savings_sbd_balance == ASSET("1.000 GBG")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").sbd_balance, ASSET("9.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_sbd_balance, ASSET("1.000 GBG")); validate_database(); - BOOST_TEST_MESSAGE("--- success transferring STEEM to other"); op.to = "bob"; op.amount = ASSET("1.000 GOLOS"); - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("8.000 GOLOS")); - BOOST_REQUIRE(db->get_account("bob").savings_balance == ASSET("1.000 GOLOS")); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("8.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("bob").savings_balance, ASSET("1.000 GOLOS")); validate_database(); - BOOST_TEST_MESSAGE("--- success transferring SBD to other"); op.amount = ASSET("1.000 GBG"); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").sbd_balance, ASSET("8.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("bob").savings_sbd_balance, ASSET("1.000 GBG")); + validate_database(); - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - BOOST_REQUIRE(db->get_account("alice").sbd_balance == ASSET("8.000 GBG")); - BOOST_REQUIRE(db->get_account("bob").savings_sbd_balance == ASSET("1.000 GBG")); + BOOST_TEST_MESSAGE("--- failure when transferring without authorities"); + op.from = "bob"; + op.to = "alice"; + op.amount = ASSET("1.000 GOLOS"); + GOLOS_CHECK_THROW_PROPS(push_tx_with_ops(tx, alice_private_key, op), tx_missing_active_auth, {}); validate_database(); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() // transfer_to_savings + + + BOOST_AUTO_TEST_SUITE(transfer_from_savings) BOOST_AUTO_TEST_CASE(transfer_from_savings_validate) { try { @@ -5452,38 +5616,27 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.request_id = 0; op.to = "alice"; op.amount = ASSET("1.000 GOLOS"); - - - BOOST_TEST_MESSAGE("failure when 'from' is empty"); - op.from = ""; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - - BOOST_TEST_MESSAGE("failure when 'to' is empty"); - op.from = "alice"; - op.to = ""; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - - BOOST_TEST_MESSAGE("sucess when 'to' is not empty"); - op.to = "bob"; - op.validate(); - - - BOOST_TEST_MESSAGE("failure when amount is VESTS"); - op.to = "alice"; - op.amount = ASSET("1.000 VESTS"); - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); - - - BOOST_TEST_MESSAGE("success when amount is SBD"); - op.amount = ASSET("1.000 GBG"); - op.validate(); - - - BOOST_TEST_MESSAGE("success when amount is STEEM"); - op.amount = ASSET("1.000 GOLOS"); - op.validate(); + BOOST_TEST_MESSAGE("--- success with valid parameters"); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, amount, ASSET_GBG(1)); + CHECK_PARAM_VALID(op, memo, string(STEEMIT_MAX_MEMO_SIZE-1, ' ')); // valid is < MAX_SIZE + CHECK_PARAM_VALID(op, memo, u8"тест"); + + BOOST_TEST_MESSAGE("--- failure when 'from' or 'to' is empty"); + CHECK_PARAM_INVALID(op, from, ""); + CHECK_PARAM_INVALID(op, to, ""); + + BOOST_TEST_MESSAGE("--- failure when amount is GESTS"); + CHECK_PARAM_INVALID(op, amount, ASSET("1.000 GESTS")); // unsupported asset + CHECK_PARAM_INVALID(op, amount, ASSET_GESTS(1)); // gests + + BOOST_TEST_MESSAGE("--- failure when amount is negative"); + CHECK_PARAM_INVALID(op, amount, ASSET_GOLOS(-1)); + CHECK_PARAM_INVALID(op, amount, ASSET_GBG(-1)); + + BOOST_TEST_MESSAGE("--- failure when memo invalid"); + CHECK_PARAM_INVALID(op, memo, string(STEEMIT_MAX_MEMO_SIZE, ' ')); + CHECK_PARAM_INVALID(op, memo, BAD_UTF8_STRING); } FC_LOG_AND_RETHROW() } @@ -5491,42 +5644,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(transfer_from_savings_authorities) { try { BOOST_TEST_MESSAGE("Testing: transfer_from_savings_authorities"); - transfer_from_savings_operation op; op.from = "alice"; op.to = "alice"; op.amount = ASSET("1.000 GOLOS"); - - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_active_authorities(auths); - expected.insert("alice"); - BOOST_REQUIRE(auths == expected); - - auths.clear(); - expected.clear(); - op.from = "bob"; - op.get_required_active_authorities(auths); - expected.insert("bob"); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"alice"}), account_name_set()); } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE(transfer_from_savings_apply) { - try { + try { BOOST_TEST_MESSAGE("Testing: transfer_from_savings_apply"); ACTORS((alice)(bob)); generate_block(); - fund("alice", ASSET("10.000 GOLOS")); fund("alice", ASSET("10.000 GBG")); @@ -5536,17 +5668,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) save.amount = ASSET("10.000 GOLOS"); signed_transaction tx; - tx.operations.push_back(save); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, save)); save.amount = ASSET("10.000 GBG"); - tx.clear(); - tx.operations.push_back(save); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, save)); BOOST_TEST_MESSAGE("--- failure when account has insufficient funds"); transfer_from_savings_operation op; @@ -5554,136 +5678,107 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.to = "bob"; op.amount = ASSET("20.000 GOLOS"); op.request_id = 0; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "alice", "savings", "20.000 GOLOS"))); BOOST_TEST_MESSAGE("--- failure withdrawing to non-existant account"); op.to = "sam"; op.amount = ASSET("1.000 GOLOS"); - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "account", "sam"))); BOOST_TEST_MESSAGE("--- success withdrawing GOLOS to self"); op.to = "alice"; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").savings_balance == ASSET("9.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 1); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).from == op.from); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).to == op.to); - BOOST_REQUIRE(to_string(db->get_savings_withdraw("alice", op.request_id).memo) == op.memo); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).request_id == op.request_id); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).amount == op.amount); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).complete == db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("0.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_balance, ASSET("9.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 1); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).from, op.from); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).to, op.to); + BOOST_CHECK_EQUAL(to_string(db->get_savings_withdraw("alice", op.request_id).memo), op.memo); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).request_id, op.request_id); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).amount, op.amount); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).complete, db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME); validate_database(); - BOOST_TEST_MESSAGE("--- success withdrawing GBG to self"); op.amount = ASSET("1.000 GBG"); op.request_id = 1; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(db->get_account("alice").savings_sbd_balance == ASSET("9.000 GBG")); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 2); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).from == op.from); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).to == op.to); - BOOST_REQUIRE(to_string(db->get_savings_withdraw("alice", op.request_id).memo) == op.memo); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).request_id == op.request_id); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).amount == op.amount); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).complete == db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + + BOOST_CHECK_EQUAL(db->get_account("alice").sbd_balance, ASSET("0.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_sbd_balance, ASSET("9.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 2); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).from, op.from); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).to, op.to); + BOOST_CHECK_EQUAL(to_string(db->get_savings_withdraw("alice", op.request_id).memo), op.memo); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).request_id, op.request_id); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).amount, op.amount); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).complete, db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME); validate_database(); - BOOST_TEST_MESSAGE("--- failure withdrawing with repeat request id"); op.amount = ASSET("2.000 GOLOS"); - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(object_already_exist, "savings_withdraw", + fc::mutable_variant_object()("owner","alice")("request_id",op.request_id)))); BOOST_TEST_MESSAGE("--- success withdrawing GOLOS to other"); op.to = "bob"; op.amount = ASSET("1.000 GOLOS"); op.request_id = 3; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").savings_balance == ASSET("8.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 3); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).from == op.from); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).to == op.to); - BOOST_REQUIRE(to_string(db->get_savings_withdraw("alice", op.request_id).memo) == op.memo); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).request_id == op.request_id); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).amount == op.amount); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).complete == db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("0.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_balance, ASSET("8.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 3); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).from, op.from); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).to, op.to); + BOOST_CHECK_EQUAL(to_string(db->get_savings_withdraw("alice", op.request_id).memo), op.memo); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).request_id, op.request_id); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).amount, op.amount); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).complete, db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME); validate_database(); - BOOST_TEST_MESSAGE("--- success withdrawing GBG to other"); op.amount = ASSET("1.000 GBG"); op.request_id = 4; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(db->get_account("alice").savings_sbd_balance == ASSET("8.000 GBG")); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 4); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).from == op.from); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).to == op.to); - BOOST_REQUIRE(to_string(db->get_savings_withdraw("alice", op.request_id).memo) == op.memo); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).request_id == op.request_id); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).amount == op.amount); - BOOST_REQUIRE(db->get_savings_withdraw("alice", op.request_id).complete == db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + + BOOST_CHECK_EQUAL(db->get_account("alice").sbd_balance, ASSET("0.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_sbd_balance, ASSET("8.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 4); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).from, op.from); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).to, op.to); + BOOST_CHECK_EQUAL(to_string(db->get_savings_withdraw("alice", op.request_id).memo), op.memo); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).request_id, op.request_id); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).amount, op.amount); + BOOST_CHECK_EQUAL(db->get_savings_withdraw("alice", op.request_id).complete, db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME); validate_database(); - BOOST_TEST_MESSAGE("--- withdraw on timeout"); generate_blocks(db->head_block_time() + STEEMIT_SAVINGS_WITHDRAW_TIME - fc::seconds(STEEMIT_BLOCK_INTERVAL), true); - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(db->get_account("bob").balance == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(db->get_account("bob").sbd_balance == ASSET("0.000 GBG")); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 4); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("0.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").sbd_balance, ASSET("0.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("bob").balance, ASSET("0.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("bob").sbd_balance, ASSET("0.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 4); validate_database(); generate_block(); - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("1.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").sbd_balance == ASSET("1.000 GBG")); - BOOST_REQUIRE(db->get_account("bob").balance == ASSET("1.000 GOLOS")); - BOOST_REQUIRE(db->get_account("bob").sbd_balance == ASSET("1.000 GBG")); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 0); + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("1.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").sbd_balance, ASSET("1.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("bob").balance, ASSET("1.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("bob").sbd_balance, ASSET("1.000 GBG")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 0); validate_database(); - BOOST_TEST_MESSAGE("--- savings withdraw request limit"); tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); op.to = "alice"; @@ -5691,41 +5786,74 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) for (int i = 0; i < STEEMIT_SAVINGS_WITHDRAW_REQUEST_LIMIT; i++) { op.request_id = i; - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == i + 1); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, i + 1); } op.request_id = STEEMIT_SAVINGS_WITHDRAW_REQUEST_LIMIT; - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == STEEMIT_SAVINGS_WITHDRAW_REQUEST_LIMIT); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::reached_limit_for_pending_withdraw_requests))); + + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, STEEMIT_SAVINGS_WITHDRAW_REQUEST_LIMIT); validate_database(); } FC_LOG_AND_RETHROW() } - BOOST_AUTO_TEST_CASE(cancel_transfer_from_savings_validate) { + BOOST_AUTO_TEST_CASE(transfer_from_savings_memo_storing_flag) { try { - BOOST_TEST_MESSAGE("Testing: cancel_transfer_from_savings_validate"); + BOOST_TEST_MESSAGE("Testing: transfer_from_savings_memo_storing_flag"); - cancel_transfer_from_savings_operation op; + ACTORS((alice)(bob)); + generate_block(); + fund("alice", ASSET("10.000 GOLOS")); + + transfer_to_savings_operation save; + save.from = "alice"; + save.to = "alice"; + save.amount = ASSET("10.000 GOLOS"); + signed_transaction tx; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, save)); + + // Default is true + transfer_from_savings_operation op; op.from = "alice"; - op.request_id = 0; + op.to = "alice"; + op.amount = ASSET("1.000 GOLOS"); + op.request_id = 1; + op.memo = "{\"test\":123}"; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(to_string(db->get_savings_withdraw("alice", op.request_id).memo), op.memo); + db->set_store_memo_in_savings_withdraws(true); + op.request_id = 2; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(to_string(db->get_savings_withdraw("alice", op.request_id).memo), op.memo); - BOOST_TEST_MESSAGE("--- failure when 'from' is empty"); - op.from = ""; - STEEMIT_REQUIRE_THROW(op.validate(), fc::exception); + db->set_store_memo_in_savings_withdraws(false); + op.request_id = 3; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + BOOST_CHECK_EQUAL(to_string(db->get_savings_withdraw("alice", op.request_id).memo), ""); + + validate_database(); + } + FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() // transfer_from_savings - BOOST_TEST_MESSAGE("--- sucess when 'from' is not empty"); + BOOST_AUTO_TEST_CASE(cancel_transfer_from_savings_validate) { + try { + BOOST_TEST_MESSAGE("Testing: cancel_transfer_from_savings_validate"); + cancel_transfer_from_savings_operation op; op.from = "alice"; - op.validate(); + op.request_id = 0; + BOOST_TEST_MESSAGE("--- sucess on valid params"); + CHECK_OP_VALID(op); + BOOST_TEST_MESSAGE("--- failure when 'from' is invalid"); + CHECK_PARAM_INVALID(op, from, ""); + } FC_LOG_AND_RETHROW() } @@ -5733,29 +5861,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(cancel_transfer_from_savings_authorities) { try { BOOST_TEST_MESSAGE("Testing: cancel_transfer_from_savings_authorities"); - cancel_transfer_from_savings_operation op; op.from = "alice"; - - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_active_authorities(auths); - expected.insert("alice"); - BOOST_REQUIRE(auths == expected); - - auths.clear(); - expected.clear(); - op.from = "bob"; - op.get_required_active_authorities(auths); - expected.insert("bob"); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"alice"}), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -5781,70 +5889,53 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) withdraw.amount = ASSET("3.000 GOLOS"); signed_transaction tx; - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.operations.push_back(save); - tx.operations.push_back(withdraw); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, save, withdraw)); validate_database(); - - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 1); - BOOST_REQUIRE(db->get_account("bob").savings_withdraw_requests == 0); - + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 1); + BOOST_CHECK_EQUAL(db->get_account("bob").savings_withdraw_requests, 0); BOOST_TEST_MESSAGE("--- Failure when there is no pending request"); cancel_transfer_from_savings_operation op; op.from = "alice"; op.request_id = 0; - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "savings_withdraw", + fc::mutable_variant_object()("account","alice")("request_id",op.request_id)))); validate_database(); - - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 1); - BOOST_REQUIRE(db->get_account("bob").savings_withdraw_requests == 0); - + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 1); + BOOST_CHECK_EQUAL(db->get_account("bob").savings_withdraw_requests, 0); BOOST_TEST_MESSAGE("--- Success"); op.request_id = 1; - - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - BOOST_REQUIRE(db->get_account("alice").balance == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").savings_balance == ASSET("10.000 GOLOS")); - BOOST_REQUIRE(db->get_account("alice").savings_withdraw_requests == 0); - BOOST_REQUIRE(db->get_account("bob").balance == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(db->get_account("bob").savings_balance == ASSET("0.000 GOLOS")); - BOOST_REQUIRE(db->get_account("bob").savings_withdraw_requests == 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + + BOOST_CHECK_EQUAL(db->get_account("alice").balance, ASSET("0.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_balance, ASSET("10.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("alice").savings_withdraw_requests, 0); + BOOST_CHECK_EQUAL(db->get_account("bob").balance, ASSET("0.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("bob").savings_balance, ASSET("0.000 GOLOS")); + BOOST_CHECK_EQUAL(db->get_account("bob").savings_withdraw_requests, 0); validate_database(); } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(decline_voting_rights_validate) { try { + BOOST_TEST_MESSAGE("Testing: decline_voting_rights_validate"); + decline_voting_rights_operation op; + op.account = "alice"; + CHECK_OP_VALID(op); + CHECK_PARAM_INVALID(op, account, ""); + } FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(decline_voting_rights_authorities) { try { BOOST_TEST_MESSAGE("Testing: decline_voting_rights_authorities"); - decline_voting_rights_operation op; op.account = "alice"; - - flat_set auths; - flat_set expected; - - op.get_required_active_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); - - expected.insert("alice"); - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set({"alice"}), account_name_set(), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -5864,78 +5955,51 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) proxy.proxy = "alice"; signed_transaction tx; - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.operations.push_back(proxy); - tx.sign(bob_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, proxy)); decline_voting_rights_operation op; op.account = "alice"; - BOOST_TEST_MESSAGE("--- success"); - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); - const auto &request_idx = db->get_index().indices().get(); + const auto& request_idx = db->get_index().indices().get(); auto itr = request_idx.find(db->get_account("alice").id); - BOOST_REQUIRE(itr != request_idx.end()); - BOOST_REQUIRE(itr->effective_date == db->head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD); - + BOOST_CHECK(itr != request_idx.end()); + BOOST_CHECK_EQUAL(itr->effective_date, db->head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD); BOOST_TEST_MESSAGE("--- failure revoking voting rights with existing request"); generate_block(); - tx.clear(); - tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(object_already_exist, "decline_voting_rights_request", "alice"))); BOOST_TEST_MESSAGE("--- successs cancelling a request"); op.decline = false; - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); itr = request_idx.find(db->get_account("alice").id); - BOOST_REQUIRE(itr == request_idx.end()); - + BOOST_CHECK(itr == request_idx.end()); BOOST_TEST_MESSAGE("--- failure cancelling a request that doesn't exist"); generate_block(); - tx.clear(); - tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); - + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "decline_voting_rights_request", "alice"))); BOOST_TEST_MESSAGE("--- check account can vote during waiting period"); op.decline = true; - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); generate_blocks( - db->head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD - - fc::seconds(STEEMIT_BLOCK_INTERVAL), true); - BOOST_REQUIRE(db->get_account("alice").can_vote); + db->head_block_time() + STEEMIT_OWNER_AUTH_RECOVERY_PERIOD - fc::seconds(STEEMIT_BLOCK_INTERVAL), true); + BOOST_CHECK(db->get_account("alice").can_vote); witness_create("alice", alice_private_key, "foo.bar", alice_private_key.get_public_key(), 0); account_witness_vote_operation witness_vote; witness_vote.account = "alice"; witness_vote.witness = "alice"; - tx.clear(); - tx.operations.push_back(witness_vote); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, witness_vote)); comment_operation comment; comment.author = "alice"; @@ -5948,55 +6012,45 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) vote.author = "alice"; vote.permlink = "test"; vote.weight = STEEMIT_100_PERCENT; - tx.clear(); - tx.operations.push_back(comment); - tx.operations.push_back(vote); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, comment, vote)); validate_database(); - BOOST_TEST_MESSAGE("--- check account cannot vote after request is processed"); generate_block(); - BOOST_REQUIRE(!db->get_account("alice").can_vote); + BOOST_CHECK(!db->get_account("alice").can_vote); validate_database(); itr = request_idx.find(db->get_account("alice").id); - BOOST_REQUIRE(itr == request_idx.end()); + BOOST_CHECK(itr == request_idx.end()); - const auto &witness_idx = db->get_index().indices().get(); + const auto& witness_idx = db->get_index().indices().get(); auto witness_itr = witness_idx.find( - boost::make_tuple(db->get_account("alice").id, db->get_witness("alice").id)); - BOOST_REQUIRE(witness_itr == witness_idx.end()); + boost::make_tuple(db->get_account("alice").id, db->get_witness("alice").id)); + BOOST_CHECK(witness_itr == witness_idx.end()); - tx.clear(); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.operations.push_back(witness_vote); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, witness_vote), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::voter_declined_voting_rights))); db->get( boost::make_tuple(db->get_comment("alice", string("test")).id, db->get_account("alice").id) ); vote.weight = 0; - tx.clear(); - tx.operations.push_back(vote); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, vote), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::voter_declined_voting_rights))); vote.weight = STEEMIT_1_PERCENT * 50; - tx.clear(); - tx.operations.push_back(vote); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, vote), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::voter_declined_voting_rights))); proxy.account = "alice"; proxy.proxy = "bob"; - tx.clear(); - tx.operations.push_back(proxy); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, proxy), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::voter_declined_voting_rights))); } FC_LOG_AND_RETHROW() } @@ -6063,7 +6117,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) comment_payout_beneficiaries b; b.beneficiaries.push_back(beneficiary_route_type(account_name_type("bob"), STEEMIT_100_PERCENT + 1)); op.extensions.insert(b); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "beneficiaries")); BOOST_TEST_MESSAGE("--- Testing more than 100% total weight"); b.beneficiaries.clear(); @@ -6071,7 +6126,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) b.beneficiaries.push_back(beneficiary_route_type(account_name_type("sam"), STEEMIT_1_PERCENT * 75)); op.extensions.clear(); op.extensions.insert(b); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "beneficiaries")); BOOST_TEST_MESSAGE("--- Testing maximum number of routes"); b.beneficiaries.clear(); @@ -6082,15 +6138,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.extensions.clear(); std::sort(b.beneficiaries.begin(), b.beneficiaries.end()); op.extensions.insert(b); - op.validate(); + BOOST_CHECK_NO_THROW(op.validate()); BOOST_TEST_MESSAGE("--- Testing one too many routes"); b.beneficiaries.push_back(beneficiary_route_type(account_name_type("bar"), 1)); std::sort(b.beneficiaries.begin(), b.beneficiaries.end()); op.extensions.clear(); op.extensions.insert(b); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); - + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "beneficiaries")); BOOST_TEST_MESSAGE("--- Testing duplicate accounts"); b.beneficiaries.clear(); @@ -6098,7 +6154,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) b.beneficiaries.push_back(beneficiary_route_type("bob", STEEMIT_1_PERCENT)); op.extensions.clear(); op.extensions.insert(b); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "beneficiaries")); BOOST_TEST_MESSAGE("--- Testing incorrect account sort order"); b.beneficiaries.clear(); @@ -6106,7 +6163,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) b.beneficiaries.push_back(beneficiary_route_type("alice", STEEMIT_1_PERCENT)); op.extensions.clear(); op.extensions.insert(b); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(op.validate(), + CHECK_ERROR(invalid_parameter, "beneficiaries")); BOOST_TEST_MESSAGE("--- Testing correct account sort order"); b.beneficiaries.clear(); @@ -6114,7 +6172,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) b.beneficiaries.push_back(beneficiary_route_type("bob", STEEMIT_1_PERCENT)); op.extensions.clear(); op.extensions.insert(b); - op.validate(); + BOOST_CHECK_NO_THROW(op.validate()); } FC_LOG_AND_RETHROW() } @@ -6146,10 +6204,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) comment.title = "test"; comment.body = "foobar"; - tx.operations.push_back(comment); - tx.set_expiration(db->head_block_time() + STEEMIT_MIN_TRANSACTION_EXPIRATION_LIMIT); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, comment)); BOOST_TEST_MESSAGE("--- Test failure on max of benefactors"); b.beneficiaries.push_back(beneficiary_route_type(account_name_type("bob"), STEEMIT_1_PERCENT)); @@ -6165,10 +6220,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.permlink = "test"; op.allow_curation_rewards = false; op.extensions.insert(b); - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cannot_specify_more_beneficiaries))); BOOST_TEST_MESSAGE("--- Test specifying a non-existent benefactor"); @@ -6176,10 +6230,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) b.beneficiaries.push_back(beneficiary_route_type(account_name_type("dave"), STEEMIT_1_PERCENT)); op.extensions.clear(); op.extensions.insert(b); - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "account", "dave"))); BOOST_TEST_MESSAGE("--- Test setting when comment has been voted on"); @@ -6198,14 +6251,13 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) tx.operations.push_back(op); tx.sign(alice_private_key, db->get_chain_id()); tx.sign(bob_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(db->push_transaction(tx), + CHECK_ERROR(tx_invalid_operation, 1, + CHECK_ERROR(logic_exception, logic_exception::comment_options_requires_no_rshares))); BOOST_TEST_MESSAGE("--- Test success"); - tx.clear(); - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); BOOST_TEST_MESSAGE("--- Test setting when there are already beneficiaries"); @@ -6213,8 +6265,100 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) b.beneficiaries.push_back(beneficiary_route_type(account_name_type("sam"), 25 * STEEMIT_1_PERCENT)); op.extensions.clear(); op.extensions.insert(b); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::comment_already_has_beneficiaries))); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(delete_comment_validate) { + try { + BOOST_TEST_MESSAGE("Testing: delete_comment_validate"); + + delete_comment_operation op; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + op.author = "alice"; + op.permlink = "foo"; + CHECK_OP_VALID(op); + + BOOST_TEST_MESSAGE("--- failed when 'author' is invalid"); + CHECK_PARAM_INVALID(op, author, ""); + CHECK_PARAM_INVALID(op, author, "a"); + + BOOST_TEST_MESSAGE("--- failed when 'permlink' is invalid"); + CHECK_PARAM_INVALID(op, permlink, std::string(STEEMIT_MAX_PERMLINK_LENGTH, ' ')); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(delete_comment_authorities) { + try { + BOOST_TEST_MESSAGE("Testing: delete_comment_authorities"); + delete_comment_operation op; + op.author = "alice"; + op.permlink = "foo"; + CHECK_OP_AUTHS(op, account_name_set(), account_name_set(), account_name_set({"alice"})); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(delete_comment_apply) { + try { + BOOST_TEST_MESSAGE("Testing: delete_comment_apply"); + ACTORS((alice)(bob)) + + signed_transaction tx; + delete_comment_operation op; + + BOOST_TEST_MESSAGE("--- failed when comment missing"); + op.author = "alice"; + op.permlink = "foo"; + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(missing_object, "comment", make_comment_id("alice", "foo")))); + validate_database(); + + BOOST_TEST_MESSAGE("--- prepare testing comments"); + { + comment_operation op; + op.author = "alice"; + op.permlink = "lorem"; + op.parent_author = ""; + op.parent_permlink = "ipsum"; + op.title = "Lorem Ipsum"; + op.body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + op.json_metadata = "{\"foo\":\"bar\"}"; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + + op.author = "bob"; + op.permlink = "bar"; + op.parent_author = "alice"; + op.parent_permlink = "lorem"; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); + validate_database(); + } + + BOOST_TEST_MESSAGE("--- failed when comment has replies"); + op.author = "alice"; + op.permlink = "lorem"; + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cannot_delete_comment_with_replies))); + validate_database(); + + BOOST_TEST_MESSAGE("--- success delete comment"); + op.author = "bob"; + op.permlink = "bar"; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); + validate_database(); + + BOOST_TEST_MESSAGE("--- success delete comment after delete replies"); + op.author = "alice"; + op.permlink = "lorem"; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + validate_database(); } FC_LOG_AND_RETHROW() } @@ -6223,8 +6367,12 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(account_create_with_delegation_validate) { try { + BOOST_TEST_MESSAGE("Testing: account_create_with_delegation_validate"); + + BOOST_TEST_MESSAGE("--- Test valid operation"); account_create_with_delegation_operation op; private_key_type priv_key = generate_private_key("temp_key"); + op.fee = ASSET_GOLOS(10); op.delegation = ASSET_GESTS(100); op.creator = "alice"; op.new_account_name = "bob"; @@ -6232,28 +6380,30 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.active = authority(1, priv_key.get_public_key(), 1); op.memo_key = priv_key.get_public_key(); op.json_metadata = "{\"foo\":\"bar\"}"; + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, json_metadata, ""); + CHECK_PARAM_VALID(op, json_metadata, "{\"a\":\"тест\"}"); BOOST_TEST_MESSAGE("--- Test failing on negative fee"); - op.fee = ASSET_GOLOS(-1); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + CHECK_PARAM_INVALID(op, fee, ASSET_GOLOS(-1)); + + BOOST_TEST_MESSAGE("--- Test failing when fee is not GOLOS"); + CHECK_PARAM_INVALID(op, fee, ASSET_GBG(10)); BOOST_TEST_MESSAGE("--- Test failing on negative delegation"); - op.fee = ASSET_GOLOS(10); - op.delegation = ASSET_GESTS(-1); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + CHECK_PARAM_INVALID(op, delegation, ASSET_GESTS(-1)); BOOST_TEST_MESSAGE("--- Test failing when delegation is not VESTS"); - op.delegation = ASSET_GOLOS(100); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + CHECK_PARAM_INVALID(op, delegation, ASSET_GOLOS(100)); - BOOST_TEST_MESSAGE("--- Test failing when fee is not GOLOS"); - op.fee = ASSET_GBG(10); - op.delegation = ASSET_GESTS(100); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + BOOST_TEST_MESSAGE("--- Test failing when account empty"); + CHECK_PARAM_INVALID(op, creator, ""); + CHECK_PARAM_INVALID(op, new_account_name, ""); - BOOST_TEST_MESSAGE("--- Test valid operation"); - op.fee = ASSET_GOLOS(10); - op.validate(); + BOOST_TEST_MESSAGE("--- Test failing when json_metadata invalid"); + CHECK_PARAM_INVALID(op, json_metadata, "{a:b}"); + + // TODO: owner/active/posting } FC_LOG_AND_RETHROW() } @@ -6262,21 +6412,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) try { BOOST_TEST_MESSAGE("Testing: account_create_with_delegation_authorities"); account_create_with_delegation_operation op; - op.creator = "alice"; - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - expected.insert("alice"); - op.get_required_active_authorities(auths); - BOOST_REQUIRE(auths == expected); - - expected.clear(); - auths.clear(); - op.get_required_posting_authorities(auths); - BOOST_REQUIRE(auths == expected); + op.creator = "bob"; + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"bob"}), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -6315,29 +6452,29 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.memo_key = priv_key.get_public_key(); op.json_metadata = "{\"foo\":\"bar\"}"; sign_tx_with_ops(tx, alice_private_key, withdraw, op); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::assert_exception); + GOLOS_CHECK_THROW_PROPS(db->push_transaction(tx, 0), tx_invalid_operation, {}); BOOST_TEST_MESSAGE("--- Test success under normal conditions"); - push_tx_with_ops(tx, alice_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); const account_object& bob_acc = db->get_account("bob"); const account_object& alice_acc = db->get_account("alice"); - BOOST_REQUIRE(alice_acc.delegated_vesting_shares == ASSET_GESTS(1e7)); - BOOST_REQUIRE(bob_acc.received_vesting_shares == ASSET_GESTS(1e7)); - BOOST_REQUIRE(bob_acc.available_vesting_shares(true) == + BOOST_CHECK_EQUAL(alice_acc.delegated_vesting_shares, ASSET_GESTS(1e7)); + BOOST_CHECK_EQUAL(bob_acc.received_vesting_shares, ASSET_GESTS(1e7)); + BOOST_CHECK_EQUAL(bob_acc.available_vesting_shares(true), bob_acc.vesting_shares - bob_acc.delegated_vesting_shares); - BOOST_REQUIRE(bob_acc.available_vesting_shares() == + BOOST_CHECK_EQUAL(bob_acc.available_vesting_shares(), bob_acc.vesting_shares - bob_acc.delegated_vesting_shares); - BOOST_REQUIRE(bob_acc.effective_vesting_shares() == + BOOST_CHECK_EQUAL(bob_acc.effective_vesting_shares(), bob_acc.vesting_shares - bob_acc.delegated_vesting_shares + bob_acc.received_vesting_shares); BOOST_TEST_MESSAGE("--- Test delegation object integrity"); auto delegation = db->find(std::make_tuple(op.creator, op.new_account_name)); - BOOST_REQUIRE(delegation != nullptr); - BOOST_REQUIRE(delegation->delegator == op.creator); - BOOST_REQUIRE(delegation->delegatee == op.new_account_name); - BOOST_REQUIRE(delegation->vesting_shares == ASSET_GESTS(1e7)); - BOOST_REQUIRE(delegation->min_delegation_time == db->head_block_time() + GOLOS_CREATE_ACCOUNT_DELEGATION_TIME); + BOOST_CHECK(delegation != nullptr); + BOOST_CHECK_EQUAL(delegation->delegator, op.creator); + BOOST_CHECK_EQUAL(delegation->delegatee, op.new_account_name); + BOOST_CHECK_EQUAL(delegation->vesting_shares, ASSET_GESTS(1e7)); + BOOST_CHECK_EQUAL(delegation->min_delegation_time, db->head_block_time() + GOLOS_CREATE_ACCOUNT_DELEGATION_TIME); auto delegated = delegation->vesting_shares; auto exp_time = delegation->min_delegation_time; @@ -6353,35 +6490,35 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.delegation = ASSET_GESTS(0); op.new_account_name = "sam"; fund("alice", op.fee); - push_tx_with_ops(tx, alice_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); BOOST_TEST_MESSAGE("--- Test success using minimum GOLOS fee"); op.fee = min_fee; op.delegation = (required_fee - min_fee) * gp.get_vesting_share_price(); op.new_account_name = "pam"; fund("alice", op.fee); - push_tx_with_ops(tx, alice_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); BOOST_TEST_MESSAGE("--- Test success using both GESTS and GOLOS to reach target delegation"); op.fee = asset(required_fee.amount / 2 + 1, STEEM_SYMBOL); op.delegation = asset(required_gests.amount / 2 + 1, VESTS_SYMBOL); op.new_account_name = "ram"; fund("alice", op.fee); - push_tx_with_ops(tx, alice_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); BOOST_TEST_MESSAGE("--- Test failure when insufficient funds to process transaction"); op.fee = ASSET_GOLOS(10); op.delegation = ASSET_GESTS(0); op.new_account_name = "dave"; sign_tx_with_ops(tx, alice_private_key, op); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_THROW_PROPS(db->push_transaction(tx, 0), fc::exception, {}); BOOST_TEST_MESSAGE("--- Test failure when insufficient fee to reach target delegation"); fund("alice", required_fee); op.fee = ASSET_GOLOS(0); op.delegation = required_gests - ASSET_GESTS(1); sign_tx_with_ops(tx, alice_private_key, op); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + GOLOS_CHECK_THROW_PROPS(db->push_transaction(tx, 0), fc::exception, {}); validate_database(); BOOST_TEST_MESSAGE("--- Test removing delegation from new account"); @@ -6389,14 +6526,14 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) delegate.delegator = "alice"; delegate.delegatee = "bob"; delegate.vesting_shares = ASSET_GESTS(0); - push_tx_with_ops(tx, alice_private_key, delegate); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, delegate)); auto itr = db->get_index().begin(); auto end = db->get_index().end(); - BOOST_REQUIRE(itr != end); - BOOST_REQUIRE(itr->delegator == "alice"); - BOOST_REQUIRE(itr->vesting_shares == delegated); - BOOST_REQUIRE(itr->expiration == exp_time); + BOOST_CHECK(itr != end); + BOOST_CHECK_EQUAL(itr->delegator, "alice"); + BOOST_CHECK_EQUAL(itr->vesting_shares, delegated); + BOOST_CHECK_EQUAL(itr->expiration, exp_time); validate_database(); } FC_LOG_AND_RETHROW() @@ -6407,18 +6544,20 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) delegate_vesting_shares_operation op; op.delegator = "alice"; op.delegatee = "bob"; + op.vesting_shares = ASSET_GESTS(1e6); + BOOST_TEST_MESSAGE("--- Test success under normal conditions"); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, vesting_shares, ASSET_GESTS(0)); + BOOST_TEST_MESSAGE("--- Test failure when delegate negative amount"); - op.vesting_shares = ASSET_GESTS(-1); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + CHECK_PARAM_INVALID(op, vesting_shares, ASSET_GESTS(-1)); BOOST_TEST_MESSAGE("--- Test failure when delegate to same acc"); - op.delegator = "bob"; - op.vesting_shares = ASSET_GESTS(1e6); - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + CHECK_PARAM_INVALID_LOGIC(op, delegator, "bob", logic_exception::cannot_delegate_to_yourself); - BOOST_TEST_MESSAGE("--- Test success under normal conditions"); - op.delegator = "alice"; - op.validate(); + BOOST_TEST_MESSAGE("--- Test failure when account not set"); + CHECK_PARAM_INVALID(op, delegator, ""); + CHECK_PARAM_INVALID(op, delegatee, ""); } FC_LOG_AND_RETHROW() } @@ -6426,42 +6565,11 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_CASE(delegate_vesting_shares_authorities) { try { BOOST_TEST_MESSAGE("Testing: delegate_vesting_shares_authorities"); - signed_transaction tx; - ACTORS((alice)(bob)) - generate_blocks(1); - vest("alice", ASSET_GOLOS(10000)); - delegate_vesting_shares_operation op; - op.vesting_shares = ASSET_GESTS(300); - op.delegator = "alice"; - op.delegatee = "bob"; - - BOOST_TEST_MESSAGE("--- Test failure when no signatures"); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.operations.push_back(op); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); - - BOOST_TEST_MESSAGE("--- Test success with delegator signature"); - sign(tx, alice_private_key); - db->push_transaction(tx, 0); - - BOOST_TEST_MESSAGE("--- Test failure when duplicate signatures"); - op.delegatee = "sam"; - sign_tx_with_ops(tx, alice_private_key, op); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_duplicate_sig); - - BOOST_TEST_MESSAGE("--- Test failure when signed by an additional signature not in the creator's authority"); - tx.signatures.clear(); - tx.sign(init_account_priv_key, db->get_chain_id()); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_irrelevant_sig); - - BOOST_TEST_MESSAGE("--- Test failure when signed by a signature not in the creator's authority"); - tx.signatures.clear(); - tx.sign(init_account_priv_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), tx_missing_active_auth); - validate_database(); + op.delegator = "bob"; + op.delegatee = "alice"; + op.vesting_shares = ASSET_GESTS(1e6); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"bob"}), account_name_set()); } FC_LOG_AND_RETHROW() } @@ -6487,29 +6595,29 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.vesting_shares = ASSET_GESTS(1e6); op.delegator = "alice"; op.delegatee = "bob"; - push_tx_with_ops(tx, alice_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); generate_blocks(1); const auto& alice_acc = db->get_account("alice"); const auto& bob_acc = db->get_account("bob"); - BOOST_REQUIRE(alice_acc.delegated_vesting_shares == ASSET_GESTS(1e6)); - BOOST_REQUIRE(bob_acc.received_vesting_shares == ASSET_GESTS(1e6)); + BOOST_CHECK_EQUAL(alice_acc.delegated_vesting_shares, ASSET_GESTS(1e6)); + BOOST_CHECK_EQUAL(bob_acc.received_vesting_shares, ASSET_GESTS(1e6)); BOOST_TEST_MESSAGE("--- Test that the delegation object is correct"); auto delegation = db->find(std::make_tuple(op.delegator, op.delegatee)); - BOOST_REQUIRE(delegation != nullptr); - BOOST_REQUIRE(delegation->delegator == op.delegator); - BOOST_REQUIRE(delegation->delegatee == op.delegatee); - BOOST_REQUIRE(delegation->vesting_shares == ASSET_GESTS(1e6)); + BOOST_CHECK(delegation != nullptr); + BOOST_CHECK_EQUAL(delegation->delegator, op.delegator); + BOOST_CHECK_EQUAL(delegation->delegatee, op.delegatee); + BOOST_CHECK_EQUAL(delegation->vesting_shares, ASSET_GESTS(1e6)); validate_database(); BOOST_TEST_MESSAGE("--- Test delegation change"); op.vesting_shares = ASSET_GESTS(2e7); - push_tx_with_ops(tx, alice_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); generate_blocks(1); - BOOST_REQUIRE(delegation != nullptr); - BOOST_REQUIRE(delegation->vesting_shares == ASSET_GESTS(2e7)); - BOOST_REQUIRE(alice_acc.delegated_vesting_shares == ASSET_GESTS(2e7)); - BOOST_REQUIRE(bob_acc.received_vesting_shares == ASSET_GESTS(2e7)); + BOOST_CHECK(delegation != nullptr); + BOOST_CHECK_EQUAL(delegation->vesting_shares, ASSET_GESTS(2e7)); + BOOST_CHECK_EQUAL(alice_acc.delegated_vesting_shares, ASSET_GESTS(2e7)); + BOOST_CHECK_EQUAL(bob_acc.received_vesting_shares, ASSET_GESTS(2e7)); // TODO: test min delta evaluator logic @@ -6520,7 +6628,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) comment_op.parent_permlink = "test"; comment_op.title = "bar"; comment_op.body = "foo bar"; - push_tx_with_ops(tx, alice_private_key, comment_op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, comment_op)); auto old_voting_power = bob_acc.voting_power; vote_operation vote_op; @@ -6528,7 +6636,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) vote_op.author = "alice"; vote_op.permlink = "foo"; vote_op.weight = STEEMIT_100_PERCENT; - push_tx_with_ops(tx, bob_private_key, vote_op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, vote_op)); generate_blocks(1); auto& alice_comment = db->get_comment("alice", string("foo")); @@ -6536,18 +6644,19 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) auto itr = vote_idx.find(std::make_tuple(alice_comment.id, bob_acc.id)); auto rshares = bob_acc.effective_vesting_shares().amount.value * (old_voting_power - bob_acc.voting_power) / STEEMIT_100_PERCENT; - BOOST_REQUIRE(rshares == itr->rshares); - BOOST_REQUIRE(rshares == alice_comment.net_rshares.value); + BOOST_CHECK_EQUAL(rshares, itr->rshares); + BOOST_CHECK_EQUAL(rshares, alice_comment.net_rshares.value); BOOST_TEST_MESSAGE("--- Test that delegation limited by current voting power"); auto max_allowed = bob_acc.vesting_shares * bob_acc.voting_power / STEEMIT_100_PERCENT; op.delegator = "bob"; op.delegatee = "alice"; op.vesting_shares = asset(max_allowed.amount + 1, VESTS_SYMBOL); - sign_tx_with_ops(tx, bob_private_key, op); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::delegation_limited_by_voting_power))); op.vesting_shares = max_allowed; - push_tx_with_ops(tx, bob_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, op)); generate_block(); ACTORS((sam)(dave)) @@ -6561,13 +6670,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) op.vesting_shares = ASSET_GESTS(0); op.delegator = "sam"; op.delegatee = "dave"; - sign_tx_with_ops(tx, sam_private_key, op); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, sam_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::delegation_difference_too_low))); BOOST_TEST_MESSAGE("--- Test failure delegating more vesting shares than account has"); op.vesting_shares = asset(sam_vest.amount + 1, VESTS_SYMBOL); - sign_tx_with_ops(tx, sam_private_key, op); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, sam_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "sam", "available vesting shares", op.vesting_shares))); BOOST_TEST_MESSAGE("--- Test failure delegating vesting shares that are part of a power down"); sam_vest = asset(sam_vest.amount / 2, VESTS_SYMBOL); @@ -6575,74 +6686,75 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) withdraw.account = "sam"; withdraw.vesting_shares = sam_vest; op.vesting_shares = asset(sam_vest.amount + 2, VESTS_SYMBOL); - push_tx_with_ops(tx, sam_private_key, withdraw); - sign_tx_with_ops(tx, sam_private_key, op); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, withdraw)); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, sam_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "sam", "available vesting shares", op.vesting_shares))); BOOST_TEST_MESSAGE("--- Test available_vesting_shares calculation with active power down"); - BOOST_REQUIRE(sam_acc.available_vesting_shares(true) == + BOOST_CHECK_EQUAL(sam_acc.available_vesting_shares(true), sam_acc.vesting_shares - sam_acc.delegated_vesting_shares - asset(sam_acc.to_withdraw, VESTS_SYMBOL)); withdraw.vesting_shares = ASSET_GESTS(0); - push_tx_with_ops(tx, sam_private_key, withdraw); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, withdraw)); BOOST_TEST_MESSAGE("--- Test failure powering down vesting shares that are delegated"); sam_vest.amount += 1000; op.vesting_shares = sam_vest; withdraw.vesting_shares = asset(sam_vest.amount, VESTS_SYMBOL); - push_tx_with_ops(tx, sam_private_key, op); - sign_tx_with_ops(tx, sam_private_key, withdraw); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx), fc::assert_exception); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, sam_private_key, withdraw), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(insufficient_funds, "sam", "having vesting shares", sam_vest))); BOOST_TEST_MESSAGE("--- Remove a delegation and ensure it is returned after 1 week"); op.vesting_shares = ASSET_GESTS(0); - push_tx_with_ops(tx, sam_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, sam_private_key, op)); auto exp_obj = db->get_index().begin(); auto end = db->get_index().end(); - BOOST_REQUIRE(exp_obj != end); - BOOST_REQUIRE(exp_obj->delegator == "sam"); - BOOST_REQUIRE(exp_obj->vesting_shares == sam_vest); - BOOST_REQUIRE(exp_obj->expiration == db->head_block_time() + STEEMIT_CASHOUT_WINDOW_SECONDS); - BOOST_REQUIRE(db->get_account("sam").delegated_vesting_shares == sam_vest); - BOOST_REQUIRE(db->get_account("dave").received_vesting_shares == ASSET_GESTS(0)); + BOOST_CHECK(exp_obj != end); + BOOST_CHECK_EQUAL(exp_obj->delegator, "sam"); + BOOST_CHECK_EQUAL(exp_obj->vesting_shares, sam_vest); + BOOST_CHECK_EQUAL(exp_obj->expiration, db->head_block_time() + STEEMIT_CASHOUT_WINDOW_SECONDS); + BOOST_CHECK_EQUAL(db->get_account("sam").delegated_vesting_shares, sam_vest); + BOOST_CHECK_EQUAL(db->get_account("dave").received_vesting_shares, ASSET_GESTS(0)); delegation = db->find(std::make_tuple(op.delegator, op.delegatee)); - BOOST_REQUIRE(delegation == nullptr); + BOOST_CHECK(delegation == nullptr); generate_blocks(exp_obj->expiration + STEEMIT_BLOCK_INTERVAL); exp_obj = db->get_index().begin(); end = db->get_index().end(); - BOOST_REQUIRE(exp_obj == end); - BOOST_REQUIRE(db->get_account("sam").delegated_vesting_shares == ASSET_GESTS(0)); + BOOST_CHECK(exp_obj == end); + BOOST_CHECK_EQUAL(db->get_account("sam").delegated_vesting_shares, ASSET_GESTS(0)); } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() // delegation + BOOST_AUTO_TEST_SUITE(account_metadata) BOOST_AUTO_TEST_CASE(account_metadata_validate) { try { BOOST_TEST_MESSAGE("Testing: account_metadata_validate"); + + BOOST_TEST_MESSAGE("--- Test success under normal conditions"); account_metadata_operation op; - BOOST_TEST_MESSAGE("--- Test failure when bad account name passed"); - op.account = "-bad-"; + op.account = "bob"; op.json_metadata = "{}"; - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, json_metadata, "{\"a\":\"тест\"}") + + BOOST_TEST_MESSAGE("--- Test failure when bad account name passed"); + CHECK_PARAM_INVALID(op, account, "-bad-"); BOOST_TEST_MESSAGE("--- Test failure when json_metadata is empty"); - op.account = "alice"; - op.json_metadata = ""; - STEEMIT_REQUIRE_THROW(op.validate(), fc::assert_exception); + CHECK_PARAM_INVALID(op, json_metadata, ""); BOOST_TEST_MESSAGE("--- Test failure when json_metadata is invalid JSON"); - op.json_metadata = "{test:fail}"; - STEEMIT_REQUIRE_THROW(op.validate(), fc::parse_error_exception); - - BOOST_TEST_MESSAGE("--- Test success under normal conditions"); - op.json_metadata = "{}"; - op.validate(); + CHECK_PARAM_INVALID(op, json_metadata, "{test:fail}"); } FC_LOG_AND_RETHROW() } @@ -6651,27 +6763,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) try { BOOST_TEST_MESSAGE("Testing: account_metadata_authorities"); account_metadata_operation op; - op.account = "alice"; + op.account = "bob"; op.json_metadata = "{}"; - flat_set auths; - flat_set expected; - - op.get_required_owner_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_active_authorities(auths); - BOOST_REQUIRE(auths == expected); - - op.get_required_posting_authorities(auths); - expected.insert("alice"); - BOOST_REQUIRE(auths == expected); + CHECK_OP_AUTHS(op, account_name_set(), account_name_set(), account_name_set({"bob"})); } FC_LOG_AND_RETHROW() } - BOOST_AUTO_TEST_CASE(account_metadata_apply) { + // WARNING: it should be before another metadata apply tests + BOOST_AUTO_TEST_CASE(account_metadata_apply_store_for_all) { try { - BOOST_TEST_MESSAGE("Testing: account_metadata_apply"); + BOOST_TEST_MESSAGE("Testing: account_metadata_apply_store_for_all"); + + // Do not set any settings related to storing of account metadata + // and it should store for all. + signed_transaction tx; ACTOR(alice); @@ -6682,23 +6788,26 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) account_metadata_operation op; op.account = "alice"; op.json_metadata = json; - push_tx_with_ops(tx, alice_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); generate_blocks(10); - auto acc = db->get_account("alice"); -#ifndef IS_LOW_MEM + auto alice_acc = db->get_account("alice"); auto meta = db->get("alice"); - BOOST_REQUIRE(meta.account == "alice"); - BOOST_REQUIRE(meta.json_metadata == json); -#endif - BOOST_REQUIRE(acc.last_account_update == now); + BOOST_CHECK_EQUAL(meta.account, "alice"); + BOOST_CHECK_EQUAL(meta.json_metadata, json); + BOOST_CHECK_EQUAL(alice_acc.last_account_update, now); + + BOOST_TEST_MESSAGE("----- Test API"); + account_api_object alice_api(alice_acc, *db); + BOOST_CHECK_EQUAL(alice_api.json_metadata, json); -#ifndef IS_LOW_MEM BOOST_TEST_MESSAGE("--- Test existance of account_metadata_object after account_create"); + // bob is created before all metadata storing settings + // therefore it should have account_metadata_object ACTOR(bob); // create_account with json_metadata = "" - meta = db->get("bob"); - BOOST_REQUIRE(meta.account == "bob"); - BOOST_REQUIRE(meta.json_metadata == ""); + meta = db->get("bob"); // just checks presence, throws on fail + BOOST_CHECK_EQUAL(meta.account, "bob"); + BOOST_CHECK_EQUAL(meta.json_metadata, ""); BOOST_TEST_MESSAGE("--- Test existance of account_metadata_object after account_create_with_delegation"); generate_blocks(1); @@ -6712,13 +6821,122 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) cr.owner = authority(1, priv_key.get_public_key(), 1); cr.active = authority(1, priv_key.get_public_key(), 1); cr.memo_key = priv_key.get_public_key(); - // cr.json_metadata = ""; // don't set - push_tx_with_ops(tx, bob_private_key, cr); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cr)); meta = db->get("sam"); - BOOST_REQUIRE(meta.account == "sam"); - BOOST_REQUIRE(meta.json_metadata == ""); -#endif + BOOST_CHECK_EQUAL(meta.account, "sam"); + BOOST_CHECK_EQUAL(meta.json_metadata, ""); + validate_database(); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(account_metadata_apply_store_for_nobody) { + try { + BOOST_TEST_MESSAGE("Testing: account_metadata_apply_store_for_nobody"); + + db->set_store_account_metadata(database::store_metadata_for_nobody); + // Add account to list to check restriction works + std::vector accs_v; + accs_v.push_back("alice"); + accs_v.push_back("sam"); + db->set_accounts_to_store_metadata(accs_v); + + signed_transaction tx; + ACTOR(alice); + + BOOST_TEST_MESSAGE("--- Test success under normal conditions"); + generate_blocks(10); + const auto json = "{\"test\":1}"; + auto now = db->head_block_time(); + account_metadata_operation op; + op.account = "alice"; + op.json_metadata = json; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + generate_blocks(10); + + auto alice_acc = db->get_account("alice"); + auto meta = db->find("alice"); + BOOST_CHECK(meta == nullptr); + BOOST_CHECK_EQUAL(alice_acc.last_account_update, now); + + BOOST_TEST_MESSAGE("----- Test API"); + account_api_object alice_api(alice_acc, *db); + BOOST_CHECK_EQUAL(alice_api.json_metadata, ""); + + ACTOR(bob); // create_account with json_metadata = "" + + BOOST_TEST_MESSAGE("--- Test existance of account_metadata_object after account_create_with_delegation"); + generate_blocks(1); + fund("bob", ASSET_GOLOS(1000)); + private_key_type priv_key = generate_private_key("temp_key"); + account_create_with_delegation_operation cr; + cr.fee = ASSET_GOLOS(1000); + cr.delegation = ASSET_GESTS(0); + cr.creator = "bob"; + cr.new_account_name = "sam"; + cr.owner = authority(1, priv_key.get_public_key(), 1); + cr.active = authority(1, priv_key.get_public_key(), 1); + cr.memo_key = priv_key.get_public_key(); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cr)); + + meta = db->find("sam"); + BOOST_CHECK(meta == nullptr); + validate_database(); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(account_metadata_apply_store_for_listed) { + try { + BOOST_TEST_MESSAGE("Testing: account_metadata_apply_store_for_listed"); + + db->set_store_account_metadata(database::store_metadata_for_listed); + // Add account to list to check restriction works + std::vector accs_v; + accs_v.push_back("sam"); + db->set_accounts_to_store_metadata(accs_v); + + signed_transaction tx; + ACTOR(alice); + + BOOST_TEST_MESSAGE("--- Test success under normal conditions"); + generate_blocks(10); + const auto json = "{\"test\":1}"; + auto now = db->head_block_time(); + account_metadata_operation op; + op.account = "alice"; + op.json_metadata = json; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); + generate_blocks(10); + + auto alice_acc = db->get_account("alice"); + auto meta = db->find("alice"); + BOOST_CHECK(meta == nullptr); + BOOST_CHECK_EQUAL(alice_acc.last_account_update, now); + + BOOST_TEST_MESSAGE("----- Test API"); + account_api_object alice_api(alice_acc, *db); + BOOST_CHECK_EQUAL(alice_api.json_metadata, ""); + + ACTOR(bob); // create_account with json_metadata = "" + + BOOST_TEST_MESSAGE("--- Test existance of account_metadata_object after account_create_with_delegation"); + generate_blocks(1); + fund("bob", ASSET_GOLOS(1000)); + private_key_type priv_key = generate_private_key("temp_key"); + account_create_with_delegation_operation cr; + cr.fee = ASSET_GOLOS(1000); + cr.delegation = ASSET_GESTS(0); + cr.creator = "bob"; + cr.new_account_name = "sam"; + cr.owner = authority(1, priv_key.get_public_key(), 1); + cr.active = authority(1, priv_key.get_public_key(), 1); + cr.memo_key = priv_key.get_public_key(); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cr)); + + meta = db->find("sam"); + BOOST_CHECK(meta != nullptr); validate_database(); } FC_LOG_AND_RETHROW() diff --git a/tests/tests/operation_time_tests.cpp b/tests/tests/operation_time_tests.cpp index 07eddf191e..a89c57a6e0 100644 --- a/tests/tests/operation_time_tests.cpp +++ b/tests/tests/operation_time_tests.cpp @@ -3,19 +3,18 @@ #include #include - #include #include #include -#include #include - +#include #include #include #include "database_fixture.hpp" #include "comment_reward.hpp" +#include "helpers.hpp" #include @@ -37,6 +36,10 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) fund("dave", 5000); vest("dave", 5000); + BOOST_CHECK(db->has_index()); + + const auto& cr_idx = db->get_index().indices().get(); + price exchange_rate(ASSET("1.000 GOLOS"), ASSET("1.000 GBG")); set_price_feed(exchange_rate); @@ -108,7 +111,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) BOOST_REQUIRE(gpo.total_reward_shares2 == total_comment_fund.reward_shares()); BOOST_REQUIRE(gpo.total_vesting_shares == total_comment_fund.vesting_shares()); BOOST_REQUIRE(gpo.total_vesting_fund_steem == total_comment_fund.vesting_fund()); - BOOST_REQUIRE(bob_comment.total_payout_value == bob_comment_reward.total_payout()); + auto bob_cr_itr = cr_idx.find(bob_comment.id); + BOOST_CHECK(bob_cr_itr != cr_idx.end()); + BOOST_CHECK_EQUAL(bob_cr_itr->total_payout_value, bob_comment_reward.total_payout()); bob_sbd_balance += bob_comment_reward.sbd_payout(); BOOST_REQUIRE(bob_account.sbd_balance == bob_sbd_balance); @@ -189,8 +194,10 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) vest("sam", 8000); fund("dave", 5000); vest("dave", 5000); - - db->set_clear_votes(0xFFFFFFFF); + + BOOST_CHECK(db->has_index()); + + const auto& cr_idx = db->get_index().indices().get(); price exchange_rate(ASSET("1.000 GOLOS"), ASSET("1.000 GBG")); set_price_feed(exchange_rate); @@ -328,7 +335,9 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) alice_comment_reward.vote_payout(bob_account); BOOST_REQUIRE(bob_comment.net_rshares.value == 0); - BOOST_REQUIRE(bob_comment.total_payout_value == bob_comment_reward.total_payout()); + auto bob_cr_itr = cr_idx.find(bob_comment.id); + BOOST_CHECK(bob_cr_itr != cr_idx.end()); + BOOST_CHECK_EQUAL(bob_cr_itr->total_payout_value, bob_comment_reward.total_payout()); BOOST_REQUIRE(bob_account.sbd_balance == bob_sbd_balance + bob_comment_reward.sbd_payout()); BOOST_REQUIRE(bob_account.vesting_shares == bob_total_vesting); @@ -360,15 +369,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) BOOST_REQUIRE(bob_author_reward.permlink == "test"); BOOST_REQUIRE(bob_author_reward.sbd_payout == bob_comment_reward.sbd_payout()); BOOST_REQUIRE(bob_author_reward.vesting_payout == bob_comment_reward.vesting_payout()); - - BOOST_REQUIRE(vote_idx.find(std::make_tuple(alice_comment.id, alice_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(alice_comment.id, bob_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(alice_comment.id, sam_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(alice_comment.id, dave_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(bob_comment.id, alice_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(bob_comment.id, bob_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(bob_comment.id, sam_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(bob_comment.id, dave_account.id)) == vote_idx.end()); + // "removing old votes" tests removed from here, tested in chain.cpp now validate_database(); BOOST_TEST_MESSAGE("Testing no payout when less than $0.02"); @@ -450,15 +451,6 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) BOOST_REQUIRE(sam_sbd_balance == sam_account.sbd_balance); BOOST_REQUIRE(dave_vest_shares == dave_account.vesting_shares); BOOST_REQUIRE(dave_sbd_balance == dave_account.sbd_balance); - - BOOST_REQUIRE(vote_idx.find(std::make_tuple(alice_comment1.id, alice_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(alice_comment1.id, bob_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(alice_comment1.id, sam_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(alice_comment1.id, dave_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(bob_comment1.id, alice_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(bob_comment1.id, bob_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(bob_comment1.id, sam_account.id)) == vote_idx.end()); - BOOST_REQUIRE(vote_idx.find(std::make_tuple(bob_comment1.id, dave_account.id)) == vote_idx.end()); validate_database(); } FC_LOG_AND_RETHROW() @@ -598,10 +590,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) BOOST_REQUIRE(gpo.total_vesting_shares == total_comment_fund.vesting_shares()); BOOST_REQUIRE(gpo.total_vesting_fund_steem == total_comment_fund.vesting_fund()); - BOOST_REQUIRE(alice_comment.total_payout_value == alice_comment_reward.total_payout()); - BOOST_REQUIRE(bob_comment.total_payout_value == bob_comment_reward.total_payout()); - BOOST_REQUIRE(sam_comment.total_payout_value.amount.value == 0); - BOOST_REQUIRE(dave_comment.total_payout_value == dave_comment_reward.total_payout()); + BOOST_REQUIRE(db->has_index()); + + const auto& cr_idx = db->get_index().indices().get(); + + auto alice_cr_itr = cr_idx.find(alice_comment.id); + BOOST_CHECK(alice_cr_itr != cr_idx.end()); + BOOST_CHECK_EQUAL(alice_cr_itr->total_payout_value, alice_comment_reward.total_payout()); + auto bob_cr_itr = cr_idx.find(bob_comment.id); + BOOST_CHECK(bob_cr_itr != cr_idx.end()); + BOOST_CHECK_EQUAL(bob_cr_itr->total_payout_value, bob_comment_reward.total_payout()); + auto sam_cr_itr = cr_idx.find(sam_comment.id); + BOOST_CHECK(sam_cr_itr == cr_idx.end()); + auto dave_cr_itr = cr_idx.find(dave_comment.id); + BOOST_CHECK(dave_cr_itr != cr_idx.end()); + BOOST_CHECK_EQUAL(dave_cr_itr->total_payout_value, dave_comment_reward.total_payout()); auto ops = get_last_operations(9); @@ -773,7 +776,13 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) BOOST_REQUIRE(gpo.total_vesting_shares == total_comment_fund.vesting_shares()); BOOST_REQUIRE(gpo.total_vesting_fund_steem == total_comment_fund.vesting_fund()); - BOOST_REQUIRE(alice_comment.total_payout_value == alice_comment_reward.total_payout()); + BOOST_REQUIRE(db->has_index()); + + const auto& cr_idx = db->get_index().indices().get(); + + auto alice_cr_itr = cr_idx.find(alice_comment.id); + BOOST_CHECK(alice_cr_itr != cr_idx.end()); + BOOST_CHECK_EQUAL(alice_cr_itr->total_payout_value, alice_comment_reward.total_payout()); auto alice_total_vesting = alice_starting_vesting + alice_comment_reward.vesting_payout(); auto alice_total_sbd = alice_starting_sbd + alice_comment_reward.sbd_payout(); @@ -923,7 +932,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) } FC_LOG_AND_RETHROW() } - + BOOST_AUTO_TEST_CASE(vesting_withdraw_route) { try { ACTORS((alice)(bob)(sam)) @@ -941,29 +950,23 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) wv.vesting_shares = withdraw_amount; signed_transaction tx; - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.operations.push_back(wv); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); - - tx.operations.clear(); - tx.signatures.clear(); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, wv)); BOOST_TEST_MESSAGE("Setting up bob destination"); - set_withdraw_vesting_route_operation op; - op.from_account = "alice"; - op.to_account = "bob"; - op.percent = STEEMIT_1_PERCENT * 50; - op.auto_vest = true; - tx.operations.push_back(op); + set_withdraw_vesting_route_operation op_bob; + op_bob.from_account = "alice"; + op_bob.to_account = "bob"; + op_bob.percent = STEEMIT_1_PERCENT * 50; + op_bob.auto_vest = true; BOOST_TEST_MESSAGE("Setting up sam destination"); - op.to_account = "sam"; - op.percent = STEEMIT_1_PERCENT * 30; - op.auto_vest = false; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + set_withdraw_vesting_route_operation op_sam; + op_sam.from_account = "alice"; + op_sam.to_account = "sam"; + op_sam.percent = STEEMIT_1_PERCENT * 30; + op_sam.auto_vest = false; + + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op_bob, op_sam)); BOOST_TEST_MESSAGE("Setting up first withdraw"); @@ -988,16 +991,16 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) (vesting_withdraw_rate.amount * STEEMIT_1_PERCENT * 20) / STEEMIT_100_PERCENT, VESTS_SYMBOL) * gpo.get_vesting_share_price(); - BOOST_REQUIRE(alice.balance == alice_total); - BOOST_REQUIRE(alice.vesting_shares == old_alice_vesting - vesting_withdraw_rate); + BOOST_CHECK_EQUAL(alice.balance, alice_total); + BOOST_CHECK_EQUAL(alice.vesting_shares, old_alice_vesting - vesting_withdraw_rate); auto bob_total = old_bob_vesting + asset( (vesting_withdraw_rate.amount * STEEMIT_1_PERCENT * 50) / STEEMIT_100_PERCENT, VESTS_SYMBOL); - BOOST_REQUIRE(bob.vesting_shares == bob_total); - BOOST_REQUIRE(bob.balance == old_bob_balance); + BOOST_CHECK_EQUAL(bob.vesting_shares, bob_total); + BOOST_CHECK_EQUAL(bob.balance, old_bob_balance); auto sam_total = old_sam_balance + @@ -1005,8 +1008,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) (vesting_withdraw_rate.amount * STEEMIT_1_PERCENT * 30) / STEEMIT_100_PERCENT, VESTS_SYMBOL) * gpo.get_vesting_share_price(); - BOOST_REQUIRE(sam.balance == sam_total); - BOOST_REQUIRE(sam.vesting_shares == old_sam_vesting); + BOOST_CHECK_EQUAL(sam.balance, sam_total); + BOOST_CHECK_EQUAL(sam.vesting_shares, old_sam_vesting); old_alice_balance = alice.balance; old_alice_vesting = alice.vesting_shares; @@ -1018,26 +1021,20 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) BOOST_TEST_MESSAGE("Test failure with greater than 100% destination assignment"); - tx.operations.clear(); - tx.signatures.clear(); - + set_withdraw_vesting_route_operation op; + op.from_account = "alice"; op.to_account = "sam"; op.percent = STEEMIT_1_PERCENT * 50 + 1; - tx.operations.push_back(op); - tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); - tx.sign(alice_private_key, db->get_chain_id()); - STEEMIT_REQUIRE_THROW(db->push_transaction(tx, 0), fc::exception); + op.auto_vest = false; + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, op), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::more_100percent_allocated_to_destinations))); BOOST_TEST_MESSAGE("Test from_account receiving no withdraw"); - tx.operations.clear(); - tx.signatures.clear(); - op.to_account = "sam"; op.percent = STEEMIT_1_PERCENT * 50; - tx.operations.push_back(op); - tx.sign(alice_private_key, db->get_chain_id()); - db->push_transaction(tx, 0); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, op)); generate_blocks(db->get_account("alice").next_vesting_withdrawal, true); { @@ -1046,25 +1043,25 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) const auto& sam = db->get_account("sam"); auto& gpo = db->get_dynamic_global_properties(); - BOOST_REQUIRE(alice.vesting_shares == old_alice_vesting - vesting_withdraw_rate); - BOOST_REQUIRE(alice.balance == old_alice_balance); + BOOST_CHECK_EQUAL(alice.vesting_shares, old_alice_vesting - vesting_withdraw_rate); + BOOST_CHECK_EQUAL(alice.balance, old_alice_balance); auto bob_vesting = old_bob_vesting + asset( (vesting_withdraw_rate.amount * STEEMIT_1_PERCENT * 50) / STEEMIT_100_PERCENT, VESTS_SYMBOL); - BOOST_REQUIRE(bob.vesting_shares == bob_vesting); - BOOST_REQUIRE(bob.balance == old_bob_balance); + BOOST_CHECK_EQUAL(bob.vesting_shares, bob_vesting); + BOOST_CHECK_EQUAL(bob.balance, old_bob_balance); - BOOST_REQUIRE(sam.vesting_shares == old_sam_vesting); + BOOST_CHECK_EQUAL(sam.vesting_shares, old_sam_vesting); auto sam_total = old_sam_balance + asset( (vesting_withdraw_rate.amount * STEEMIT_1_PERCENT * 50) / STEEMIT_100_PERCENT, VESTS_SYMBOL) * gpo.get_vesting_share_price(); - BOOST_REQUIRE(sam.balance == sam_total); + BOOST_CHECK_EQUAL(sam.balance, sam_total); } } FC_LOG_AND_RETHROW() @@ -1208,8 +1205,15 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) generate_blocks(db->get_comment("alice", string("test")).cashout_time, true); + BOOST_REQUIRE(db->has_index()); + + const auto& cr_idx = db->get_index().indices().get(); + + auto alice_cr_itr = cr_idx.find(db->get_comment("alice", string("test")).id); + BOOST_REQUIRE(alice_cr_itr != cr_idx.end()); + auto start_balance = asset( - db->get_comment("alice", string("test")).total_payout_value.amount /2, + alice_cr_itr->total_payout_value.amount /2, SBD_SYMBOL); BOOST_TEST_MESSAGE("Setup conversion to GOLOS"); @@ -1235,7 +1239,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) BOOST_REQUIRE(convert_request != convert_request_idx.end()); BOOST_REQUIRE(alice_2.balance.amount.value == 0); - BOOST_REQUIRE(alice_2.sbd_balance.amount.value == (start_balance - op.amount).amount.value); + APPROX_CHECK_EQUAL(alice_2.sbd_balance.amount.value, (start_balance - op.amount).amount.value, 10); validate_database(); BOOST_TEST_MESSAGE("Generate one more block"); @@ -1248,7 +1252,7 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) convert_request = convert_request_idx.find(std::make_tuple("alice", 2)); BOOST_REQUIRE(convert_request == convert_request_idx.end()); BOOST_REQUIRE(alice_3.balance.amount.value == 2500); - BOOST_REQUIRE(alice_3.sbd_balance.amount.value == (start_balance - op.amount).amount.value); + APPROX_CHECK_EQUAL(alice_3.sbd_balance.amount.value, (start_balance - op.amount).amount.value, 10); BOOST_REQUIRE(vop.owner == "alice"); BOOST_REQUIRE(vop.requestid == 2); BOOST_REQUIRE(vop.amount_in.amount.value == ASSET("2.000 GBG").amount.value); @@ -1932,8 +1936,8 @@ BOOST_FIXTURE_TEST_SUITE(operation_time_tests, clean_database_fixture) db->head_block_time() + fc::seconds(STEEMIT_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10.to_seconds() / 2), true); - ops = get_last_operations(1); - fill_order_op = ops[0].get(); + auto fill_ops = get_last_operations(1); + fill_order_op = fill_ops[0]; BOOST_REQUIRE(fill_order_op.open_owner == "alice"); BOOST_REQUIRE(fill_order_op.open_orderid == 6); diff --git a/tests/tests/proposal_tests.cpp b/tests/tests/proposal_tests.cpp index 6b2f50b8a2..ef7f65c773 100644 --- a/tests/tests/proposal_tests.cpp +++ b/tests/tests/proposal_tests.cpp @@ -13,11 +13,18 @@ #include #include "database_fixture.hpp" +#include "helpers.hpp" #include #include #include + +// TODO: move somewhere globally accessible +#define GOLOS_PROPOSAL_MAX_TITLE_SIZE 256 +#define GOLOS_PROPOSAL_MAX_MEMO_SIZE 4096 + + using namespace golos; using namespace golos::chain; using namespace golos::protocol; @@ -25,6 +32,180 @@ using std::string; BOOST_FIXTURE_TEST_SUITE(proposal_tests, clean_database_fixture) + +// validate + authority tests +BOOST_AUTO_TEST_CASE(proposal_create_validate) { try { + BOOST_TEST_MESSAGE("Testing: proposal_create_validate"); + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + transfer_operation top; + top.from = "alice"; + top.to = "bob"; + top.amount = ASSET_GBG(1.0); + + proposal_create_operation op; + op.author = "alice"; + op.title = "test"; + op.expiration_time = db->head_block_time() + fc::hours(6); + op.review_period_time = db->head_block_time() + fc::hours(3); + op.proposed_operations.push_back(operation_wrapper(top)); + + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, title, "-"); + CHECK_PARAM_VALID(op, title, string(GOLOS_PROPOSAL_MAX_TITLE_SIZE, ' ')); + CHECK_PARAM_VALID(op, title, u8"тест"); + CHECK_PARAM_VALID(op, memo, string(GOLOS_PROPOSAL_MAX_MEMO_SIZE, ' ')); + CHECK_PARAM_VALID(op, memo, u8"тест"); + + BOOST_TEST_MESSAGE("--- failure when author is empty"); + CHECK_PARAM_INVALID(op, author, ""); + + BOOST_TEST_MESSAGE("--- failure when title is not valid"); + CHECK_PARAM_INVALID(op, title, ""); + CHECK_PARAM_INVALID(op, title, string(1+GOLOS_PROPOSAL_MAX_TITLE_SIZE, ' ')); + CHECK_PARAM_INVALID(op, title, "\xc3\x28"); + + BOOST_TEST_MESSAGE("--- failure when memo is not valid"); + CHECK_PARAM_INVALID(op, memo, string(1+GOLOS_PROPOSAL_MAX_MEMO_SIZE, ' ')); + CHECK_PARAM_INVALID(op, memo, "\xc3\x28"); + + BOOST_TEST_MESSAGE("--- failure when proposed_operations is empty"); + proposal_create_operation e; + CHECK_PARAM_INVALID(op, proposed_operations, e.proposed_operations); + + BOOST_TEST_MESSAGE("--- failure when proposed_operations contains invalid op"); + auto ops = op.proposed_operations; + transfer_operation t2; + t2.from = "alice"; + t2.to = "bob"; + t2.amount = ASSET_GESTS(1.0); + ops.push_back(operation_wrapper(t2)); + // validation fails inside internal transaction, so error contains "amount" field + CHECK_PARAM_VALIDATION_FAIL(op, proposed_operations, ops, + CHECK_ERROR(invalid_parameter, "amount")); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(proposal_create_authorities) { try { + BOOST_TEST_MESSAGE("Testing: proposal_create_authorities"); + proposal_create_operation op; + op.author = "bob"; + op.title = "test"; + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"bob"}), account_name_set()); +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE(proposal_update_validate) { try { + BOOST_TEST_MESSAGE("Testing: proposal_update_validate"); + + using approvals = flat_set; + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + proposal_update_operation op; + op.author = "alice"; + op.title = "test"; + op.owner_approvals_to_add = approvals({"bob"}); + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, title, "-"); + CHECK_PARAM_VALID(op, title, string(GOLOS_PROPOSAL_MAX_TITLE_SIZE, ' ')); + CHECK_PARAM_VALID(op, title, u8"тест"); + CHECK_PARAM_VALID(op, active_approvals_to_add, approvals({"cid", "dave"})); + + BOOST_TEST_MESSAGE("--- failure when author is empty"); + CHECK_PARAM_INVALID(op, author, ""); + + BOOST_TEST_MESSAGE("--- failure when title is not valid"); + CHECK_PARAM_INVALID(op, title, ""); + CHECK_PARAM_INVALID(op, title, string(1+GOLOS_PROPOSAL_MAX_TITLE_SIZE, ' ')); + CHECK_PARAM_INVALID(op, title, "\xc3\x28"); + + BOOST_TEST_MESSAGE("--- failure when no approvals"); + CHECK_PARAM_INVALID_LOGIC(op, owner_approvals_to_add, approvals(), empty_approvals); + + BOOST_TEST_MESSAGE("--- failure when adding and deleting same approval"); + CHECK_PARAM_INVALID_LOGIC(op, owner_approvals_to_remove, approvals({"bob"}), add_and_remove_same_approval); + +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE(proposal_update_authorities) { try { + BOOST_TEST_MESSAGE("Testing: proposal_update_authorities"); + account_name_set nobody; + account_name_set bob_acc = account_name_set({"bob"}); + account_name_set cid_acc = account_name_set({"cid"}); + account_name_set bob_and_cid = account_name_set({"bob", "cid"}); + + proposal_update_operation op; + op.author = "alice"; + op.title = "test"; + + BOOST_TEST_MESSAGE("--- owner_approvals_to_add and owner_approvals_to_remove"); + op.owner_approvals_to_add = bob_acc; + CHECK_OP_AUTHS(op, bob_acc, nobody, nobody); + op.owner_approvals_to_remove = cid_acc; + CHECK_OP_AUTHS(op, bob_and_cid, nobody, nobody); + op.owner_approvals_to_add = nobody; + CHECK_OP_AUTHS(op, cid_acc, nobody, nobody); + op.owner_approvals_to_remove = nobody; + + BOOST_TEST_MESSAGE("--- active_approvals_to_add and active_approvals_to_remove"); + op.active_approvals_to_add = bob_acc; + CHECK_OP_AUTHS(op, nobody, bob_acc, nobody); + op.active_approvals_to_remove = cid_acc; + CHECK_OP_AUTHS(op, nobody, bob_and_cid, nobody); + op.active_approvals_to_add = nobody; + CHECK_OP_AUTHS(op, nobody, cid_acc, nobody); + op.active_approvals_to_remove = nobody; + + BOOST_TEST_MESSAGE("--- poting_approvals_to_add and poting_approvals_to_remove"); + op.posting_approvals_to_add = bob_acc; + CHECK_OP_AUTHS(op, nobody, nobody, bob_acc); + op.posting_approvals_to_remove = cid_acc; + CHECK_OP_AUTHS(op, nobody, nobody, bob_and_cid); + op.posting_approvals_to_add = nobody; + CHECK_OP_AUTHS(op, nobody, nobody, cid_acc); + op.posting_approvals_to_remove = nobody; + + // TODO: keys? + +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE(proposal_delete_validate) { try { + BOOST_TEST_MESSAGE("Testing: proposal_delete_validate"); + + BOOST_TEST_MESSAGE("--- success on valid parameters"); + proposal_delete_operation op; + op.requester = "bob"; + op.author = "alice"; + op.title = "test"; + CHECK_OP_VALID(op); + CHECK_PARAM_VALID(op, title, "-"); + CHECK_PARAM_VALID(op, title, string(GOLOS_PROPOSAL_MAX_TITLE_SIZE, ' ')); + CHECK_PARAM_VALID(op, title, u8"тест"); + + BOOST_TEST_MESSAGE("--- failure when requester or author is empty"); + CHECK_PARAM_INVALID(op, requester, ""); + CHECK_PARAM_INVALID(op, author, ""); + + BOOST_TEST_MESSAGE("--- failure when title is not valid"); + CHECK_PARAM_INVALID(op, title, ""); + CHECK_PARAM_INVALID(op, title, string(GOLOS_PROPOSAL_MAX_TITLE_SIZE+1, ' ')); + CHECK_PARAM_INVALID(op, title, "\xc3\x28"); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(proposal_delete_authorities) { try { + BOOST_TEST_MESSAGE("Testing: proposal_delete_authorities"); + proposal_delete_operation op; + op.requester = "bob"; + op.author = "alice"; + op.title = "test"; + CHECK_OP_AUTHS(op, account_name_set(), account_name_set({"bob"}), account_name_set()); +} FC_LOG_AND_RETHROW() } + + +// apply tests BOOST_AUTO_TEST_CASE(create_proposal) { try { BOOST_TEST_MESSAGE("Testing: proposal_create_operation"); @@ -56,7 +237,9 @@ BOOST_AUTO_TEST_CASE(create_proposal) { try { cop.proposed_operations.push_back(operation_wrapper(top)); cop.proposed_operations.push_back(operation_wrapper(vop)); - BOOST_REQUIRE_THROW(push_tx_with_ops(tx, bob_private_key, cop), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, cop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::tx_with_both_posting_active_ops))); BOOST_TEST_MESSAGE("--- Simple proposal with a transfer operation"); @@ -73,7 +256,7 @@ BOOST_AUTO_TEST_CASE(create_proposal) { try { cop1.review_period_time = db->head_block_time() + fc::hours(3); cop1.proposed_operations.push_back(operation_wrapper(top1)); - push_tx_with_ops(tx, bob_private_key, cop1); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cop1)); generate_blocks(1); const auto& p = db->get_proposal(cop1.author, cop1.title); @@ -111,7 +294,7 @@ BOOST_AUTO_TEST_CASE(update_proposal) { try { cop.review_period_time = db->head_block_time() + fc::hours(3); cop.proposed_operations.push_back(operation_wrapper(top)); - push_tx_with_ops(tx, bob_private_key, cop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cop)); generate_blocks(1); const auto& p = db->get_proposal(cop.author, cop.title); @@ -122,7 +305,8 @@ BOOST_AUTO_TEST_CASE(update_proposal) { try { uop.title = cop.title; uop.active_approvals_to_add.insert("alice"); - BOOST_REQUIRE_THROW(push_tx_with_ops(tx, bob_private_key, uop), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, uop), + CHECK_ERROR(tx_missing_active_auth, 0)); proposal_update_operation uop1; uop1.author = cop.author; @@ -130,14 +314,17 @@ BOOST_AUTO_TEST_CASE(update_proposal) { try { uop1.active_approvals_to_add.insert("alice"); uop1.active_approvals_to_remove.insert("alice"); - BOOST_REQUIRE_THROW(push_tx_with_ops(tx, alice_private_key, uop1), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, uop1), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::add_and_remove_same_approval))); proposal_update_operation uop2; uop2.author = cop.author; uop2.title = cop.title; uop2.key_approvals_to_add.insert(bob_public_key); - BOOST_REQUIRE_THROW(push_tx_with_ops(tx, alice_private_key, uop2), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, alice_private_key, uop2), + CHECK_ERROR(tx_missing_other_auth, 0)); BOOST_TEST_MESSAGE("--- Add an approval to a proposal"); @@ -145,14 +332,14 @@ BOOST_AUTO_TEST_CASE(update_proposal) { try { uop3.author = cop.author; uop3.title = cop.title; uop3.active_approvals_to_add.insert("alice"); - push_tx_with_ops(tx, alice_private_key, uop3); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, uop3)); generate_blocks(1); proposal_update_operation uop4; uop4.author = cop.author; uop4.title = cop.title; uop4.key_approvals_to_add.insert(bob_public_key); - push_tx_with_ops(tx, bob_private_key, uop4); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, uop4)); generate_blocks(1); BOOST_CHECK_EQUAL(p.required_active_approvals.size(), 1); @@ -173,14 +360,14 @@ BOOST_AUTO_TEST_CASE(update_proposal) { try { uop5.author = cop.author; uop5.title = cop.title; uop5.active_approvals_to_remove.insert("alice"); - push_tx_with_ops(tx, alice_private_key, uop5); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, uop5)); generate_blocks(1); proposal_update_operation uop6; uop6.author = cop.author; uop6.title = cop.title; uop6.key_approvals_to_remove.insert(bob_public_key); - push_tx_with_ops(tx, bob_private_key, uop6); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, uop6)); generate_blocks(1); BOOST_CHECK_EQUAL(p.required_active_approvals.size(), 1); @@ -199,14 +386,14 @@ BOOST_AUTO_TEST_CASE(update_proposal) { try { uop7.author = cop.author; uop7.title = cop.title; uop7.active_approvals_to_add.insert("alice"); - push_tx_with_ops(tx, alice_private_key, uop7); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, uop7)); generate_blocks(1); proposal_update_operation uop8; uop8.author = cop.author; uop8.title = cop.title; uop8.key_approvals_to_add.insert(bob_public_key); - push_tx_with_ops(tx, bob_private_key, uop8); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, uop8)); generate_blocks(1); BOOST_CHECK_EQUAL(p.required_active_approvals.size(), 1); @@ -227,7 +414,7 @@ BOOST_AUTO_TEST_CASE(update_proposal) { try { uop9.author = cop.author; uop9.title = cop.title; uop9.key_approvals_to_remove.insert(bob_public_key); - push_tx_with_ops(tx, bob_private_key, uop9); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, uop9)); generate_blocks(1); BOOST_CHECK_EQUAL(p.required_active_approvals.size(), 1); @@ -245,17 +432,19 @@ BOOST_AUTO_TEST_CASE(update_proposal) { try { uop10.author = cop.author; uop10.title = cop.title; uop10.active_approvals_to_add.insert("bob"); - BOOST_REQUIRE_THROW(push_tx_with_ops(tx, bob_private_key, uop10), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, bob_private_key, uop10), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::cannot_add_approval_in_review_period))); BOOST_TEST_MESSAGE("--- Auto removing of a proposal if no approvals in a review period"); proposal_update_operation uop11; uop11.author = cop.author; uop11.title = cop.title; uop11.active_approvals_to_remove.insert("alice"); - push_tx_with_ops(tx, alice_private_key, uop11); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, uop11)); generate_blocks(1); - BOOST_REQUIRE_THROW(db->get_proposal(cop.author, cop.title), fc::exception); + BOOST_CHECK(nullptr == db->find_proposal(cop.author, cop.title)); } FC_LOG_AND_RETHROW() } @@ -280,18 +469,17 @@ BOOST_AUTO_TEST_CASE(update_proposal1) { try { cop.expiration_time = db->head_block_time() + fc::hours(6); cop.proposed_operations.push_back(operation_wrapper(top)); - push_tx_with_ops(tx, bob_private_key, cop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cop)); generate_blocks(1); proposal_update_operation uop; uop.author = cop.author; uop.title = cop.title; uop.active_approvals_to_add.insert("alice"); - push_tx_with_ops(tx, alice_private_key, uop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, uop)); generate_blocks(1); - BOOST_REQUIRE_THROW(db->get_proposal(cop.author, cop.title), fc::exception); - + BOOST_CHECK(nullptr == db->find_proposal(cop.author, cop.title)); BOOST_CHECK_EQUAL(db->get_account("alice").balance.amount.value, 7500); BOOST_CHECK_EQUAL(db->get_account("bob").balance.amount.value, 2500); @@ -318,18 +506,17 @@ BOOST_AUTO_TEST_CASE(update_proposal2) { try { cop.expiration_time = db->head_block_time() + fc::hours(6); cop.proposed_operations.push_back(operation_wrapper(top)); - push_tx_with_ops(tx, bob_private_key, cop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cop)); generate_blocks(1); proposal_update_operation uop; uop.author = cop.author; uop.title = cop.title; uop.key_approvals_to_add.insert(alice_public_key); - push_tx_with_ops(tx, alice_private_key, uop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, uop)); generate_blocks(1); - BOOST_REQUIRE_THROW(db->get_proposal(cop.author, cop.title), fc::exception); - + BOOST_CHECK(nullptr == db->find_proposal(cop.author, cop.title)); BOOST_CHECK_EQUAL(db->get_account("alice").balance.amount.value, 7500); BOOST_CHECK_EQUAL(db->get_account("bob").balance.amount.value, 2500); @@ -357,14 +544,14 @@ BOOST_AUTO_TEST_CASE(update_proposal3) { try { cop.expiration_time = db->head_block_time() + fc::hours(6); cop.proposed_operations.push_back(operation_wrapper(top)); - push_tx_with_ops(tx, bob_private_key, cop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cop)); generate_blocks(1); proposal_update_operation uop; uop.author = cop.author; uop.title = cop.title; uop.key_approvals_to_add.insert(alice_public_key); - push_tx_with_ops(tx, alice_private_key, uop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, uop)); generate_blocks(1); BOOST_CHECK_NO_THROW(db->get_proposal(cop.author, cop.title)); @@ -377,8 +564,7 @@ BOOST_AUTO_TEST_CASE(update_proposal3) { try { generate_blocks(p.expiration_time); - BOOST_REQUIRE_THROW(db->get_proposal(cop.author, cop.title), fc::exception); - + BOOST_CHECK(nullptr == db->find_proposal(cop.author, cop.title)); BOOST_CHECK_EQUAL(db->get_account("alice").balance.amount.value, 7500); BOOST_CHECK_EQUAL(db->get_account("bob").balance.amount.value, 2500); @@ -413,19 +599,17 @@ BOOST_AUTO_TEST_CASE(nested_signatures) { try { account_update_operation op; op.account = account; op.active = auth; - push_tx_with_ops(tx, account_private_key, op); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, account_private_key, op)); generate_blocks(1); }; - auto get_active = [&](const std::string& name) { + auto get_active = [&](const string& name) { return authority(db->get(name).active); }; - - auto get_owner = [&](const std::string& name) { + auto get_owner = [&](const string& name) { return authority(db->get(name).owner); }; - - auto get_posting = [&](const std::string& name) { + auto get_posting = [&](const string& name) { return authority(db->get(name).posting); }; @@ -487,7 +671,7 @@ BOOST_AUTO_TEST_CASE(nested_signatures) { try { cop.memo = "Some memo about transfer"; cop.expiration_time = db->head_block_time() + fc::hours(6); cop.proposed_operations.push_back(operation_wrapper(op)); - push_tx_with_ops(tx, edy_private_key, cop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, edy_private_key, cop)); generate_blocks(1); BOOST_REQUIRE_NO_THROW(db->get_proposal(cop.author, cop.title)); @@ -496,7 +680,7 @@ BOOST_AUTO_TEST_CASE(nested_signatures) { try { uop.author = cop.author; uop.title = cop.title; uop.key_approvals_to_add.insert(alice_public_key); - push_tx_with_ops(tx, alice_private_key, uop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, uop)); generate_blocks(1); const auto& poxx_account = db->get_account("poxx"); @@ -510,7 +694,7 @@ BOOST_AUTO_TEST_CASE(nested_signatures) { try { uop1.author = cop.author; uop1.title = cop.title; uop1.key_approvals_to_add.insert(bob_public_key); - push_tx_with_ops(tx, bob_private_key, uop1); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, uop1)); generate_blocks(1); BOOST_REQUIRE_NO_THROW(db->get_proposal(cop.author, cop.title)); @@ -521,7 +705,7 @@ BOOST_AUTO_TEST_CASE(nested_signatures) { try { uop2.author = cop.author; uop2.title = cop.title; uop2.key_approvals_to_add.insert(cindy_public_key); - push_tx_with_ops(tx, cindy_private_key, uop2); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, cindy_private_key, uop2)); generate_blocks(1); BOOST_REQUIRE_NO_THROW(db->get_proposal(cop.author, cop.title)); @@ -532,16 +716,17 @@ BOOST_AUTO_TEST_CASE(nested_signatures) { try { uop3.author = cop.author; uop3.title = cop.title; uop3.key_approvals_to_add.insert(dave_public_key); - BOOST_REQUIRE_THROW(push_tx_with_ops(tx, dave_private_key, uop3), tx_irrelevant_sig); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, dave_private_key, uop3), + CHECK_ERROR(tx_invalid_operation, 0, CHECK_ERROR(tx_irrelevant_sig, 0))); proposal_update_operation uop4; uop4.author = cop.author; uop4.title = cop.title; uop4.key_approvals_to_add.insert(dan_public_key); - push_tx_with_ops(tx, dan_private_key, uop4); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, dan_private_key, uop4)); generate_blocks(1); - BOOST_REQUIRE_THROW(db->get_proposal(cop.author, cop.title), fc::exception); + BOOST_CHECK(nullptr == db->find_proposal(cop.author, cop.title)); BOOST_CHECK_EQUAL(poxx_account.balance.amount.value, 7500); BOOST_CHECK_EQUAL(edy_account.balance.amount.value, 2500); @@ -569,7 +754,7 @@ BOOST_AUTO_TEST_CASE(delete_proposal) { try { cop.expiration_time = db->head_block_time() + fc::hours(6); cop.proposed_operations.push_back(operation_wrapper(top)); - push_tx_with_ops(tx, bob_private_key, cop); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cop)); generate_blocks(1); BOOST_TEST_MESSAGE("--- Unauthorized trying of delete of proposal"); @@ -578,7 +763,9 @@ BOOST_AUTO_TEST_CASE(delete_proposal) { try { dop.author = cop.author; dop.title = cop.title; dop.requester = "dave"; - BOOST_REQUIRE_THROW(push_tx_with_ops(tx, dave_private_key, dop), fc::exception); + GOLOS_CHECK_ERROR_PROPS(push_tx_with_ops(tx, dave_private_key, dop), + CHECK_ERROR(tx_invalid_operation, 0, + CHECK_ERROR(logic_exception, logic_exception::proposal_delete_not_allowed))); BOOST_TEST_MESSAGE("--- Authorized delete of proposal"); @@ -586,10 +773,10 @@ BOOST_AUTO_TEST_CASE(delete_proposal) { try { dop1.author = cop.author; dop1.title = cop.title; dop1.requester = "alice"; - push_tx_with_ops(tx, alice_private_key, dop1); + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, alice_private_key, dop1)); generate_blocks(1); - BOOST_REQUIRE_THROW(db->get_proposal(cop.author, cop.title), fc::exception); + BOOST_CHECK(nullptr == db->find_proposal(cop.author, cop.title)); } FC_LOG_AND_RETHROW() } diff --git a/thirdparty/fc b/thirdparty/fc index d9e526e6e7..afa113d86e 160000 --- a/thirdparty/fc +++ b/thirdparty/fc @@ -1 +1 @@ -Subproject commit d9e526e6e7b8d39fa40adecbf4407fa6fd6366c1 +Subproject commit afa113d86e1e7ba91c62e9fe256de921f375b608