github.com/igggame/nebulas-go@v2.1.0+incompatible/nbre/runtime/nr/impl/nebulas_rank.cpp (about)

     1  // Copyright (C) 2018 go-nebulas authors
     2  //
     3  // This file is part of the go-nebulas library.
     4  //
     5  // the go-nebulas library is free software: you can redistribute it and/or
     6  // modify
     7  // it under the terms of the GNU General Public License as published by
     8  // the Free Software Foundation, either version 3 of the License, or
     9  // (at your option) any later version.
    10  //
    11  // the go-nebulas library is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU General Public License
    17  // along with the go-nebulas library.  If not, see
    18  // <http://www.gnu.org/licenses/>.
    19  //
    20  
    21  #include "runtime/nr/impl/nebulas_rank.h"
    22  #include "common/int128_conversion.h"
    23  #include "common/log.h"
    24  #include "common/version.h"
    25  #include <boost/algorithm/string/replace.hpp>
    26  #include <boost/foreach.hpp>
    27  #include <boost/property_tree/json_parser.hpp>
    28  #include <chrono>
    29  #include <thread>
    30  
    31  namespace neb {
    32  namespace rt {
    33  
    34  namespace nr {
    35  
    36  std::unique_ptr<std::vector<std::vector<neb::fs::transaction_info_t>>>
    37  nebulas_rank::split_transactions_by_block_interval(
    38      const std::vector<neb::fs::transaction_info_t> &txs,
    39      int32_t block_interval) {
    40  
    41    auto ret =
    42        std::make_unique<std::vector<std::vector<neb::fs::transaction_info_t>>>();
    43  
    44    if (block_interval < 1 || txs.empty()) {
    45      return ret;
    46    }
    47  
    48    auto it = txs.begin();
    49    block_height_t block_first = it->m_height;
    50    it = txs.end();
    51    it--;
    52    block_height_t block_last = it->m_height;
    53  
    54    std::vector<neb::fs::transaction_info_t> v;
    55    it = txs.begin();
    56    block_height_t b = block_first;
    57    while (b <= block_last) {
    58      block_height_t h = it->m_height;
    59      if (h < b + block_interval) {
    60        v.push_back(*it++);
    61      } else {
    62        ret->push_back(v);
    63        v.clear();
    64        b += block_interval;
    65      }
    66      if (it == txs.end()) {
    67        ret->push_back(v);
    68        break;
    69      }
    70    }
    71    return ret;
    72  }
    73  
    74  void nebulas_rank::filter_empty_transactions_this_interval(
    75      std::vector<std::vector<neb::fs::transaction_info_t>> &txs) {
    76    for (auto it = txs.begin(); it != txs.end();) {
    77      if (it->empty()) {
    78        it = txs.erase(it);
    79      } else {
    80        it++;
    81      }
    82    }
    83  }
    84  
    85  transaction_graph_ptr_t nebulas_rank::build_graph_from_transactions(
    86      const std::vector<neb::fs::transaction_info_t> &trans) {
    87    auto ret = std::make_unique<neb::rt::transaction_graph>();
    88  
    89    for (auto ite = trans.begin(); ite != trans.end(); ite++) {
    90      address_t from = ite->m_from;
    91      address_t to = ite->m_to;
    92      wei_t value = ite->m_tx_value;
    93      int64_t timestamp = ite->m_timestamp;
    94      ret->add_edge(from, to, value, timestamp);
    95    }
    96    return ret;
    97  }
    98  
    99  std::unique_ptr<std::vector<transaction_graph_ptr_t>>
   100  nebulas_rank::build_transaction_graphs(
   101      const std::vector<std::vector<neb::fs::transaction_info_t>> &txs) {
   102  
   103    std::unique_ptr<std::vector<transaction_graph_ptr_t>> tgs =
   104        std::make_unique<std::vector<transaction_graph_ptr_t>>();
   105  
   106    for (auto it = txs.begin(); it != txs.end(); it++) {
   107      auto p = build_graph_from_transactions(*it);
   108      tgs->push_back(std::move(p));
   109    }
   110    return tgs;
   111  }
   112  
   113  block_height_t nebulas_rank::get_max_height_this_block_interval(
   114      const std::vector<neb::fs::transaction_info_t> &txs) {
   115    if (txs.empty()) {
   116      return 0;
   117    }
   118    // suppose transactions in height increasing order
   119    return txs.back().m_height;
   120  }
   121  
   122  std::unique_ptr<std::unordered_set<address_t>>
   123  nebulas_rank::get_normal_accounts(
   124      const std::vector<neb::fs::transaction_info_t> &txs) {
   125  
   126    auto ret = std::make_unique<std::unordered_set<address_t>>();
   127  
   128    for (auto it = txs.begin(); it != txs.end(); it++) {
   129      auto from = it->m_from;
   130      ret->insert(from);
   131  
   132      auto to = it->m_to;
   133      ret->insert(to);
   134    }
   135    return ret;
   136  }
   137  
   138  std::unique_ptr<std::unordered_map<address_t, floatxx_t>>
   139  nebulas_rank::get_account_balance_median(
   140      const std::unordered_set<address_t> &accounts,
   141      const std::vector<std::vector<neb::fs::transaction_info_t>> &txs,
   142      const account_db_ptr_t &db_ptr) {
   143  
   144    auto ret = std::make_unique<std::unordered_map<address_t, floatxx_t>>();
   145    std::unordered_map<address_t, std::vector<wei_t>> addr_balance_v;
   146  
   147    for (auto it = txs.begin(); it != txs.end(); it++) {
   148      block_height_t max_height = get_max_height_this_block_interval(*it);
   149      for (auto ite = accounts.begin(); ite != accounts.end(); ite++) {
   150        address_t addr = *ite;
   151        wei_t balance = db_ptr->get_account_balance_internal(addr, max_height);
   152        addr_balance_v[addr].push_back(balance);
   153      }
   154    }
   155  
   156    floatxx_t zero = softfloat_cast<uint32_t, typename floatxx_t::value_type>(0);
   157    for (auto it = addr_balance_v.begin(); it != addr_balance_v.end(); it++) {
   158      std::vector<wei_t> &v = it->second;
   159      sort(v.begin(), v.end(),
   160           [](const wei_t &w1, const wei_t &w2) { return w1 < w2; });
   161      size_t v_len = v.size();
   162      floatxx_t median = to_float<floatxx_t>(v[v_len >> 1]);
   163      if ((v_len & 0x1) == 0) {
   164        auto tmp = to_float<floatxx_t>(v[(v_len >> 1) - 1]);
   165        median = (median + tmp) / 2;
   166      }
   167  
   168      floatxx_t normalized_median = db_ptr->get_normalized_value(median);
   169      ret->insert(std::make_pair(it->first, math::max(zero, normalized_median)));
   170    }
   171  
   172    return ret;
   173  }
   174  
   175  floatxx_t nebulas_rank::f_account_weight(floatxx_t in_val, floatxx_t out_val) {
   176    floatxx_t pi = math::constants<floatxx_t>::pi();
   177    floatxx_t atan_val = pi / 2.0;
   178    if (in_val > 0) {
   179      atan_val = math::arctan(out_val / in_val);
   180    }
   181    auto tmp = math::sin(pi / 4.0 - atan_val);
   182    return (in_val + out_val) * math::exp((-2.0) * tmp * tmp);
   183  }
   184  
   185  std::unique_ptr<std::unordered_map<address_t, floatxx_t>>
   186  nebulas_rank::get_account_weight(
   187      const std::unordered_map<address_t, neb::rt::in_out_val_t> &in_out_vals,
   188      const account_db_ptr_t &db_ptr) {
   189  
   190    auto ret = std::make_unique<std::unordered_map<address_t, floatxx_t>>();
   191  
   192    for (auto it = in_out_vals.begin(); it != in_out_vals.end(); it++) {
   193      wei_t in_val = it->second.m_in_val;
   194      wei_t out_val = it->second.m_out_val;
   195  
   196      floatxx_t f_in_val = to_float<floatxx_t>(in_val);
   197      floatxx_t f_out_val = to_float<floatxx_t>(out_val);
   198  
   199      floatxx_t normalized_in_val = db_ptr->get_normalized_value(f_in_val);
   200      floatxx_t normalized_out_val = db_ptr->get_normalized_value(f_out_val);
   201  
   202      auto tmp = f_account_weight(normalized_in_val, normalized_out_val);
   203      ret->insert(std::make_pair(it->first, tmp));
   204    }
   205    return ret;
   206  }
   207  
   208  floatxx_t nebulas_rank::f_account_rank(int64_t a, int64_t b, int64_t c,
   209                                         int64_t d, floatxx_t theta, floatxx_t mu,
   210                                         floatxx_t lambda, floatxx_t S,
   211                                         floatxx_t R) {
   212    floatxx_t zero = softfloat_cast<uint32_t, typename floatxx_t::value_type>(0);
   213    floatxx_t one = softfloat_cast<uint32_t, typename floatxx_t::value_type>(1);
   214    auto gamma = math::pow(theta * R / (R + mu), lambda);
   215    auto ret = zero;
   216    if (S > 0) {
   217      ret = (S / (one + math::pow(a / S, one / b))) * gamma;
   218    }
   219    return ret;
   220  }
   221  
   222  std::unique_ptr<std::unordered_map<address_t, floatxx_t>>
   223  nebulas_rank::get_account_rank(
   224      const std::unordered_map<address_t, floatxx_t> &account_median,
   225      const std::unordered_map<address_t, floatxx_t> &account_weight,
   226      const rank_params_t &rp) {
   227  
   228    auto ret = std::make_unique<std::unordered_map<address_t, floatxx_t>>();
   229  
   230    for (auto it_m = account_median.begin(); it_m != account_median.end();
   231         it_m++) {
   232      auto it_w = account_weight.find(it_m->first);
   233      if (it_w != account_weight.end()) {
   234        floatxx_t rank_val =
   235            f_account_rank(rp.m_a, rp.m_b, rp.m_c, rp.m_d, rp.m_theta, rp.m_mu,
   236                           rp.m_lambda, it_m->second, it_w->second);
   237        ret->insert(std::make_pair(it_m->first, rank_val));
   238      }
   239    }
   240  
   241    return ret;
   242  }
   243  
   244  std::vector<std::shared_ptr<nr_info_t>> nebulas_rank::get_nr_score(
   245      const transaction_db_ptr_t &tdb_ptr, const account_db_ptr_t &adb_ptr,
   246      const rank_params_t &rp, neb::block_height_t start_block,
   247      neb::block_height_t end_block) {
   248  
   249    auto start_time = std::chrono::high_resolution_clock::now();
   250    // transactions in total and account inter transactions
   251    auto txs_ptr =
   252        tdb_ptr->read_transactions_from_db_with_duration(start_block, end_block);
   253    LOG(INFO) << "raw tx size: " << txs_ptr->size();
   254    auto inter_txs_ptr = fs::transaction_db::read_transactions_with_address_type(
   255        *txs_ptr, NAS_ADDRESS_ACCOUNT_MAGIC_NUM, NAS_ADDRESS_ACCOUNT_MAGIC_NUM);
   256    LOG(INFO) << "account to account: " << inter_txs_ptr->size();
   257  
   258    // graph operation
   259    auto txs_v_ptr = split_transactions_by_block_interval(*inter_txs_ptr);
   260    LOG(INFO) << "split by block interval: " << txs_v_ptr->size();
   261  
   262    filter_empty_transactions_this_interval(*txs_v_ptr);
   263    auto tgs_ptr = build_transaction_graphs(*txs_v_ptr);
   264    if (tgs_ptr->empty()) {
   265      return std::vector<std::shared_ptr<nr_info_t>>();
   266    }
   267    LOG(INFO) << "we have " << tgs_ptr->size() << " subgraphs.";
   268    for (auto it = tgs_ptr->begin(); it != tgs_ptr->end(); it++) {
   269      transaction_graph *ptr = it->get();
   270      graph_algo::non_recursive_remove_cycles_based_on_time_sequence(
   271          ptr->internal_graph());
   272      graph_algo::merge_edges_with_same_from_and_same_to(ptr->internal_graph());
   273    }
   274    LOG(INFO) << "done with remove cycle.";
   275  
   276    transaction_graph *tg = neb::rt::graph_algo::merge_graphs(*tgs_ptr);
   277    graph_algo::merge_topk_edges_with_same_from_and_same_to(tg->internal_graph());
   278    LOG(INFO) << "done with merge graphs.";
   279  
   280    // in_out amount
   281    auto in_out_vals_p = graph_algo::get_in_out_vals(tg->internal_graph());
   282    auto in_out_vals = *in_out_vals_p;
   283    LOG(INFO) << "done with get in_out_vals";
   284  
   285    // median, weight, rank
   286    auto accounts_ptr = get_normal_accounts(*inter_txs_ptr);
   287    LOG(INFO) << "account size: " << accounts_ptr->size();
   288  
   289    std::unordered_map<neb::address_t, neb::wei_t> addr_balance;
   290    for (auto &acc : *accounts_ptr) {
   291      auto balance = adb_ptr->get_balance(acc, start_block);
   292      addr_balance.insert(std::make_pair(acc, balance));
   293    }
   294    LOG(INFO) << "done with get balance";
   295    adb_ptr->set_height_address_val_internal(*txs_ptr, addr_balance);
   296    LOG(INFO) << "done with set height address";
   297  
   298    auto account_median_ptr =
   299        get_account_balance_median(*accounts_ptr, *txs_v_ptr, adb_ptr);
   300    LOG(INFO) << "done with get account balance median";
   301    auto account_weight_ptr = get_account_weight(in_out_vals, adb_ptr);
   302    LOG(INFO) << "done with get account weight";
   303  
   304    auto account_median = *account_median_ptr;
   305    auto account_weight = *account_weight_ptr;
   306    auto account_rank_ptr = get_account_rank(account_median, account_weight, rp);
   307    auto account_rank = *account_rank_ptr;
   308    LOG(INFO) << "account rank size: " << account_rank.size();
   309  
   310    std::vector<std::shared_ptr<nr_info_t>> infos;
   311    for (auto it = accounts_ptr->begin(); it != accounts_ptr->end(); it++) {
   312      address_t addr = *it;
   313      if (in_out_vals.find(addr) != in_out_vals.end() &&
   314          account_median.find(addr) != account_median.end() &&
   315          account_weight.find(addr) != account_weight.end() &&
   316          account_rank.find(addr) != account_rank.end()) {
   317        auto in_outs = in_out_vals[addr].m_in_val + in_out_vals[addr].m_out_val;
   318        auto f_in_outs = to_float<floatxx_t>(in_outs);
   319  
   320        auto info = std::shared_ptr<nr_info_t>(
   321            new nr_info_t({addr, f_in_outs, account_median[addr],
   322                           account_weight[addr], account_rank[addr]}));
   323        infos.push_back(info);
   324      }
   325    }
   326  
   327    auto end_time = std::chrono::high_resolution_clock::now();
   328    LOG(INFO) << "time spend: "
   329              << std::chrono::duration_cast<std::chrono::seconds>(end_time -
   330                                                                  start_time)
   331                     .count()
   332              << " seconds";
   333    return infos;
   334  }
   335  
   336  void nebulas_rank::convert_nr_info_to_ptree(const nr_info_t &info,
   337                                              boost::property_tree::ptree &p) {
   338  
   339    neb::bytes addr_bytes = info.m_address;
   340    floatxx_t f_in_outs = info.m_in_outs;
   341    floatxx_t f_median = info.m_median;
   342    floatxx_t f_weight = info.m_weight;
   343    floatxx_t f_nr_score = info.m_nr_score;
   344  
   345    p.put(std::string("address"), addr_bytes.to_base58());
   346    p.put(std::string("in_outs"), neb::math::to_string(f_in_outs));
   347    p.put(std::string("median"), neb::math::to_string(f_median));
   348    p.put(std::string("weight"), neb::math::to_string(f_weight));
   349    p.put(std::string("score"), neb::math::to_string(f_nr_score));
   350  }
   351  
   352  void nebulas_rank::full_fill_meta_info(
   353      const std::vector<std::pair<std::string, std::string>> &meta,
   354      boost::property_tree::ptree &root) {
   355  
   356    assert(meta.size() == 3);
   357  
   358    for (auto &ele : meta) {
   359      root.put(ele.first, ele.second);
   360    }
   361  }
   362  
   363  str_uptr_t nebulas_rank::nr_info_to_json(const nr_ret_type &nr_ret) {
   364  
   365    boost::property_tree::ptree root;
   366    boost::property_tree::ptree arr;
   367  
   368    const auto &meta_info_json = std::get<1>(nr_ret);
   369    const auto &meta_info = neb::rt::json_to_meta_info(meta_info_json);
   370    if (!meta_info.empty()) {
   371      full_fill_meta_info(meta_info, root);
   372    }
   373  
   374    auto &nr_infos = std::get<2>(nr_ret);
   375    if (nr_infos.empty()) {
   376      boost::property_tree::ptree p;
   377      arr.push_back(std::make_pair(std::string(), p));
   378    }
   379  
   380    for (auto &it : nr_infos) {
   381      const neb::rt::nr::nr_info_t &info = *it;
   382      boost::property_tree::ptree p;
   383      convert_nr_info_to_ptree(info, p);
   384      arr.push_back(std::make_pair(std::string(), p));
   385    }
   386    root.add_child("nrs", arr);
   387  
   388    std::stringstream ss;
   389    boost::property_tree::json_parser::write_json(ss, root, false);
   390    auto tmp_ptr = std::make_unique<std::string>(ss.str());
   391    boost::replace_all(*tmp_ptr, "[\"\"]", "[]");
   392    return tmp_ptr;
   393  }
   394  
   395  nr_ret_type nebulas_rank::json_to_nr_info(const std::string &nr_result) {
   396  
   397    nr_ret_type nr_ret;
   398    std::get<0>(nr_ret) = 0;
   399  
   400    boost::property_tree::ptree pt;
   401    std::stringstream ss(nr_result);
   402    boost::property_tree::json_parser::read_json(ss, pt);
   403  
   404    auto start_block = pt.get<std::string>("start_height");
   405    auto end_block = pt.get<std::string>("end_height");
   406    auto version = pt.get<std::string>("version");
   407  
   408    std::vector<std::pair<std::string, std::string>> meta_info;
   409    meta_info.push_back(std::make_pair("start_height", start_block));
   410    meta_info.push_back(std::make_pair("end_height", end_block));
   411    meta_info.push_back(std::make_pair("version", version));
   412    std::get<1>(nr_ret) = meta_info_to_json(meta_info);
   413  
   414    boost::property_tree::ptree nrs = pt.get_child("nrs");
   415    auto &infos = std::get<2>(nr_ret);
   416  
   417    BOOST_FOREACH (boost::property_tree::ptree::value_type &v, nrs) {
   418      boost::property_tree::ptree nr = v.second;
   419      auto info_ptr = std::make_shared<nr_info_t>();
   420      nr_info_t &info = *info_ptr;
   421      const auto &address = nr.get<base58_address_t>("address");
   422      info.m_address = neb::bytes::from_base58(address);
   423  
   424      const auto &in_outs = nr.get<std::string>("in_outs");
   425      const auto &median = nr.get<std::string>("median");
   426      const auto &weight = nr.get<std::string>("weight");
   427      const auto &score = nr.get<std::string>("score");
   428      info.m_in_outs = neb::math::from_string<floatxx_t>(in_outs);
   429      info.m_median = neb::math::from_string<floatxx_t>(median);
   430      info.m_weight = neb::math::from_string<floatxx_t>(weight);
   431      info.m_nr_score = neb::math::from_string<floatxx_t>(score);
   432      infos.push_back(info_ptr);
   433    }
   434    std::get<0>(nr_ret) = 1;
   435  
   436    return nr_ret;
   437  }
   438  
   439  str_uptr_t nebulas_rank::get_nr_sum_str(const nr_ret_type &nr_ret) {
   440  
   441    boost::property_tree::ptree root;
   442  
   443    const auto &meta_info_json = std::get<1>(nr_ret);
   444    const auto &meta_info = neb::rt::json_to_meta_info(meta_info_json);
   445    if (!meta_info.empty()) {
   446      full_fill_meta_info(meta_info, root);
   447    }
   448  
   449    auto &nr_infos = std::get<2>(nr_ret);
   450    floatxx_t zero = softfloat_cast<uint32_t, typename floatxx_t::value_type>(0);
   451    floatxx_t sum_in_outs = zero;
   452    floatxx_t sum_score = zero;
   453  
   454    for (auto &info : nr_infos) {
   455      sum_in_outs += info->m_in_outs;
   456      sum_score += info->m_nr_score;
   457    }
   458  
   459    boost::property_tree::ptree pt;
   460    auto str_in_outs = neb::math::to_string(from_float(sum_in_outs));
   461    auto str_score = neb::math::to_string(from_float(sum_score));
   462  
   463    pt.put(std::string("in_outs"), str_in_outs);
   464    pt.put(std::string("score"), str_score);
   465    root.add_child("sum", pt);
   466  
   467    std::stringstream ss;
   468    boost::property_tree::json_parser::write_json(ss, root, false);
   469    auto tmp_ptr = std::make_unique<std::string>(ss.str());
   470    return tmp_ptr;
   471  }
   472  
   473  } // namespace nr
   474  } // namespace rt
   475  } // namespace neb