From 985f7dc604d57d40ae75858a5ccbb0fe67adbe6b Mon Sep 17 00:00:00 2001 From: Dmytro Kostenko Date: Fri, 28 Apr 2023 11:09:48 +0200 Subject: [PATCH 1/3] refactor: rewards stats passing --- .../common/include/common/range_view.hpp | 7 +-- .../include/final_chain/rewards_stats.hpp | 34 +++++++------ .../include/final_chain/state_api.hpp | 4 +- .../consensus/src/final_chain/final_chain.cpp | 25 ++++++---- .../src/final_chain/rewards_stats.cpp | 50 +++++++++---------- .../consensus/src/final_chain/state_api.cpp | 7 +-- .../pbft_block/include/pbft/pbft_block.hpp | 23 +++++---- tests/final_chain_test.cpp | 31 ++++++++++-- tests/state_api_test.cpp | 6 +-- 9 files changed, 104 insertions(+), 83 deletions(-) diff --git a/libraries/common/include/common/range_view.hpp b/libraries/common/include/common/range_view.hpp index 5c93b77870..8f6cbf29d4 100644 --- a/libraries/common/include/common/range_view.hpp +++ b/libraries/common/include/common/range_view.hpp @@ -3,7 +3,7 @@ #include #include -namespace taraxa::util::range_view { +namespace taraxa::util { template struct RangeView { @@ -72,9 +72,4 @@ auto make_range_view(Seq const &seq) { return RangeView(seq); } -} // namespace taraxa::util::range_view - -namespace taraxa::util { -using range_view::make_range_view; -using range_view::RangeView; } // namespace taraxa::util diff --git a/libraries/core_libs/consensus/include/final_chain/rewards_stats.hpp b/libraries/core_libs/consensus/include/final_chain/rewards_stats.hpp index ee925e16db..c5ce0a0b1c 100644 --- a/libraries/core_libs/consensus/include/final_chain/rewards_stats.hpp +++ b/libraries/core_libs/consensus/include/final_chain/rewards_stats.hpp @@ -16,20 +16,25 @@ namespace taraxa { class RewardsStats { public: /** - * @brief Process PeriodData and returns vector of validators, who included provided block.transactions as first in - * dag block, e.g. returned validator on position 2 included transaction block.transactions[2] as first in his dag - * block + * @brief setting block_author_, max_votes_weight_ and calls processStats function * - * @param block * @param dpos_vote_count - votes count for previous block * @param committee_size - * @return vector of validators */ - std::vector processStats(const PeriodData& block, uint64_t dpos_vote_count, uint32_t committee_size); + RewardsStats(const PeriodData& block, uint64_t dpos_vote_count, uint32_t committee_size); HAS_RLP_FIELDS private: + /** + * @brief Process PeriodData and returns vector of validators, who included provided block.transactions as first in + * dag block, e.g. returned validator on position 2 included transaction block.transactions[2] as first in his dag + * block + * + * @param block + * @return vector of validators + */ + void processStats(const PeriodData& block); /** * @brief In case unique tx_hash is provided, it is mapped to it's validator's address + validator's unique txs count * is incremented. If provided tx_hash was already processed, nothing happens @@ -56,15 +61,6 @@ class RewardsStats { */ bool addVote(const std::shared_ptr& vote); - /** - * @brief Prepares reward statistics bases on period data data - * - * @param sync_blk - * @param dpos_vote_count - votes count for previous block - * @param committee_size - */ - void initStats(const PeriodData& sync_blk, uint64_t dpos_vote_count, uint32_t committee_size); - private: struct ValidatorStats { // count of rewardable(with 1 or more unique transactions) DAG blocks produced by this validator @@ -76,8 +72,14 @@ class RewardsStats { HAS_RLP_FIELDS }; + // Pbft block author + addr_t block_author_; + // Transactions validators: tx hash -> validator that included it as first in his block - std::unordered_map txs_validators_; + std::unordered_map validator_by_tx_hash_; + + // Vector with all transactions validators + std::vector txs_validators_; // Txs stats: validator -> ValidatorStats std::unordered_map validators_stats_; diff --git a/libraries/core_libs/consensus/include/final_chain/state_api.hpp b/libraries/core_libs/consensus/include/final_chain/state_api.hpp index 9d09067dbb..691494456f 100644 --- a/libraries/core_libs/consensus/include/final_chain/state_api.hpp +++ b/libraries/core_libs/consensus/include/final_chain/state_api.hpp @@ -43,9 +43,7 @@ class StateAPI { StateDescriptor get_last_committed_state_descriptor() const; const StateTransitionResult& transition_state(const EVMBlock& block, const util::RangeView& transactions, - const util::RangeView& transactions_validators = {}, - const util::RangeView& uncles = {}, - const RewardsStats& rewards_stats = {}); + const std::vector& rewards_stats = {}); void transition_state_commit(); void create_snapshot(PbftPeriod period); void prune(const std::vector& state_root_to_keep, EthBlockNumber blk_num); diff --git a/libraries/core_libs/consensus/src/final_chain/final_chain.cpp b/libraries/core_libs/consensus/src/final_chain/final_chain.cpp index ceb25d4d7b..53256f9afe 100644 --- a/libraries/core_libs/consensus/src/final_chain/final_chain.cpp +++ b/libraries/core_libs/consensus/src/final_chain/final_chain.cpp @@ -144,19 +144,26 @@ class FinalChainImpl final : public FinalChain { EthBlockNumber delegation_delay() const override { return delegation_delay_; } + std::vector prepare_rewards_stats_(const PeriodData& blk) { + std::vector rewards_stats; + uint64_t dpos_vote_count = kCommitteeSize; + + // Block zero + if (!blk.previous_block_cert_votes.empty()) [[likely]] { + dpos_vote_count = dpos_eligible_total_vote_count(blk.previous_block_cert_votes[0]->getPeriod() - 1); + } + + rewards_stats.emplace_back(blk, dpos_vote_count, kCommitteeSize); + + return rewards_stats; + } + std::shared_ptr finalize_(PeriodData&& new_blk, std::vector&& finalized_dag_blk_hashes, std::shared_ptr&& anchor) { auto batch = db_->createWriteBatch(); - RewardsStats rewards_stats; - uint64_t dpos_vote_count = kCommitteeSize; - // Block zero - if (!new_blk.previous_block_cert_votes.empty()) [[unlikely]] { - dpos_vote_count = dpos_eligible_total_vote_count(new_blk.previous_block_cert_votes[0]->getPeriod() - 1); - } - // returns list of validators for new_blk.transactions - const std::vector txs_validators = rewards_stats.processStats(new_blk, dpos_vote_count, kCommitteeSize); + auto rewards_stats = prepare_rewards_stats_(new_blk); block_applying_emitter_.emit(block_header()->number + 1); @@ -180,7 +187,7 @@ class FinalChainImpl final : public FinalChain { auto const& [exec_results, state_root, total_reward] = state_api_.transition_state({new_blk.pbft_blk->getBeneficiary(), kBlockGasLimit, new_blk.pbft_blk->getTimestamp(), BlockHeader::difficulty()}, - to_state_api_transactions(new_blk.transactions), txs_validators, {}, rewards_stats); + to_state_api_transactions(new_blk.transactions), rewards_stats); TransactionReceipts receipts; receipts.reserve(exec_results.size()); diff --git a/libraries/core_libs/consensus/src/final_chain/rewards_stats.cpp b/libraries/core_libs/consensus/src/final_chain/rewards_stats.cpp index 809a48d227..45f20a82ea 100644 --- a/libraries/core_libs/consensus/src/final_chain/rewards_stats.cpp +++ b/libraries/core_libs/consensus/src/final_chain/rewards_stats.cpp @@ -2,25 +2,33 @@ #include +#include "pbft/pbft_block.hpp" + namespace taraxa { +RewardsStats::RewardsStats(const PeriodData& block, uint64_t dpos_vote_count, uint32_t committee_size) + : block_author_(block.pbft_blk->getBeneficiary()), + max_votes_weight_(std::min(committee_size, dpos_vote_count)) { + processStats(block); +} + bool RewardsStats::addTransaction(const trx_hash_t& tx_hash, const addr_t& validator) { - auto found_tx = txs_validators_.find(tx_hash); + auto found_tx = validator_by_tx_hash_.find(tx_hash); // Already processed tx - if (found_tx != txs_validators_.end()) { + if (found_tx != validator_by_tx_hash_.end()) { return false; } // New tx - txs_validators_[tx_hash] = validator; + validator_by_tx_hash_[tx_hash] = validator; return true; } std::optional RewardsStats::getTransactionValidator(const trx_hash_t& tx_hash) { - auto found_tx = txs_validators_.find(tx_hash); - if (found_tx == txs_validators_.end()) { + auto found_tx = validator_by_tx_hash_.find(tx_hash); + if (found_tx == validator_by_tx_hash_.end()) { return {}; } @@ -52,12 +60,12 @@ std::set toTrxHashesSet(const SharedTransactions& transactions) { return block_transactions_hashes_; } -void RewardsStats::initStats(const PeriodData& sync_blk, uint64_t dpos_vote_count, uint32_t committee_size) { - txs_validators_.reserve(sync_blk.transactions.size()); - validators_stats_.reserve(std::max(sync_blk.dag_blocks.size(), sync_blk.previous_block_cert_votes.size())); - auto block_transactions_hashes_ = toTrxHashesSet(sync_blk.transactions); +void RewardsStats::processStats(const PeriodData& block) { + validator_by_tx_hash_.reserve(block.transactions.size()); + validators_stats_.reserve(std::max(block.dag_blocks.size(), block.previous_block_cert_votes.size())); + auto block_transactions_hashes_ = toTrxHashesSet(block.transactions); - for (const auto& dag_block : sync_blk.dag_blocks) { + for (const auto& dag_block : block.dag_blocks) { const addr_t& dag_block_author = dag_block.getSender(); bool has_unique_transactions = false; for (const auto& tx_hash : dag_block.getTrxs()) { @@ -78,34 +86,24 @@ void RewardsStats::initStats(const PeriodData& sync_blk, uint64_t dpos_vote_coun } } // total_unique_txs_count_ should be always equal to transactions count in block - assert(txs_validators_.size() == sync_blk.transactions.size()); + assert(validator_by_tx_hash_.size() == block.transactions.size()); - max_votes_weight_ = std::min(committee_size, dpos_vote_count); - for (const auto& vote : sync_blk.previous_block_cert_votes) { + for (const auto& vote : block.previous_block_cert_votes) { addVote(vote); } -} - -std::vector RewardsStats::processStats(const PeriodData& block, uint64_t dpos_vote_count, - uint32_t committee_size) { - initStats(block, dpos_vote_count, committee_size); - - // Dag blocks validators that included transactions to be executed as first in their blocks - std::vector txs_validators; - txs_validators.reserve(block.transactions.size()); + txs_validators_.reserve(block.transactions.size()); for (const auto& tx : block.transactions) { // Non-executed trxs auto tx_validator = getTransactionValidator(tx->getHash()); assert(tx_validator.has_value()); - txs_validators.push_back(*tx_validator); + txs_validators_.push_back(*tx_validator); } - - return txs_validators; } RLP_FIELDS_DEFINE(RewardsStats::ValidatorStats, dag_blocks_count_, vote_weight_) -RLP_FIELDS_DEFINE(RewardsStats, validators_stats_, total_dag_blocks_count_, total_votes_weight_, max_votes_weight_) +RLP_FIELDS_DEFINE(RewardsStats, block_author_, validators_stats_, txs_validators_, total_dag_blocks_count_, + total_votes_weight_, max_votes_weight_) } // namespace taraxa \ No newline at end of file diff --git a/libraries/core_libs/consensus/src/final_chain/state_api.cpp b/libraries/core_libs/consensus/src/final_chain/state_api.cpp index b8d37375e7..c73ceb2f4b 100644 --- a/libraries/core_libs/consensus/src/final_chain/state_api.cpp +++ b/libraries/core_libs/consensus/src/final_chain/state_api.cpp @@ -177,14 +177,11 @@ StateDescriptor StateAPI::get_last_committed_state_descriptor() const { const StateTransitionResult& StateAPI::transition_state(const EVMBlock& block, const util::RangeView& transactions, - const util::RangeView& transactions_validators, - const util::RangeView& uncles, - const RewardsStats& rewards_stats) { + const std::vector& rewards_stats) { result_buf_transition_state_.execution_results.clear(); rlp_enc_transition_state_.clear(); c_method_args_rlp( - this_c_, rlp_enc_transition_state_, result_buf_transition_state_, block, transactions, transactions_validators, - uncles, rewards_stats); + this_c_, rlp_enc_transition_state_, result_buf_transition_state_, block, transactions, rewards_stats); return result_buf_transition_state_; } diff --git a/libraries/types/pbft_block/include/pbft/pbft_block.hpp b/libraries/types/pbft_block/include/pbft/pbft_block.hpp index a830a3afa5..9aa3a35cbf 100644 --- a/libraries/types/pbft_block/include/pbft/pbft_block.hpp +++ b/libraries/types/pbft_block/include/pbft/pbft_block.hpp @@ -16,7 +16,7 @@ namespace taraxa { */ /** - * @brief The PbftBlockk class is a PBFT block class that includes PBFT block hash, previous PBFT block hash, DAG anchor + * @brief The PbftBlock class is a PBFT block class that includes PBFT block hash, previous PBFT block hash, DAG anchor * hash, DAG blocks ordering hash, period number, timestamp, proposer address, and proposer signature. */ class PbftBlock { @@ -35,8 +35,8 @@ class PbftBlock { PbftBlock(const blk_hash_t& prev_blk_hash, const blk_hash_t& dag_blk_hash_as_pivot, const blk_hash_t& order_hash, const blk_hash_t& prev_state_root, PbftPeriod period, const addr_t& beneficiary, const secret_t& sk, std::vector&& reward_votes); - explicit PbftBlock(dev::RLP const& rlp); - explicit PbftBlock(bytes const& RLP); + explicit PbftBlock(const dev::RLP& rlp); + explicit PbftBlock(const bytes& RLP); /** * @brief Secure Hash Algorithm 3 @@ -77,33 +77,33 @@ class PbftBlock { * @param dag_blks DAG blocks hashes * @return PBFT block with DAG blocks in JSON */ - static Json::Value toJson(PbftBlock const& b, std::vector const& dag_blks); + static Json::Value toJson(const PbftBlock& b, const std::vector& dag_blks); /** * @brief Get PBFT block hash * @return PBFT block hash */ - auto const& getBlockHash() const { return block_hash_; } + const auto& getBlockHash() const { return block_hash_; } /** * @brief Get previous PBFT block hash * @return previous PBFT block hash */ - auto const& getPrevBlockHash() const { return prev_block_hash_; } + const auto& getPrevBlockHash() const { return prev_block_hash_; } /** * @brief Get DAG anchor hash for the finalized PBFT block * @return DAG anchor hash */ - auto const& getPivotDagBlockHash() const { return dag_block_hash_as_pivot_; } + const auto& getPivotDagBlockHash() const { return dag_block_hash_as_pivot_; } /** * @brief Get DAG blocks ordering hash * @return DAG blocks ordering hash */ - auto const& getOrderHash() const { return order_hash_; } + const auto& getOrderHash() const { return order_hash_; } - auto const& getPrevStateRoot() const { return prev_state_root_hash_; } + const auto& getPrevStateRoot() const { return prev_state_root_hash_; } /** * @brief Get period number @@ -121,7 +121,8 @@ class PbftBlock { * @brief Get PBFT block proposer address * @return PBFT block proposer address */ - auto const& getBeneficiary() const { return beneficiary_; } + const auto& getBeneficiary() const { return beneficiary_; } + const auto& getRewardVotes() const { return reward_votes_; } private: @@ -136,7 +137,7 @@ class PbftBlock { */ void checkUniqueRewardVotes(); }; -std::ostream& operator<<(std::ostream& strm, PbftBlock const& pbft_blk); +std::ostream& operator<<(std::ostream& strm, const PbftBlock& pbft_blk); /** @}*/ diff --git a/tests/final_chain_test.cpp b/tests/final_chain_test.cpp index a15be2ae36..781223a14b 100644 --- a/tests/final_chain_test.cpp +++ b/tests/final_chain_test.cpp @@ -49,12 +49,15 @@ struct FinalChainTest : WithDataDir { for (const auto& trx : trxs) { trx_hashes.emplace_back(trx->getHash()); } - DagBlock dag_blk({}, {}, {}, trx_hashes, {}, {}, secret_t::random()); + + auto proposer_keys = dev::KeyPair::create(); + DagBlock dag_blk({}, {}, {}, trx_hashes, {}, {}, proposer_keys.secret()); db->saveDagBlock(dag_blk); std::vector reward_votes_hashes; auto pbft_block = std::make_shared(kNullBlockHash, kNullBlockHash, kNullBlockHash, kNullBlockHash, expected_blk_num, - addr_t::random(), dev::KeyPair::create().secret(), std::move(reward_votes_hashes)); + addr_t::random(), proposer_keys.secret(), std::move(reward_votes_hashes)); + std::vector> votes; PeriodData period_data(pbft_block, votes); period_data.dag_blocks.push_back(dag_blk); @@ -62,7 +65,6 @@ struct FinalChainTest : WithDataDir { auto batch = db->createWriteBatch(); db->savePeriodData(period_data, batch); - db->commitWriteBatch(batch); auto result = SUT->finalize(std::move(period_data), {dag_blk.getHash()}).get(); @@ -447,6 +449,9 @@ TEST_F(FinalChainTest, failed_transaction_fee) { auto trx2_1 = std::make_shared(2, 101, 1, gas, dev::bytes(), sk, receiver); advance({trx1}); + auto blk = SUT->block_header(expected_blk_num); + auto proposer_balance = SUT->getBalance(blk->author); + EXPECT_EQ(proposer_balance.first, 21000); advance({trx2}); advance({trx3}); @@ -600,6 +605,26 @@ TEST_F(FinalChainTest, incorrect_estimation_regress) { } } +TEST_F(FinalChainTest, fee_rewards_distribution) { + auto sender_keys = dev::KeyPair::create(); + auto gas = 30000; + + const auto& receiver = dev::KeyPair::create().address(); + const auto& addr = sender_keys.address(); + const auto& sk = sender_keys.secret(); + cfg.genesis.state.initial_balances = {}; + cfg.genesis.state.initial_balances[addr] = 100000; + init(); + const auto gas_price = 1; + auto trx1 = std::make_shared(1, 100, gas_price, gas, dev::bytes(), sk, receiver); + + auto res = advance({trx1}); + auto gas_used = res->trx_receipts.front().gas_used; + auto blk = SUT->block_header(expected_blk_num); + auto proposer_balance = SUT->getBalance(blk->author); + EXPECT_EQ(proposer_balance.first, gas_used * gas_price); +} + // This test should be last as state_api isn't destructed correctly because of exception TEST_F(FinalChainTest, initial_validator_exceed_maximum_stake) { const dev::KeyPair key = dev::KeyPair::create(); diff --git a/tests/state_api_test.cpp b/tests/state_api_test.cpp index 49ce8fbe72..9d27f86220 100644 --- a/tests/state_api_test.cpp +++ b/tests/state_api_test.cpp @@ -25,9 +25,8 @@ struct TestBlock { h256 state_root; EVMBlock evm_block; vector transactions; - vector uncle_blocks; - RLP_FIELDS_DEFINE_INPLACE(hash, state_root, evm_block, transactions, uncle_blocks) + RLP_FIELDS_DEFINE_INPLACE(hash, state_root, evm_block, transactions) }; template @@ -202,8 +201,7 @@ TEST_F(StateAPITest, DISABLED_eth_mainnet_smoke) { progress_pct_log_threshold += 10; } auto const& test_block = test_blocks[blk_num]; - auto const& result = - SUT.transition_state(test_block.evm_block, test_block.transactions, {}, test_block.uncle_blocks); + auto const& result = SUT.transition_state(test_block.evm_block, test_block.transactions); ASSERT_EQ(result.state_root, test_block.state_root); SUT.transition_state_commit(); } From 363dc8ef406aeed45237acf2edf3c9121e490b10 Mon Sep 17 00:00:00 2001 From: Dmytro Kostenko Date: Fri, 5 May 2023 13:18:34 +0200 Subject: [PATCH 2/3] refector: move process rewards stats functionality to separate class --- libraries/core_libs/CMakeLists.txt | 6 +++ .../final_chain/contract_interface.hpp | 1 - .../include/final_chain/state_api.hpp | 4 +- .../block_stats.hpp} | 16 ++++---- .../include/rewards/rewards_stats.hpp | 17 ++++++++ .../consensus/src/final_chain/final_chain.cpp | 24 +++-------- .../consensus/src/final_chain/state_api.cpp | 2 +- .../block_stats.cpp} | 20 ++++----- .../consensus/src/rewards/rewards_stats.cpp | 41 +++++++++++++++++++ submodules/taraxa-evm | 2 +- 10 files changed, 91 insertions(+), 42 deletions(-) rename libraries/core_libs/consensus/include/{final_chain/rewards_stats.hpp => rewards/block_stats.hpp} (83%) create mode 100644 libraries/core_libs/consensus/include/rewards/rewards_stats.hpp rename libraries/core_libs/consensus/src/{final_chain/rewards_stats.cpp => rewards/block_stats.cpp} (81%) create mode 100644 libraries/core_libs/consensus/src/rewards/rewards_stats.cpp diff --git a/libraries/core_libs/CMakeLists.txt b/libraries/core_libs/CMakeLists.txt index b7bb353dcd..b5d3266a2c 100644 --- a/libraries/core_libs/CMakeLists.txt +++ b/libraries/core_libs/CMakeLists.txt @@ -15,11 +15,15 @@ file(GLOB_RECURSE STORAGE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/storage/*.cpp) file(GLOB_RECURSE NODE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/node/*.hpp) file(GLOB_RECURSE NODE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/node/*.cpp) +file(GLOB_RECURSE REWARDS_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/rewards/*.hpp) +file(GLOB_RECURSE REWARDS_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/rewards/*.cpp) + set(HEADERS ${CONSENSUS_HEADERS} ${NETWORK_HEADERS} ${STORAGE_HEADERS} ${NODE_HEADERS} + ${REWARDS_HEADERS} ) set(SOURCES @@ -28,6 +32,7 @@ set(SOURCES ${STORAGE_SOURCES} ${NODE_SOURCES} ${GRAPHQL_GENERATED_SOURCES} + ${REWARDS_SOURCES} ) add_library(core_libs ${SOURCES} ${HEADERS}) @@ -36,6 +41,7 @@ target_include_directories(core_libs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/consensu target_include_directories(core_libs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/network/include) target_include_directories(core_libs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/node/include) target_include_directories(core_libs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/storage/include) +target_include_directories(core_libs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/rewards/include) target_include_directories(core_libs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # GraphQL target_include_directories(core_libs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/network/graphql/gen) diff --git a/libraries/core_libs/consensus/include/final_chain/contract_interface.hpp b/libraries/core_libs/consensus/include/final_chain/contract_interface.hpp index 9c4820edec..a4b7ddabb3 100644 --- a/libraries/core_libs/consensus/include/final_chain/contract_interface.hpp +++ b/libraries/core_libs/consensus/include/final_chain/contract_interface.hpp @@ -6,7 +6,6 @@ #include "common/types.hpp" #include "final_chain/final_chain.hpp" -#include "final_chain/rewards_stats.hpp" namespace taraxa::final_chain { class ContractInterface { diff --git a/libraries/core_libs/consensus/include/final_chain/state_api.hpp b/libraries/core_libs/consensus/include/final_chain/state_api.hpp index 691494456f..c30763f864 100644 --- a/libraries/core_libs/consensus/include/final_chain/state_api.hpp +++ b/libraries/core_libs/consensus/include/final_chain/state_api.hpp @@ -5,8 +5,8 @@ #include #include "common/range_view.hpp" -#include "final_chain/rewards_stats.hpp" #include "final_chain/state_api_data.hpp" +#include "rewards/block_stats.hpp" #include "storage/storage.hpp" namespace taraxa::state_api { @@ -43,7 +43,7 @@ class StateAPI { StateDescriptor get_last_committed_state_descriptor() const; const StateTransitionResult& transition_state(const EVMBlock& block, const util::RangeView& transactions, - const std::vector& rewards_stats = {}); + const std::vector& rewards_stats = {}); void transition_state_commit(); void create_snapshot(PbftPeriod period); void prune(const std::vector& state_root_to_keep, EthBlockNumber blk_num); diff --git a/libraries/core_libs/consensus/include/final_chain/rewards_stats.hpp b/libraries/core_libs/consensus/include/rewards/block_stats.hpp similarity index 83% rename from libraries/core_libs/consensus/include/final_chain/rewards_stats.hpp rename to libraries/core_libs/consensus/include/rewards/block_stats.hpp index c5ce0a0b1c..a3f0f057ca 100644 --- a/libraries/core_libs/consensus/include/final_chain/rewards_stats.hpp +++ b/libraries/core_libs/consensus/include/rewards/block_stats.hpp @@ -7,13 +7,13 @@ #include "pbft/period_data.hpp" #include "vote/vote.hpp" -namespace taraxa { +namespace taraxa::rewards { /** * @class RewardsStats * @brief RewardsStats contains rewards statistics for single pbft block */ -class RewardsStats { +class BlockStats { public: /** * @brief setting block_author_, max_votes_weight_ and calls processStats function @@ -21,18 +21,15 @@ class RewardsStats { * @param dpos_vote_count - votes count for previous block * @param committee_size */ - RewardsStats(const PeriodData& block, uint64_t dpos_vote_count, uint32_t committee_size); + BlockStats(const PeriodData& block, uint64_t dpos_vote_count, uint32_t committee_size); HAS_RLP_FIELDS private: /** - * @brief Process PeriodData and returns vector of validators, who included provided block.transactions as first in - * dag block, e.g. returned validator on position 2 included transaction block.transactions[2] as first in his dag - * block + * @brief Process PeriodData and save stats in class for future serialization. returns * * @param block - * @return vector of validators */ void processStats(const PeriodData& block); /** @@ -78,7 +75,8 @@ class RewardsStats { // Transactions validators: tx hash -> validator that included it as first in his block std::unordered_map validator_by_tx_hash_; - // Vector with all transactions validators + // Vector with all transactions validators, who included provided block.transactions as first in dag block, + // e.g. returned validator on position 2 included transaction block.transactions[2] as first in his dag block std::vector txs_validators_; // Txs stats: validator -> ValidatorStats @@ -94,4 +92,4 @@ class RewardsStats { uint64_t max_votes_weight_{0}; }; -} // namespace taraxa \ No newline at end of file +} // namespace taraxa::rewards \ No newline at end of file diff --git a/libraries/core_libs/consensus/include/rewards/rewards_stats.hpp b/libraries/core_libs/consensus/include/rewards/rewards_stats.hpp new file mode 100644 index 0000000000..062d6a7b8d --- /dev/null +++ b/libraries/core_libs/consensus/include/rewards/rewards_stats.hpp @@ -0,0 +1,17 @@ +#include "config/hardfork.hpp" +#include "rewards/block_stats.hpp" + +namespace taraxa::rewards { +class Stats { + public: + Stats(uint32_t committee_size, std::function&& dpos_eligible_total_vote_count); + + std::vector getStats(const PeriodData& current_blk); + + private: + BlockStats getBlockStats(const PeriodData& current_blk); + + const uint32_t kCommitteeSize; + const std::function dpos_eligible_total_vote_count_; +}; +} // namespace taraxa::rewards \ No newline at end of file diff --git a/libraries/core_libs/consensus/src/final_chain/final_chain.cpp b/libraries/core_libs/consensus/src/final_chain/final_chain.cpp index 53256f9afe..785ba45e5c 100644 --- a/libraries/core_libs/consensus/src/final_chain/final_chain.cpp +++ b/libraries/core_libs/consensus/src/final_chain/final_chain.cpp @@ -6,20 +6,21 @@ #include "common/constants.hpp" #include "common/thread_pool.hpp" #include "final_chain/cache.hpp" -#include "final_chain/rewards_stats.hpp" #include "final_chain/trie_common.hpp" +#include "rewards/rewards_stats.hpp" #include "vote/vote.hpp" namespace taraxa::final_chain { class FinalChainImpl final : public FinalChain { std::shared_ptr db_; - const uint32_t kCommitteeSize; const uint64_t kBlockGasLimit; StateAPI state_api_; const bool kLightNode = false; const uint64_t kLightNodeHistory = 0; const uint32_t kMaxLevelsPerPeriod; + const uint32_t kRewardsDistributionInterval = 100; + rewards::Stats rewards_; // It is not prepared to use more then 1 thread. Examine it if you want to change threads count boost::asio::thread_pool executor_thread_{1}; @@ -49,7 +50,6 @@ class FinalChainImpl final : public FinalChain { public: FinalChainImpl(const std::shared_ptr& db, const taraxa::FullNodeConfig& config, const addr_t& node_addr) : db_(db), - kCommitteeSize(config.genesis.pbft.committee_size), kBlockGasLimit(config.genesis.pbft.gas_limit), state_api_([this](auto n) { return block_hash(n).value_or(ZeroHash()); }, // config.genesis.state, config.opts_final_chain, @@ -59,6 +59,8 @@ class FinalChainImpl final : public FinalChain { kLightNode(config.is_light_node), kLightNodeHistory(config.light_node_history), kMaxLevelsPerPeriod(config.max_levels_per_period), + rewards_(config.genesis.pbft.committee_size, + [this](EthBlockNumber n) { return dpos_eligible_total_vote_count(n); }), block_headers_cache_(config.final_chain_cache_in_blocks, [this](uint64_t blk) { return get_block_header(blk); }), block_hashes_cache_(config.final_chain_cache_in_blocks, [this](uint64_t blk) { return get_block_hash(blk); }), @@ -144,26 +146,12 @@ class FinalChainImpl final : public FinalChain { EthBlockNumber delegation_delay() const override { return delegation_delay_; } - std::vector prepare_rewards_stats_(const PeriodData& blk) { - std::vector rewards_stats; - uint64_t dpos_vote_count = kCommitteeSize; - - // Block zero - if (!blk.previous_block_cert_votes.empty()) [[likely]] { - dpos_vote_count = dpos_eligible_total_vote_count(blk.previous_block_cert_votes[0]->getPeriod() - 1); - } - - rewards_stats.emplace_back(blk, dpos_vote_count, kCommitteeSize); - - return rewards_stats; - } - std::shared_ptr finalize_(PeriodData&& new_blk, std::vector&& finalized_dag_blk_hashes, std::shared_ptr&& anchor) { auto batch = db_->createWriteBatch(); - auto rewards_stats = prepare_rewards_stats_(new_blk); + auto rewards_stats = rewards_.getStats(new_blk); block_applying_emitter_.emit(block_header()->number + 1); diff --git a/libraries/core_libs/consensus/src/final_chain/state_api.cpp b/libraries/core_libs/consensus/src/final_chain/state_api.cpp index c73ceb2f4b..3e29cf8b97 100644 --- a/libraries/core_libs/consensus/src/final_chain/state_api.cpp +++ b/libraries/core_libs/consensus/src/final_chain/state_api.cpp @@ -177,7 +177,7 @@ StateDescriptor StateAPI::get_last_committed_state_descriptor() const { const StateTransitionResult& StateAPI::transition_state(const EVMBlock& block, const util::RangeView& transactions, - const std::vector& rewards_stats) { + const std::vector& rewards_stats) { result_buf_transition_state_.execution_results.clear(); rlp_enc_transition_state_.clear(); c_method_args_rlp( diff --git a/libraries/core_libs/consensus/src/final_chain/rewards_stats.cpp b/libraries/core_libs/consensus/src/rewards/block_stats.cpp similarity index 81% rename from libraries/core_libs/consensus/src/final_chain/rewards_stats.cpp rename to libraries/core_libs/consensus/src/rewards/block_stats.cpp index 45f20a82ea..2e6d1bfbeb 100644 --- a/libraries/core_libs/consensus/src/final_chain/rewards_stats.cpp +++ b/libraries/core_libs/consensus/src/rewards/block_stats.cpp @@ -1,18 +1,18 @@ -#include "final_chain/rewards_stats.hpp" +#include "rewards/block_stats.hpp" #include #include "pbft/pbft_block.hpp" -namespace taraxa { +namespace taraxa::rewards { -RewardsStats::RewardsStats(const PeriodData& block, uint64_t dpos_vote_count, uint32_t committee_size) +BlockStats::BlockStats(const PeriodData& block, uint64_t dpos_vote_count, uint32_t committee_size) : block_author_(block.pbft_blk->getBeneficiary()), max_votes_weight_(std::min(committee_size, dpos_vote_count)) { processStats(block); } -bool RewardsStats::addTransaction(const trx_hash_t& tx_hash, const addr_t& validator) { +bool BlockStats::addTransaction(const trx_hash_t& tx_hash, const addr_t& validator) { auto found_tx = validator_by_tx_hash_.find(tx_hash); // Already processed tx @@ -26,7 +26,7 @@ bool RewardsStats::addTransaction(const trx_hash_t& tx_hash, const addr_t& valid return true; } -std::optional RewardsStats::getTransactionValidator(const trx_hash_t& tx_hash) { +std::optional BlockStats::getTransactionValidator(const trx_hash_t& tx_hash) { auto found_tx = validator_by_tx_hash_.find(tx_hash); if (found_tx == validator_by_tx_hash_.end()) { return {}; @@ -35,7 +35,7 @@ std::optional RewardsStats::getTransactionValidator(const trx_hash_t& tx return {found_tx->second}; } -bool RewardsStats::addVote(const std::shared_ptr& vote) { +bool BlockStats::addVote(const std::shared_ptr& vote) { // Set valid cert vote to validator auto& validator_stats = validators_stats_[vote->getVoterAddr()]; assert(validator_stats.vote_weight_ == 0); @@ -60,7 +60,7 @@ std::set toTrxHashesSet(const SharedTransactions& transactions) { return block_transactions_hashes_; } -void RewardsStats::processStats(const PeriodData& block) { +void BlockStats::processStats(const PeriodData& block) { validator_by_tx_hash_.reserve(block.transactions.size()); validators_stats_.reserve(std::max(block.dag_blocks.size(), block.previous_block_cert_votes.size())); auto block_transactions_hashes_ = toTrxHashesSet(block.transactions); @@ -102,8 +102,8 @@ void RewardsStats::processStats(const PeriodData& block) { } } -RLP_FIELDS_DEFINE(RewardsStats::ValidatorStats, dag_blocks_count_, vote_weight_) -RLP_FIELDS_DEFINE(RewardsStats, block_author_, validators_stats_, txs_validators_, total_dag_blocks_count_, +RLP_FIELDS_DEFINE(BlockStats::ValidatorStats, dag_blocks_count_, vote_weight_) +RLP_FIELDS_DEFINE(BlockStats, block_author_, validators_stats_, txs_validators_, total_dag_blocks_count_, total_votes_weight_, max_votes_weight_) -} // namespace taraxa \ No newline at end of file +} // namespace taraxa::rewards \ No newline at end of file diff --git a/libraries/core_libs/consensus/src/rewards/rewards_stats.cpp b/libraries/core_libs/consensus/src/rewards/rewards_stats.cpp new file mode 100644 index 0000000000..fa201db1ce --- /dev/null +++ b/libraries/core_libs/consensus/src/rewards/rewards_stats.cpp @@ -0,0 +1,41 @@ +#include "rewards/rewards_stats.hpp" + +#include "storage/storage.hpp" + +namespace taraxa::rewards { +Stats::Stats(uint32_t committee_size, std::function&& dpos_eligible_total_vote_count) + : kCommitteeSize(committee_size), dpos_eligible_total_vote_count_(dpos_eligible_total_vote_count) {} + +// std::vector processBlockHardfork(const PeriodData& current_blk, uint32_t interval) { +// const auto current = current_blk.pbft_blk->getPeriod(); +// // skip for intermediate blocks +// if (current % interval != 0) { +// return {}; +// } + +// std::vector rewards_stats; +// rewards_stats.reserve(interval); +// // add rewards stats for (last_distribution_block, current_block) +// for (auto p = current - interval + 1; p < current; ++p) { +// auto blk = PeriodData(db_->getPeriodDataRaw(p)); +// rewards_stats.emplace_back(get_block_rewards_stats(blk)); +// } +// // add current block rewards stats +// rewards_stats.emplace_back(get_block_rewards_stats(current_blk)); + +// return rewards_stats; +// } + +BlockStats Stats::getBlockStats(const PeriodData& blk) { + uint64_t dpos_vote_count = kCommitteeSize; + + // Block zero + if (!blk.previous_block_cert_votes.empty()) [[likely]] { + dpos_vote_count = dpos_eligible_total_vote_count_(blk.previous_block_cert_votes[0]->getPeriod() - 1); + } + + return BlockStats{blk, dpos_vote_count, kCommitteeSize}; +} + +std::vector Stats::getStats(const PeriodData& current_blk) { return {getBlockStats(current_blk)}; } +} // namespace taraxa::rewards \ No newline at end of file diff --git a/submodules/taraxa-evm b/submodules/taraxa-evm index c620c6fe72..ffd207a4cb 160000 --- a/submodules/taraxa-evm +++ b/submodules/taraxa-evm @@ -1 +1 @@ -Subproject commit c620c6fe72301f6eef8d0fe24c5bdf8db8e9f39d +Subproject commit ffd207a4cb80919c1cdbf26f919b3a158ce497f3 From 184a7a25c0d350039afcaee180d66845342ad4f1 Mon Sep 17 00:00:00 2001 From: Dmytro Kostenko Date: Tue, 9 May 2023 12:39:19 +0200 Subject: [PATCH 3/3] feat: add option to change rewards distribution frequency with hardfork --- .../config_jsons/devnet/devnet_genesis.json | 4 + .../config_jsons/mainnet/mainnet_genesis.json | 4 + .../config_jsons/testnet/testnet_genesis.json | 4 + libraries/config/CMakeLists.txt | 4 +- libraries/config/include/config/hardfork.hpp | 13 +- .../config/include/config/state_config.hpp | 4 +- libraries/config/src/hardfork.cpp | 17 +- libraries/config/src/state_config.cpp | 4 +- .../consensus/include/rewards/block_stats.hpp | 4 +- .../include/rewards/rewards_stats.hpp | 43 +++- .../consensus/src/final_chain/final_chain.cpp | 4 +- .../consensus/src/rewards/rewards_stats.cpp | 80 +++++--- .../storage/include/storage/storage.hpp | 3 + libraries/core_libs/storage/src/storage.cpp | 10 + tests/CMakeLists.txt | 3 + tests/rewards_stats_test.cpp | 185 ++++++++++++++++++ .../test_util/include/test_util/test_util.hpp | 2 +- tests/test_util/src/test_util.cpp | 6 +- 18 files changed, 350 insertions(+), 44 deletions(-) create mode 100644 tests/rewards_stats_test.cpp diff --git a/libraries/cli/include/cli/config_jsons/devnet/devnet_genesis.json b/libraries/cli/include/cli/config_jsons/devnet/devnet_genesis.json index f86fc92377..ff7bf49bb2 100644 --- a/libraries/cli/include/cli/config_jsons/devnet/devnet_genesis.json +++ b/libraries/cli/include/cli/config_jsons/devnet/devnet_genesis.json @@ -226,6 +226,10 @@ "7e4aa664f71de4e9d0b4a6473d796372639bdcde": "0x1027e72f1f12813088000000", "ee1326fbf7d9322e5ea02c6fe5eb63535fceccd1": "0x52b7d2dcc80cd2e4000000" }, + "hardforks": { + "rewards_distribution_frequency": { + } + }, "gas_price": { "blocks": 200, "percentile": 60, diff --git a/libraries/cli/include/cli/config_jsons/mainnet/mainnet_genesis.json b/libraries/cli/include/cli/config_jsons/mainnet/mainnet_genesis.json index 5c9777aae6..a488289bce 100644 --- a/libraries/cli/include/cli/config_jsons/mainnet/mainnet_genesis.json +++ b/libraries/cli/include/cli/config_jsons/mainnet/mainnet_genesis.json @@ -243,6 +243,10 @@ } ] }, + "hardforks": { + "rewards_distribution_frequency": { + } + }, "initial_balances": { "723304d1357a2334fcf902aa3d232f5139080a1b": "0xd53323b7ca3737afbb45000", "b0800c7af0a6aec0ff8dbe01708bd8e300c6305b": "0x208b1d135e4a8000", diff --git a/libraries/cli/include/cli/config_jsons/testnet/testnet_genesis.json b/libraries/cli/include/cli/config_jsons/testnet/testnet_genesis.json index f07f92f352..7a7f30890f 100644 --- a/libraries/cli/include/cli/config_jsons/testnet/testnet_genesis.json +++ b/libraries/cli/include/cli/config_jsons/testnet/testnet_genesis.json @@ -1698,6 +1698,10 @@ "a903715b57d3bf62e098a6a643c6924d9bdacec4": "0x170a0f5040e50400000", "5bd47fef8e8dcb6677c2957ecd78b8232354f145": "0x191cf61eb2bec223400" }, + "hardforks": { + "rewards_distribution_frequency": { + } + }, "gas_price": { "blocks": 200, "percentile": 60, diff --git a/libraries/config/CMakeLists.txt b/libraries/config/CMakeLists.txt index 75616c110c..5df88e6af7 100644 --- a/libraries/config/CMakeLists.txt +++ b/libraries/config/CMakeLists.txt @@ -7,7 +7,7 @@ set(HEADERS include/config/dag_config.hpp include/config/pbft_config.hpp include/config/state_config.hpp - # include/config/hardfork.hpp + include/config/hardfork.hpp ) set(SOURCES @@ -18,7 +18,7 @@ set(SOURCES src/dag_config.cpp src/pbft_config.cpp src/state_config.cpp - # src/hardfork.cpp + src/hardfork.cpp ) # Configure file with version diff --git a/libraries/config/include/config/hardfork.hpp b/libraries/config/include/config/hardfork.hpp index e0ca9262a5..f0364b5dbc 100644 --- a/libraries/config/include/config/hardfork.hpp +++ b/libraries/config/include/config/hardfork.hpp @@ -5,7 +5,18 @@ #include "common/encoding_rlp.hpp" struct Hardforks { - uint64_t fix_genesis_fork_block = 0; + /* + * @brief key is block number at which change is applied and value is new distribution interval. + * Default distribution frequency is every block + * To change rewards distribution frequency we should add a new element in map below. + * For example {{101, 20}, {201, 10}} means: + * 1. for blocks [1,100] we are distributing rewards every block + * 2. for blocks [101, 200] rewards are distributed every 20 block. On blocks 120, 140, etc. + * 3. for blocks after 201 rewards are distributed every 10 block. On blocks 210, 220, etc. + */ + using RewardsDistributionMap = std::map; + RewardsDistributionMap rewards_distribution_frequency; + HAS_RLP_FIELDS }; diff --git a/libraries/config/include/config/state_config.hpp b/libraries/config/include/config/state_config.hpp index 1cbda1b401..15a0a6da18 100644 --- a/libraries/config/include/config/state_config.hpp +++ b/libraries/config/include/config/state_config.hpp @@ -6,7 +6,7 @@ #include "common/encoding_rlp.hpp" #include "common/types.hpp" #include "common/vrf_wrapper.hpp" -// #include "config/hardfork.hpp" +#include "config/hardfork.hpp" namespace taraxa::state_api { @@ -61,7 +61,7 @@ struct Config { EVMChainConfig evm_chain_config; BalanceMap initial_balances; DPOSConfig dpos; - // Hardforks hardforks; + Hardforks hardforks; HAS_RLP_FIELDS }; diff --git a/libraries/config/src/hardfork.cpp b/libraries/config/src/hardfork.cpp index 950558fc19..fc4f804c0e 100644 --- a/libraries/config/src/hardfork.cpp +++ b/libraries/config/src/hardfork.cpp @@ -2,14 +2,23 @@ Json::Value enc_json(const Hardforks& obj) { Json::Value json(Json::objectValue); - json["fix_genesis_fork_block"] = dev::toJS(obj.fix_genesis_fork_block); + + auto& rewards = json["rewards_distribution_frequency"]; + rewards = Json::objectValue; + for (auto i = obj.rewards_distribution_frequency.begin(); i != obj.rewards_distribution_frequency.end(); ++i) { + rewards[std::to_string(i->first)] = i->second; + } return json; } void dec_json(const Json::Value& json, Hardforks& obj) { - if (auto const& e = json["fix_genesis_fork_block"]) { - obj.fix_genesis_fork_block = dev::getUInt(e); + if (const auto& e = json["rewards_distribution_frequency"]) { + assert(e.isObject()); + + for (auto itr = e.begin(); itr != e.end(); ++itr) { + obj.rewards_distribution_frequency[itr.key().asUInt64()] = itr->asUInt64(); + } } } -RLP_FIELDS_DEFINE(Hardforks, fix_genesis_fork_block) +RLP_FIELDS_DEFINE(Hardforks, rewards_distribution_frequency) diff --git a/libraries/config/src/state_config.cpp b/libraries/config/src/state_config.cpp index 29278f78e8..a284a65e3e 100644 --- a/libraries/config/src/state_config.cpp +++ b/libraries/config/src/state_config.cpp @@ -22,14 +22,14 @@ void dec_json(const Json::Value& /*json*/, uint64_t chain_id, EVMChainConfig& ob void append_json(Json::Value& json, const Config& obj) { json["evm_chain_config"] = enc_json(obj.evm_chain_config); json["initial_balances"] = enc_json(obj.initial_balances); - // json["hardforks"] = enc_json(obj.hardforks); + json["hardforks"] = enc_json(obj.hardforks); json["dpos"] = enc_json(obj.dpos); } void dec_json(const Json::Value& json, Config& obj) { dec_json(json["evm_chain_config"], json["chain_id"].asUInt(), obj.evm_chain_config); dec_json(json["initial_balances"], obj.initial_balances); - // dec_json(json["hardforks"], obj.hardforks); + dec_json(json["hardforks"], obj.hardforks); dec_json(json["dpos"], obj.dpos); } diff --git a/libraries/core_libs/consensus/include/rewards/block_stats.hpp b/libraries/core_libs/consensus/include/rewards/block_stats.hpp index a3f0f057ca..2e80eff84b 100644 --- a/libraries/core_libs/consensus/include/rewards/block_stats.hpp +++ b/libraries/core_libs/consensus/include/rewards/block_stats.hpp @@ -15,6 +15,8 @@ namespace taraxa::rewards { */ class BlockStats { public: + // Needed for RLP + BlockStats() = default; /** * @brief setting block_author_, max_votes_weight_ and calls processStats function * @@ -58,7 +60,7 @@ class BlockStats { */ bool addVote(const std::shared_ptr& vote); - private: + protected: struct ValidatorStats { // count of rewardable(with 1 or more unique transactions) DAG blocks produced by this validator uint32_t dag_blocks_count_ = 0; diff --git a/libraries/core_libs/consensus/include/rewards/rewards_stats.hpp b/libraries/core_libs/consensus/include/rewards/rewards_stats.hpp index 062d6a7b8d..22fca73188 100644 --- a/libraries/core_libs/consensus/include/rewards/rewards_stats.hpp +++ b/libraries/core_libs/consensus/include/rewards/rewards_stats.hpp @@ -1,17 +1,54 @@ #include "config/hardfork.hpp" #include "rewards/block_stats.hpp" +#include "storage/storage.hpp" namespace taraxa::rewards { +/* + * @brief class that is managing rewards stats processing and hardforks(intervals changes) + * So intermediate blocks stats are stored in the vector in data(to restore on the node restart) + * and full list of interval stats is returned in the end of interval + */ class Stats { public: - Stats(uint32_t committee_size, std::function&& dpos_eligible_total_vote_count); + Stats(uint32_t committee_size, const Hardforks::RewardsDistributionMap& rdm, std::shared_ptr db, + std::function&& dpos_eligible_total_vote_count); - std::vector getStats(const PeriodData& current_blk); + /* + * @brief processing passed block and returns stats that should be processed at this block + * @param current_blk block to process + * @return vector that should be processed at current block + */ + std::vector processStats(const PeriodData& current_blk); - private: + protected: + /* + * @brief load current interval stats from database + */ + void loadFromDb(); + /* + * @brief returns rewards distribution frequency for specified period + */ + uint32_t getCurrentDistributionFrequency(uint64_t current_period) const; + /* + * @brief gets all needed data and makes(processes) BlocksStats + * @param current_blk block to process + * @return block statistics needed for rewards distribution + */ BlockStats getBlockStats(const PeriodData& current_blk); + /* + * @brief saves stats to database to not lose this data in case of node restart + */ + void saveBlockStats(uint64_t number, const BlockStats& stats); + /* + * @brief called on start of new rewards interval. clears blocks_stats_ collection + * and removes all data saved in db column + */ + void clear(); const uint32_t kCommitteeSize; + const Hardforks::RewardsDistributionMap kRewardsDistributionFrequency; + std::shared_ptr db_; const std::function dpos_eligible_total_vote_count_; + std::vector blocks_stats_; }; } // namespace taraxa::rewards \ No newline at end of file diff --git a/libraries/core_libs/consensus/src/final_chain/final_chain.cpp b/libraries/core_libs/consensus/src/final_chain/final_chain.cpp index 785ba45e5c..752e256d27 100644 --- a/libraries/core_libs/consensus/src/final_chain/final_chain.cpp +++ b/libraries/core_libs/consensus/src/final_chain/final_chain.cpp @@ -59,7 +59,7 @@ class FinalChainImpl final : public FinalChain { kLightNode(config.is_light_node), kLightNodeHistory(config.light_node_history), kMaxLevelsPerPeriod(config.max_levels_per_period), - rewards_(config.genesis.pbft.committee_size, + rewards_(config.genesis.pbft.committee_size, config.genesis.state.hardforks.rewards_distribution_frequency, db_, [this](EthBlockNumber n) { return dpos_eligible_total_vote_count(n); }), block_headers_cache_(config.final_chain_cache_in_blocks, [this](uint64_t blk) { return get_block_header(blk); }), @@ -151,7 +151,7 @@ class FinalChainImpl final : public FinalChain { std::shared_ptr&& anchor) { auto batch = db_->createWriteBatch(); - auto rewards_stats = rewards_.getStats(new_blk); + auto rewards_stats = rewards_.processStats(new_blk); block_applying_emitter_.emit(block_header()->number + 1); diff --git a/libraries/core_libs/consensus/src/rewards/rewards_stats.cpp b/libraries/core_libs/consensus/src/rewards/rewards_stats.cpp index fa201db1ce..1d4a03f5e5 100644 --- a/libraries/core_libs/consensus/src/rewards/rewards_stats.cpp +++ b/libraries/core_libs/consensus/src/rewards/rewards_stats.cpp @@ -3,28 +3,41 @@ #include "storage/storage.hpp" namespace taraxa::rewards { -Stats::Stats(uint32_t committee_size, std::function&& dpos_eligible_total_vote_count) - : kCommitteeSize(committee_size), dpos_eligible_total_vote_count_(dpos_eligible_total_vote_count) {} - -// std::vector processBlockHardfork(const PeriodData& current_blk, uint32_t interval) { -// const auto current = current_blk.pbft_blk->getPeriod(); -// // skip for intermediate blocks -// if (current % interval != 0) { -// return {}; -// } - -// std::vector rewards_stats; -// rewards_stats.reserve(interval); -// // add rewards stats for (last_distribution_block, current_block) -// for (auto p = current - interval + 1; p < current; ++p) { -// auto blk = PeriodData(db_->getPeriodDataRaw(p)); -// rewards_stats.emplace_back(get_block_rewards_stats(blk)); -// } -// // add current block rewards stats -// rewards_stats.emplace_back(get_block_rewards_stats(current_blk)); - -// return rewards_stats; -// } +Stats::Stats(uint32_t committee_size, const Hardforks::RewardsDistributionMap& rdm, std::shared_ptr db, + std::function&& dpos_eligible_total_vote_count) + : kCommitteeSize(committee_size), + kRewardsDistributionFrequency(rdm), + db_(std::move(db)), + dpos_eligible_total_vote_count_(dpos_eligible_total_vote_count) { + loadFromDb(); +} + +void Stats::loadFromDb() { + auto i = db_->getColumnIterator(DB::Columns::block_rewards_stats); + for (i->SeekToFirst(); i->Valid(); i->Next()) { + blocks_stats_.push_back(util::rlp_dec(dev::RLP(i->value().ToString()))); + } +} + +void Stats::saveBlockStats(uint64_t period, const BlockStats& stats) { + dev::RLPStream encoding; + stats.rlp(encoding); + + db_->insert(DB::Columns::block_rewards_stats, period, encoding.out()); +} + +uint32_t Stats::getCurrentDistributionFrequency(uint64_t current_block) const { + auto itr = kRewardsDistributionFrequency.upper_bound(current_block); + if (kRewardsDistributionFrequency.empty() || itr == kRewardsDistributionFrequency.begin()) { + return 1; + } + return (--itr)->second; +} + +void Stats::clear() { + blocks_stats_.clear(); + db_->deleteColumnData(DB::Columns::block_rewards_stats); +} BlockStats Stats::getBlockStats(const PeriodData& blk) { uint64_t dpos_vote_count = kCommitteeSize; @@ -37,5 +50,26 @@ BlockStats Stats::getBlockStats(const PeriodData& blk) { return BlockStats{blk, dpos_vote_count, kCommitteeSize}; } -std::vector Stats::getStats(const PeriodData& current_blk) { return {getBlockStats(current_blk)}; } +std::vector Stats::processStats(const PeriodData& current_blk) { + const auto current_period = current_blk.pbft_blk->getPeriod(); + const auto frequency = getCurrentDistributionFrequency(current_period); + + // Distribute rewards every block + if (frequency == 1) { + return {getBlockStats(current_blk)}; + } + + blocks_stats_.push_back(getBlockStats(current_blk)); + // Blocks between distribution. Process and save for future processing + if (current_period % frequency != 0) { + // Save to db, so in case of restart data could be just loaded for the period + saveBlockStats(current_period, *blocks_stats_.rbegin()); + return {}; + } + + std::vector res(std::move(blocks_stats_)); + clear(); + return res; +} + } // namespace taraxa::rewards \ No newline at end of file diff --git a/libraries/core_libs/storage/include/storage/storage.hpp b/libraries/core_libs/storage/include/storage/storage.hpp index 8d6f00182d..1dedb67234 100644 --- a/libraries/core_libs/storage/include/storage/storage.hpp +++ b/libraries/core_libs/storage/include/storage/storage.hpp @@ -121,6 +121,8 @@ class DbStorage : public std::enable_shared_from_this { COLUMN(final_chain_log_blooms_index); COLUMN_W_COMP(sortition_params_change, getIntComparator()); + COLUMN_W_COMP(block_rewards_stats, getIntComparator()); + #undef COLUMN #undef COLUMN_W_COMP }; @@ -177,6 +179,7 @@ class DbStorage : public std::enable_shared_from_this { void disableSnapshots(); void enableSnapshots(); void updateDbVersions(); + void deleteColumnData(const Column& c); uint32_t getMajorVersion() const; std::unique_ptr getColumnIterator(const Column& c); diff --git a/libraries/core_libs/storage/src/storage.cpp b/libraries/core_libs/storage/src/storage.cpp index aeb076e2b8..feb9d05f4b 100644 --- a/libraries/core_libs/storage/src/storage.cpp +++ b/libraries/core_libs/storage/src/storage.cpp @@ -91,6 +91,16 @@ void DbStorage::updateDbVersions() { saveStatusField(StatusDbField::DbMinorVersion, TARAXA_DB_MINOR_VERSION); } +void DbStorage::deleteColumnData(const Column& c) { + checkStatus(db_->DropColumnFamily(handle(c))); + + auto options = rocksdb::ColumnFamilyOptions(); + if (c.comparator_) { + options.comparator = c.comparator_; + } + checkStatus(db_->CreateColumnFamily(options, c.name(), &handles_[c.ordinal_])); +} + void DbStorage::rebuildColumns(const rocksdb::Options& options) { std::unique_ptr db; std::vector column_families; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b5b9bc2bf3..f9613497b3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -69,6 +69,9 @@ add_executable(vote_test vote_test.cpp) target_link_libraries(vote_test test_util) add_test(vote_test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/vote_test) +add_executable(rewards_stats_test rewards_stats_test.cpp) +target_link_libraries(rewards_stats_test test_util) +add_test(rewards_stats_test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/rewards_stats_test) add_executable(tarcap_threadpool_test tarcap_threadpool_test.cpp) target_link_libraries(tarcap_threadpool_test test_util) diff --git a/tests/rewards_stats_test.cpp b/tests/rewards_stats_test.cpp new file mode 100644 index 0000000000..cce156c735 --- /dev/null +++ b/tests/rewards_stats_test.cpp @@ -0,0 +1,185 @@ +#include "rewards/rewards_stats.hpp" + +#include +#include +#include +#include + +#include "test_util/gtest.hpp" +#include "test_util/samples.hpp" + +namespace taraxa::core_tests { + +auto g_secret = dev::Secret("3800b2875669d9b2053c1aff9224ecfdc411423aac5b5a73d7a45ced1c3b9dcd", + dev::Secret::ConstructFromStringType::FromHex); +auto g_key_pair = Lazy([] { return dev::KeyPair(g_secret); }); + +struct RewardsStatsTest : NodesTest {}; + +class TestableRewardsStats : public rewards::Stats { + public: + TestableRewardsStats(const Hardforks::RewardsDistributionMap& rdm, std::shared_ptr db) + : rewards::Stats(100, rdm, db, [](auto) { return 100; }) {} + std::vector getStats() { return blocks_stats_; } +}; + +class TestableBlockStats : public rewards::BlockStats { + public: + const addr_t& getAuthor() const { return block_author_; } +}; + +TEST_F(RewardsStatsTest, defaultDistribution) { + auto db = std::make_shared(data_dir / "db"); + + std::vector> empty_votes; + auto rewards_stats = TestableRewardsStats({}, db); + + for (auto i = 1; i < 5; ++i) { + PeriodData block(make_simple_pbft_block(blk_hash_t(i), i), empty_votes); + auto stats = rewards_stats.processStats(block); + ASSERT_EQ(stats.size(), 1); + ASSERT_TRUE(rewards_stats.getStats().empty()); + } +} + +TEST_F(RewardsStatsTest, statsSaving) { + auto db = std::make_shared(data_dir / "db"); + + // distribute every 5 blocks + Hardforks::RewardsDistributionMap distribution{{0, 5}}; + + std::vector> empty_votes; + std::vector block_authors; + { + auto rewards_stats = TestableRewardsStats(distribution, db); + + for (auto i = 1; i < 5; ++i) { + auto kp = dev::KeyPair::create(); + block_authors.push_back(kp.address()); + + PeriodData block(make_simple_pbft_block(blk_hash_t(i), i, kp.secret()), empty_votes); + auto stats = rewards_stats.processStats(block); + ASSERT_EQ(rewards_stats.getStats().size(), block_authors.size()); + ASSERT_TRUE(stats.empty()); + } + } + { + // Load from db + auto rewards_stats = TestableRewardsStats(distribution, db); + auto stats = rewards_stats.getStats(); + ASSERT_EQ(rewards_stats.getStats().size(), block_authors.size()); + + for (size_t i = 0; i < stats.size(); ++i) { + auto stats_with_get = reinterpret_cast(&stats[i]); + ASSERT_EQ(stats_with_get->getAuthor(), block_authors[i]); + } + } +} + +TEST_F(RewardsStatsTest, statsCleaning) { + auto db = std::make_shared(data_dir / "db"); + + // distribute every 5 blocks + Hardforks::RewardsDistributionMap distribution{{0, 5}}; + + std::vector> empty_votes; + std::vector block_authors; + { + auto rewards_stats = TestableRewardsStats(distribution, db); + + for (auto i = 1; i < 5; ++i) { + auto kp = dev::KeyPair::create(); + block_authors.push_back(kp.address()); + + PeriodData block(make_simple_pbft_block(blk_hash_t(i), i, kp.secret()), empty_votes); + auto stats = rewards_stats.processStats(block); + ASSERT_EQ(rewards_stats.getStats().size(), block_authors.size()); + ASSERT_TRUE(stats.empty()); + } + + // Process block 5 after which we should have no stats elements in db + PeriodData block(make_simple_pbft_block(blk_hash_t(5), 5), empty_votes); + rewards_stats.processStats(block); + } + + // Load from db + auto rewards_stats = TestableRewardsStats(distribution, db); + ASSERT_TRUE(rewards_stats.getStats().empty()); +} + +TEST_F(RewardsStatsTest, statsProcessing) { + auto db = std::make_shared(data_dir / "db"); + // distribute every 10 blocks + auto rewards_stats = TestableRewardsStats({{0, 10}}, db); + + std::vector> empty_votes; + std::vector block_authors; + + // make blocks [1,9] and process them. output of processStats should be empty + for (auto i = 1; i < 10; ++i) { + auto kp = dev::KeyPair::create(); + block_authors.push_back(kp.address()); + + PeriodData block(make_simple_pbft_block(blk_hash_t(i), i, kp.secret()), empty_votes); + auto stats = rewards_stats.processStats(block); + ASSERT_TRUE(stats.empty()); + ASSERT_EQ(rewards_stats.getStats().size(), block_authors.size()); + } + + auto kp = dev::KeyPair::create(); + block_authors.push_back(kp.address()); + + PeriodData block(make_simple_pbft_block(blk_hash_t(10), 10, kp.secret()), empty_votes); + auto stats = rewards_stats.processStats(block); + ASSERT_EQ(stats.size(), block_authors.size()); + + for (size_t i = 0; i < stats.size(); ++i) { + auto stats_with_get = reinterpret_cast(&stats[i]); + ASSERT_EQ(stats_with_get->getAuthor(), block_authors[i]); + } + ASSERT_TRUE(rewards_stats.getStats().empty()); +} + +TEST_F(RewardsStatsTest, distributionChange) { + auto db = std::make_shared(data_dir / "db"); + + Hardforks::RewardsDistributionMap distribution{{6, 5}, {11, 2}}; + + auto rewards_stats = TestableRewardsStats(distribution, db); + + std::vector> empty_votes; + uint64_t period = 1; + for (; period <= 5; ++period) { + PeriodData block(make_simple_pbft_block(blk_hash_t(period), period), empty_votes); + auto stats = rewards_stats.processStats(block); + ASSERT_FALSE(stats.empty()); + } + { + // make blocks [1,9] and process them. output of processStats should be empty + for (; period < 10; ++period) { + PeriodData block(make_simple_pbft_block(blk_hash_t(period), period), empty_votes); + auto stats = rewards_stats.processStats(block); + ASSERT_TRUE(stats.empty()); + } + PeriodData block(make_simple_pbft_block(blk_hash_t(period), period), empty_votes); + auto stats = rewards_stats.processStats(block); + } + + PeriodData block(make_simple_pbft_block(blk_hash_t(period), period), empty_votes); + auto stats = rewards_stats.processStats(block); +} + +} // namespace taraxa::core_tests + +using namespace taraxa; +int main(int argc, char** argv) { + taraxa::static_init(); + + auto logging = logger::createDefaultLoggingConfig(); + logging.verbosity = logger::Verbosity::Error; + addr_t node_addr; + logging.InitLogging(node_addr); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/tests/test_util/include/test_util/test_util.hpp b/tests/test_util/include/test_util/test_util.hpp index cdbd8659dc..951a22dec3 100644 --- a/tests/test_util/include/test_util/test_util.hpp +++ b/tests/test_util/include/test_util/test_util.hpp @@ -169,7 +169,7 @@ state_api::BalanceMap effective_initial_balances(const state_api::Config& cfg); u256 own_effective_genesis_bal(const FullNodeConfig& cfg); std::shared_ptr make_simple_pbft_block(const h256& hash, uint64_t period, - const h256& anchor_hash = kNullBlockHash); + const secret_t& pk = secret_t::random()); std::vector getOrderedDagBlocks(const std::shared_ptr& db); diff --git a/tests/test_util/src/test_util.cpp b/tests/test_util/src/test_util.cpp index 6599c4ea84..7c045c3df4 100644 --- a/tests/test_util/src/test_util.cpp +++ b/tests/test_util/src/test_util.cpp @@ -96,10 +96,10 @@ u256 own_effective_genesis_bal(const FullNodeConfig& cfg) { return effective_initial_balances(cfg.genesis.state)[dev::toAddress(dev::Secret(cfg.node_secret))]; } -std::shared_ptr make_simple_pbft_block(const h256& hash, uint64_t period, const h256& anchor_hash) { +std::shared_ptr make_simple_pbft_block(const h256& hash, uint64_t period, const secret_t& pk) { std::vector reward_votes_hashes; - return std::make_shared(hash, anchor_hash, kNullBlockHash, kNullBlockHash, period, addr_t(0), - secret_t::random(), std::move(reward_votes_hashes)); + return std::make_shared(hash, kNullBlockHash, kNullBlockHash, kNullBlockHash, period, addr_t(0), pk, + std::move(reward_votes_hashes)); } std::vector getOrderedDagBlocks(const std::shared_ptr& db) {