Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use adjusted difficulty for trees in active transactions #1810

Merged
merged 11 commits into from
Mar 21, 2019
Merged
96 changes: 96 additions & 0 deletions nano/core_test/conflicts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,99 @@ TEST (conflicts, reprioritize)
ASSERT_EQ (difficulty2, existing2->difficulty);
}
}

TEST (conflicts, dependency)
{
nano::system system (24000, 1);
auto & node1 (*system.nodes[0]);
nano::genesis genesis;
nano::keypair key1;
auto send1 (std::make_shared<nano::send_block> (genesis.hash (), key1.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0));
node1.work_generate_blocking (*send1);
ASSERT_EQ (nano::process_result::progress, node1.process (*send1).code);
ASSERT_EQ (0, node1.active.size ());
node1.active.start (genesis.open);
node1.active.start (send1);
ASSERT_EQ (2, node1.active.size ());
// Check dependecy for genesis block
{
std::lock_guard<std::mutex> guard (node1.active.mutex);
auto existing1 (node1.active.roots.find (nano::uint512_union (genesis.open->previous (), genesis.open->root ())));
ASSERT_NE (node1.active.roots.end (), existing1);
auto election1 (existing1->election);
ASSERT_NE (nullptr, election1);
ASSERT_EQ (1, election1->dependent_blocks.size ());
ASSERT_NE (election1->dependent_blocks.end (), election1->dependent_blocks.find (send1->hash ()));
}
}

TEST (conflicts, adjusted_difficulty)
{
nano::system system (24000, 1);
auto & node1 (*system.nodes[0]);
nano::genesis genesis;
nano::keypair key1;
nano::keypair key2;
nano::keypair key3;
ASSERT_EQ (0, node1.active.size ());
node1.active.start (genesis.open);
auto send1 (std::make_shared<nano::send_block> (genesis.hash (), key1.pub, nano::genesis_amount - 2 * nano::xrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (genesis.hash ())));
node1.process_active (send1);
auto send2 (std::make_shared<nano::send_block> (send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3 * nano::xrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1->hash ())));
node1.process_active (send2);
auto receive1 (std::make_shared<nano::receive_block> (send2->hash (), send2->hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send2->hash ())));
node1.process_active (receive1);
auto open1 (std::make_shared<nano::open_block> (send1->hash (), key1.pub, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)));
node1.process_active (open1);
auto send3 (std::make_shared<nano::state_block> (key1.pub, open1->hash (), key1.pub, nano::xrb_ratio, key2.pub, key1.prv, key1.pub, system.work.generate (open1->hash ())));
node1.process_active (send3);
auto send4 (std::make_shared<nano::state_block> (key1.pub, send3->hash (), key1.pub, 0, key3.pub, key1.prv, key1.pub, system.work.generate (send3->hash ())));
node1.process_active (send4);
ASSERT_EQ (node1.ledger.epoch_signer, nano::test_genesis_key.pub);
auto open_epoch1 (std::make_shared<nano::state_block> (key2.pub, 0, 0, 0, node1.ledger.epoch_link, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (key2.pub)));
node1.process_active (open_epoch1);
auto receive2 (std::make_shared<nano::state_block> (key2.pub, open_epoch1->hash (), 0, nano::xrb_ratio, send3->hash (), key2.prv, key2.pub, system.work.generate (open_epoch1->hash ())));
node1.process_active (receive2);
auto open2 (std::make_shared<nano::state_block> (key3.pub, 0, key3.pub, nano::xrb_ratio, send4->hash (), key3.prv, key3.pub, system.work.generate (key3.pub)));
node1.process_active (open2);
auto change1 (std::make_shared<nano::state_block> (key3.pub, open2->hash (), nano::test_genesis_key.pub, nano::xrb_ratio, 0, key3.prv, key3.pub, system.work.generate (open2->hash ())));
node1.process_active (change1);
node1.block_processor.flush ();
ASSERT_EQ (11, node1.active.size ());
std::unordered_map<nano::block_hash, uint64_t> adjusted_difficulties;
{
std::lock_guard<std::mutex> guard (node1.active.mutex);
ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), genesis.hash ());
for (auto i (node1.active.roots.get<1> ().begin ()), n (node1.active.roots.get<1> ().end ()); i != n; ++i)
{
adjusted_difficulties.insert (std::make_pair (i->election->status.winner->hash (), i->adjusted_difficulty));
}
}
// genesis
ASSERT_GT (adjusted_difficulties.find (genesis.hash ())->second, adjusted_difficulties.find (send1->hash ())->second);
ASSERT_GT (adjusted_difficulties.find (send1->hash ())->second, adjusted_difficulties.find (send2->hash ())->second);
ASSERT_GT (adjusted_difficulties.find (send2->hash ())->second, adjusted_difficulties.find (receive1->hash ())->second);
// key1
ASSERT_GT (adjusted_difficulties.find (send1->hash ())->second, adjusted_difficulties.find (open1->hash ())->second);
ASSERT_GT (adjusted_difficulties.find (open1->hash ())->second, adjusted_difficulties.find (send3->hash ())->second);
ASSERT_GT (adjusted_difficulties.find (send3->hash ())->second, adjusted_difficulties.find (send4->hash ())->second);
//key2
ASSERT_GT (adjusted_difficulties.find (send3->hash ())->second, adjusted_difficulties.find (receive2->hash ())->second);
ASSERT_GT (adjusted_difficulties.find (open_epoch1->hash ())->second, adjusted_difficulties.find (receive2->hash ())->second);
// key3
ASSERT_GT (adjusted_difficulties.find (send4->hash ())->second, adjusted_difficulties.find (open2->hash ())->second);
ASSERT_GT (adjusted_difficulties.find (open2->hash ())->second, adjusted_difficulties.find (change1->hash ())->second);
// Independent elections can have higher difficulty than adjusted tree
nano::keypair key4;
auto open_epoch2 (std::make_shared<nano::state_block> (key4.pub, 0, 0, 0, node1.ledger.epoch_link, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (key4.pub, adjusted_difficulties.find (genesis.hash ())->second)));
uint64_t difficulty;
ASSERT_FALSE (nano::work_validate (*open_epoch2, &difficulty));
ASSERT_GT (difficulty, adjusted_difficulties.find (genesis.hash ())->second);
node1.process_active (open_epoch2);
node1.block_processor.flush ();
ASSERT_EQ (12, node1.active.size ());
{
std::lock_guard<std::mutex> guard (node1.active.mutex);
ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), open_epoch2->hash ());
}
}
135 changes: 131 additions & 4 deletions nano/node/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2478,6 +2478,7 @@ announcements (0)
{
last_votes.insert (std::make_pair (node.network_params.ledger.not_an_account (), nano::vote_info{ std::chrono::steady_clock::now (), 0, block_a->hash () }));
blocks.insert (std::make_pair (block_a->hash (), block_a));
update_dependent ();
}

