From 668f677867748d38606fabb36c65803aeeb34fff Mon Sep 17 00:00:00 2001 From: sierra19XX <15652887+sierra19XX@users.noreply.github.com> Date: Mon, 3 May 2021 09:26:43 +0000 Subject: [PATCH] rate limiting for son deposit, withdrawal, pw change, tx settle operations --- .../peerplays_sidechain_plugin.hpp | 16 +++++++++ .../peerplays_sidechain_plugin.cpp | 33 ++++++++++++++++++- .../sidechain_net_handler.cpp | 27 ++++++++++++--- .../sidechain_net_handler_bitcoin.cpp | 6 +++- 4 files changed, 75 insertions(+), 7 deletions(-) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp index 80086c10..5b7ea6e9 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp @@ -12,6 +12,20 @@ namespace detail { class peerplays_sidechain_plugin_impl; } +struct son_proposal_type { + son_proposal_type(int op, son_id_type son, object_id_type object) : + op_type(op), + son_id(son), + object_id(object) { + } + int op_type; + son_id_type son_id; + object_id_type object_id; + bool operator<(const son_proposal_type &other) const { + return std::tie(op_type, son_id, object_id) < std::tie(other.op_type, other.son_id, other.object_id); + } +}; + class peerplays_sidechain_plugin : public graphene::app::plugin { public: peerplays_sidechain_plugin(); @@ -34,6 +48,8 @@ public: bool is_son_deregistered(son_id_type son_id); fc::ecc::private_key get_private_key(son_id_type son_id); fc::ecc::private_key get_private_key(chain::public_key_type public_key); + void log_son_proposal_retry(int op_type, object_id_type object_id); + bool can_son_participate(int op_type, object_id_type object_id); }; }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index abde7b51..2f439711 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -41,6 +41,8 @@ public: bool is_valid_son_proposal(const chain::proposal_object &proposal); fc::ecc::private_key get_private_key(son_id_type son_id); fc::ecc::private_key get_private_key(chain::public_key_type public_key); + void log_son_proposal_retry(int op_type, object_id_type object_id); + bool can_son_participate(int op_type, object_id_type object_id); void schedule_heartbeat_loop(); void heartbeat_loop(); @@ -75,6 +77,8 @@ private: std::map private_keys; fc::future _heartbeat_task; fc::future _son_processing_task; + std::map son_retry_count; + uint16_t retries_threshold; bool first_block_skipped; void on_applied_block(const signed_block &b); @@ -130,6 +134,7 @@ void peerplays_sidechain_plugin_impl::plugin_set_program_options( cli.add_options()("bitcoin-wallet-password", bpo::value(), "Bitcoin wallet password"); cli.add_options()("bitcoin-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair("02d0f137e717fb3aab7aff99904001d49a0a636c5e1342f8927a4ba2eaee8e9772", "cVN31uC9sTEr392DLVUEjrtMgLA8Yb3fpYmTRj7bomTm6nn2ANPr")), "Tuple of [Bitcoin public key, Bitcoin private key] (may specify multiple times)"); + cli.add_options()("sidechain-retry-threshold", bpo::value()->default_value(15), "Sidechain retry throttling threshold"); cfg.add(cli); } @@ -169,6 +174,8 @@ void peerplays_sidechain_plugin_impl::plugin_initialize(const boost::program_opt } config_ready_son = config_ready_son && !private_keys.empty(); } + retries_threshold = options.at("sidechain-retry-threshold").as(); + ilog("sidechain-retry-threshold: ${sidechain-retry-threshold}", ("sidechain-retry-threshold", retries_threshold)); } if (!config_ready_son) { wlog("Haven't set up SON parameters"); @@ -279,7 +286,7 @@ bool peerplays_sidechain_plugin_impl::is_son_deregistered(son_id_type son_id) { if (son_obj == idx.end()) return true; - if(son_obj->status == chain::son_status::deregistered) { + if (son_obj->status == chain::son_status::deregistered) { return true; } @@ -449,6 +456,22 @@ bool peerplays_sidechain_plugin_impl::is_valid_son_proposal(const chain::proposa return false; } +void peerplays_sidechain_plugin_impl::log_son_proposal_retry(int op_type, object_id_type object_id) { + son_proposal_type prop_type(op_type, get_current_son_id(), object_id); + auto itr = son_retry_count.find(prop_type); + if (itr != son_retry_count.end()) { + itr->second++; + } else { + son_retry_count[prop_type] = 1; + } +} + +bool peerplays_sidechain_plugin_impl::can_son_participate(int op_type, object_id_type object_id) { + son_proposal_type prop_type(op_type, get_current_son_id(), object_id); + auto itr = son_retry_count.find(prop_type); + return (itr == son_retry_count.end() || itr->second < retries_threshold); +} + void peerplays_sidechain_plugin_impl::approve_proposals() { auto check_approve_proposal = [&](const chain::son_id_type &son_id, const chain::proposal_object &proposal) { @@ -695,4 +718,12 @@ fc::ecc::private_key peerplays_sidechain_plugin::get_private_key(chain::public_k return my->get_private_key(public_key); } +void peerplays_sidechain_plugin::log_son_proposal_retry(int op_type, object_id_type object_id) { + my->log_son_proposal_retry(op_type, object_id); +} + +bool peerplays_sidechain_plugin::can_son_participate(int op_type, object_id_type object_id) { + return my->can_son_participate(op_type, object_id); +} + }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp index 5ceab83a..d025c178 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp @@ -275,6 +275,7 @@ void sidechain_net_handler::process_proposals() { int32_t op_idx_0 = -1; chain::operation op_obj_idx_0; + object_id_type object_id; if (po->proposed_transaction.operations.size() >= 1) { op_idx_0 = po->proposed_transaction.operations[0].which(); @@ -283,7 +284,7 @@ void sidechain_net_handler::process_proposals() { int32_t op_idx_1 = -1; chain::operation op_obj_idx_1; - (void) op_idx_1; + (void)op_idx_1; if (po->proposed_transaction.operations.size() >= 2) { op_idx_1 = po->proposed_transaction.operations[1].which(); @@ -293,11 +294,13 @@ void sidechain_net_handler::process_proposals() { switch (op_idx_0) { case chain::operation::tag::value: { should_process = (op_obj_idx_0.get().sidechain == sidechain); + object_id = op_obj_idx_0.get().son_wallet_id; break; } case chain::operation::tag::value: { son_wallet_deposit_id_type swdo_id = op_obj_idx_0.get().son_wallet_deposit_id; + object_id = swdo_id; const auto &idx = database.get_index_type().indices().get(); const auto swdo = idx.find(swdo_id); if (swdo != idx.end()) { @@ -308,6 +311,7 @@ void sidechain_net_handler::process_proposals() { case chain::operation::tag::value: { son_wallet_withdraw_id_type swwo_id = op_obj_idx_0.get().son_wallet_withdraw_id; + object_id = swwo_id; const auto &idx = database.get_index_type().indices().get(); const auto swwo = idx.find(swwo_id); if (swwo != idx.end()) { @@ -323,6 +327,7 @@ void sidechain_net_handler::process_proposals() { const auto sto = idx.find(st_id); if (sto != idx.end()) { should_process = ((sto->sidechain == sidechain) && (sto->status == sidechain_transaction_status::valid) && signer_expected(*sto, signer)); + object_id = sto->object_id; } break; } @@ -333,6 +338,7 @@ void sidechain_net_handler::process_proposals() { const auto sto = idx.find(st_id); if (sto != idx.end()) { should_process = (sto->sidechain == sidechain); + object_id = sto->object_id; } break; } @@ -344,10 +350,14 @@ void sidechain_net_handler::process_proposals() { elog("=================================================="); } - if (should_process) { + if (should_process && (op_idx_0 == chain::operation::tag::value || plugin.can_son_participate(op_idx_0, object_id))) { bool should_approve = process_proposal(*po); if (should_approve) { - approve_proposal(po->id, plugin.get_current_son_id()); + if (approve_proposal(po->id, plugin.get_current_son_id())) { + if (op_idx_0 != chain::operation::tag::value) { + plugin.log_son_proposal_retry(op_idx_0, object_id); + } + } } } } @@ -374,7 +384,7 @@ void sidechain_net_handler::process_deposits() { const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_deposit_object &swdo) { - if (swdo.id == object_id_type(0, 0, 0)) { + if (swdo.id == object_id_type(0, 0, 0) || !plugin.can_son_participate(chain::operation::tag::value, swdo.id)) { return; } //Ignore the deposits which are not valid anymore, considered refunds. @@ -392,6 +402,7 @@ void sidechain_net_handler::process_deposits() { wlog("Deposit not processed: ${swdo}", ("swdo", swdo)); return; } + plugin.log_son_proposal_retry(chain::operation::tag::value, swdo.id); }); } @@ -404,7 +415,7 @@ void sidechain_net_handler::process_withdrawals() { const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_withdraw_object &swwo) { - if (swwo.id == object_id_type(0, 0, 0)) { + if (swwo.id == object_id_type(0, 0, 0) || !plugin.can_son_participate(chain::operation::tag::value, swwo.id)) { return; } @@ -416,6 +427,7 @@ void sidechain_net_handler::process_withdrawals() { wlog("Withdraw not processed: ${swwo}", ("swwo", swwo)); return; } + plugin.log_son_proposal_retry(chain::operation::tag::value, swwo.id); }); } @@ -514,6 +526,10 @@ void sidechain_net_handler::settle_sidechain_transactions() { return; } + if (!plugin.can_son_participate(chain::operation::tag::value, sto.object_id)) { + return; + } + ilog("Sidechain transaction to settle: ${sto}", ("sto", sto.id)); int64_t settle_amount = settle_sidechain_transaction(sto); @@ -560,6 +576,7 @@ void sidechain_net_handler::settle_sidechain_transactions() { database.push_transaction(trx, database::validation_steps::skip_block_size_check); if (plugin.app().p2p_node()) plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + plugin.log_son_proposal_retry(chain::operation::tag::value, sto.object_id); } catch (fc::exception &e) { elog("Sending proposal for sidechain transaction settle operation failed with exception ${e}", ("e", e.what())); } diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp index 65b00b19..59fa01fd 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -1005,7 +1005,7 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) int32_t op_idx_1 = -1; chain::operation op_obj_idx_1; - (void) op_idx_1; + (void)op_idx_1; if (po.proposed_transaction.operations.size() >= 2) { op_idx_1 = po.proposed_transaction.operations[1].which(); @@ -1287,6 +1287,9 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() { boost::property_tree::ptree active_pw_pt; boost::property_tree::read_json(active_pw_ss, active_pw_pt); if (active_pw_pt.count("error") && active_pw_pt.get_child("error").empty()) { + if (!plugin.can_son_participate(chain::operation::tag::value, active_sw->id)) { + return; + } proposal_create_operation proposal_op; proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; @@ -1325,6 +1328,7 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() { database.push_transaction(trx, database::validation_steps::skip_block_size_check); if (plugin.app().p2p_node()) plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + plugin.log_son_proposal_retry(chain::operation::tag::value, active_sw->id); } catch (fc::exception &e) { elog("Sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what())); return;