void nano::election::compute_rep_votes (nano::transaction const & transaction_a)
Expand Down Expand Up @@ -2564,6 +2565,8 @@ void nano::election::confirm_if_quorum (nano::transaction const & transaction_a)
auto node_l (node.shared ());
node_l->block_processor.force (block_l);
status.winner = block_l;
update_dependent ();
node_l->active.adjust_difficulty (block_l->hash ());
}
if (have_quorum (tally_l, sum))
{
Expand Down Expand Up @@ -2727,6 +2730,39 @@ size_t nano::election::last_votes_size ()
return last_votes.size ();
}

void nano::election::update_dependent ()
{
assert (!node.active.mutex.try_lock ());
std::vector<nano::block_hash> blocks_search;
auto hash (status.winner->hash ());
auto previous (status.winner->previous ());
if (!previous.is_zero ())
{
blocks_search.push_back (previous);
}
auto source (status.winner->source ());
if (!source.is_zero () && source != previous)
{
blocks_search.push_back (source);
}
auto link (status.winner->link ());
if (!link.is_zero () && !node.ledger.is_epoch_link (link) && link != previous)
{
blocks_search.push_back (link);
}
for (auto & block_search : blocks_search)
{
auto existing (node.active.blocks.find (block_search));
if (existing != node.active.blocks.end () && !existing->second->confirmed && !existing->second->stopped)
{
if (existing->second->dependent_blocks.find (hash) == existing->second->dependent_blocks.end ())
{
existing->second->dependent_blocks.insert (hash);
}
}
}
}

void nano::active_transactions::request_confirm (std::unique_lock<std::mutex> & lock_a)
{
std::unordered_set<nano::uint512_union> inactive;
Expand Down Expand Up @@ -2795,6 +2831,7 @@ void nano::active_transactions::request_confirm (std::unique_lock<std::mutex> &
}
}
}
election_l->update_dependent ();
}
}
if (election_l->announcements < announcement_long || election_l->announcements % announcement_long == 1)
Expand Down Expand Up @@ -2932,6 +2969,10 @@ void nano::active_transactions::request_confirm (std::unique_lock<std::mutex> &
(void)erased;
assert (erased == 1);
}
for (auto & dependent_block : root_it->election->dependent_blocks)
{
adjust_difficulty (dependent_block);
}
roots.erase (*i);
}
if (unconfirmed_count > 0)
Expand Down Expand Up @@ -2994,8 +3035,9 @@ bool nano::active_transactions::add (std::shared_ptr<nano::block> block_a, std::
uint64_t difficulty (0);
auto error (nano::work_validate (*block_a, &difficulty));
release_assert (!error);
roots.insert (nano::conflict_info{ root, difficulty, election });
roots.insert (nano::conflict_info{ root, difficulty, difficulty, election });
blocks.insert (std::make_pair (block_a->hash (), election));
adjust_difficulty (block_a->hash ());
}
error = existing != roots.end ();
}
Expand Down Expand Up @@ -3061,9 +3103,94 @@ void nano::active_transactions::update_difficulty (nano::block const & block_a)
uint64_t difficulty;
auto error (nano::work_validate (block_a, &difficulty));
assert (!error);
roots.modify (existing, [difficulty](nano::conflict_info & info_a) {
info_a.difficulty = difficulty;
});
if (difficulty > existing->difficulty)
{
roots.modify (existing, [difficulty](nano::conflict_info & info_a) {
info_a.difficulty = difficulty;
});
adjust_difficulty (block_a.hash ());
}
}
}

void nano::active_transactions::adjust_difficulty (nano::block_hash const & hash_a)
{
assert (!mutex.try_lock ());
std::deque<std::pair<nano::block_hash, int64_t>> remaining_blocks;
remaining_blocks.emplace_back (hash_a, 0);
std::unordered_set<nano::block_hash> processed_blocks;
std::vector<std::pair<nano::uint512_union, int64_t>> elections_list;
uint128_t sum (0);
while (!remaining_blocks.empty ())
{
auto const & item (remaining_blocks.front ());
auto hash (item.first);
auto level (item.second);
if (processed_blocks.find (hash) == processed_blocks.end ())
{
auto existing (blocks.find (hash));
if (existing != blocks.end () && !existing->second->confirmed && !existing->second->stopped && existing->second->status.winner->hash () == hash)
{
auto previous (existing->second->status.winner->previous ());
if (!previous.is_zero ())
{
remaining_blocks.emplace_back (previous, level + 1);
}
auto source (existing->second->status.winner->source ());
if (!source.is_zero () && source != previous)
{
remaining_blocks.emplace_back (source, level + 1);
}
auto link (existing->second->status.winner->link ());
if (!link.is_zero () && !node.ledger.is_epoch_link (link) && link != previous)
{
remaining_blocks.emplace_back (link, level + 1);
}
for (auto & dependent_block : existing->second->dependent_blocks)
{
remaining_blocks.emplace_back (dependent_block, level - 1);
}
processed_blocks.insert (hash);
nano::uint512_union root (previous, existing->second->status.winner->root ());
auto existing_root (roots.find (root));
if (existing_root != roots.end ())
{
sum += existing_root->difficulty;
elections_list.emplace_back (root, level);
}
}
}
remaining_blocks.pop_front ();
}
if (elections_list.size () > 1)
{
uint64_t average (static_cast<uint64_t> (sum / elections_list.size ()));
// Potential overflow check
uint64_t divider (1);
if (elections_list.size () > 1000000 && (average - node.network_params.publish_threshold) > elections_list.size ())
{
divider = ((average - node.network_params.publish_threshold) / elections_list.size ()) + 1;
}
// Set adjusted difficulty
for (auto & item : elections_list)
{
auto existing_root (roots.find (item.first));
uint64_t difficulty_a (average + (item.second / divider));
roots.modify (existing_root, [difficulty_a](nano::conflict_info & info_a) {
info_a.adjusted_difficulty = difficulty_a;
});
}
}
// Set adjusted difficulty equals to difficulty
else if (elections_list.size () == 1)
{
auto existing_root (roots.find (elections_list.begin ()->first));
if (existing_root->difficulty != existing_root->adjusted_difficulty)
{
roots.modify (existing_root, [](nano::conflict_info & info_a) {
info_a.adjusted_difficulty = info_a.difficulty;
});
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion nano/node/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class election : public std::enable_shared_from_this<nano::election>
void log_votes (nano::tally_t const &);
bool publish (std::shared_ptr<nano::block> block_a);
size_t last_votes_size ();
void update_dependent ();
void stop ();
nano::node & node;
std::unordered_map<nano::account, nano::vote_info> last_votes;
Expand All @@ -91,12 +92,14 @@ class election : public std::enable_shared_from_this<nano::election>
bool stopped;
std::unordered_map<nano::block_hash, nano::uint128_t> last_tally;
unsigned announcements;
std::unordered_set<nano::block_hash> dependent_blocks;
};
class conflict_info
{
public:
nano::uint512_union root;
uint64_t difficulty;
uint64_t adjusted_difficulty;
std::shared_ptr<nano::election> election;
};
// Core class for determining consensus
Expand All @@ -117,6 +120,7 @@ class active_transactions
// Is the root of this block in the roots container
bool active (nano::block const &);
void update_difficulty (nano::block const &);
void adjust_difficulty (nano::block_hash const &);
std::deque<std::shared_ptr<nano::block>> list_blocks (bool = false);
void erase (nano::block const &);
bool empty ();
Expand All @@ -130,7 +134,7 @@ class active_transactions
boost::multi_index::hashed_unique<
boost::multi_index::member<nano::conflict_info, nano::uint512_union, &nano::conflict_info::root>>,
boost::multi_index::ordered_non_unique<
boost::multi_index::member<nano::conflict_info, uint64_t, &nano::conflict_info::difficulty>,
boost::multi_index::member<nano::conflict_info, uint64_t, &nano::conflict_info::adjusted_difficulty>,
std::greater<uint64_t>>>>
roots;
std::unordered_map<nano::block_hash, std::shared_ptr<nano::election>> blocks;
Expand Down