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

Ledger: Use representative for change blocks and epoch signer for epoch blocks in linked_account #4842

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 172 additions & 1 deletion nano/core_test/websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,177 @@ TEST (websocket, confirmation_options_votes)
}
}

TEST (websocket, confirmation_options_linked_account)
{
nano::test::system system;
nano::node_config config = system.default_config ();
config.websocket_config.enabled = true;
config.websocket_config.port = system.get_available_port ();
auto node1 (system.add_node (config));

std::atomic<bool> ack_ready{ false };
auto task1 = ([&ack_ready, config, &node1] () {
fake_websocket_client client (node1->websocket.server->listening_port ());
client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "include_block": "true", "include_linked_account": "true"}})json");
client.await_ack ();
ack_ready = true;
EXPECT_EQ (1, node1->websocket.server->subscriber_count (nano::websocket::topic::confirmation));
return client.get_response ();
});
auto future1 = std::async (std::launch::async, task1);

ASSERT_TIMELY (10s, ack_ready);

// Confirm a state block for an in-wallet account
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key;
auto balance = nano::dev::constants.genesis_amount;
auto send_amount = node1->config.online_weight_minimum.number () + 1;
nano::block_hash previous (node1->latest (nano::dev::genesis_key.pub));
{
nano::state_block_builder builder;
balance -= send_amount;
auto send = builder
.account (nano::dev::genesis_key.pub)
.previous (previous)
.representative (nano::dev::genesis_key.pub)
.balance (balance)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (previous))
.build ();

node1->process_active (send);
previous = send->hash ();
}

ASSERT_TIMELY_EQ (5s, future1.wait_for (0s), std::future_status::ready);

auto response1 = future1.get ();
ASSERT_TRUE (response1);
boost::property_tree::ptree event;
std::stringstream stream;
stream << response1.get ();
boost::property_tree::read_json (stream, event);
ASSERT_EQ (event.get<std::string> ("topic"), "confirmation");
try
{
boost::property_tree::ptree block_content = event.get_child ("message.block");
// Check if linked_account is present
ASSERT_EQ (1, block_content.count ("linked_account"));
// Make sure linked_account is non-zero.
ASSERT_NE ("0", block_content.get<std::string> ("linked_account"));
}
catch (std::runtime_error const & ex)
{
FAIL () << ex.what ();
}

ack_ready = false;
auto task2 = ([&ack_ready, config, &node1] () {
fake_websocket_client client (node1->websocket.server->listening_port ());
client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "include_block": "true", "include_linked_account": "true"}})json");
client.await_ack ();
ack_ready = true;
EXPECT_EQ (1, node1->websocket.server->subscriber_count (nano::websocket::topic::confirmation));
return client.get_response ();
});
auto future2 = std::async (std::launch::async, task2);

ASSERT_TIMELY (10s, ack_ready);

// Quick-confirm a receive block
{
nano::state_block_builder builder;
balance = send_amount;
auto open = builder
.account (key.pub)
.previous (0)
.representative (nano::dev::genesis_key.pub)
.balance (balance)
.link (previous)
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();

node1->process_active (open);
previous = open->hash ();
}

ASSERT_TIMELY_EQ (5s, future2.wait_for (0s), std::future_status::ready);

auto response2 = future2.get ();
ASSERT_TRUE (response2);
boost::property_tree::ptree event2;
std::stringstream stream2;
stream2 << response2.get ();
boost::property_tree::read_json (stream2, event2);
ASSERT_EQ (event2.get<std::string> ("topic"), "confirmation");
try
{
boost::property_tree::ptree block_content = event2.get_child ("message.block");
// Check if linked_account is present
ASSERT_EQ (1, block_content.count ("linked_account"));
// Make sure linked_account is non-zero.
ASSERT_NE ("0", block_content.get<std::string> ("linked_account"));
}
catch (std::runtime_error const & ex)
{
FAIL () << ex.what ();
}

ack_ready = false;
auto task3 = ([&ack_ready, config, &node1] () {
fake_websocket_client client (node1->websocket.server->listening_port ());
client.send_message (R"json({"action": "subscribe", "topic": "confirmation", "ack": "true", "options": {"confirmation_type": "active_quorum", "include_block": "true", "include_linked_account": "true"}})json");
client.await_ack ();
ack_ready = true;
EXPECT_EQ (1, node1->websocket.server->subscriber_count (nano::websocket::topic::confirmation));
return client.get_response ();
});
auto future3 = std::async (std::launch::async, task3);

ASSERT_TIMELY (10s, ack_ready);

// Quick-confirm a change block
{
nano::state_block_builder builder;
auto change = builder
.account (key.pub)
.previous (previous)
.representative (key.pub)
.balance (balance)
.link (0)
.sign (key.prv, key.pub)
.work (*system.work.generate (previous))
.build ();

node1->process_active (change);
}

ASSERT_TIMELY_EQ (5s, future3.wait_for (0s), std::future_status::ready);

auto response3 = future3.get ();
ASSERT_TRUE (response3);
boost::property_tree::ptree event3;
std::stringstream stream3;
stream3 << response3.get ();
boost::property_tree::read_json (stream3, event3);
ASSERT_EQ (event3.get<std::string> ("topic"), "confirmation");
try
{
boost::property_tree::ptree block_content = event3.get_child ("message.block");
// Check if linked_account is present
ASSERT_EQ (1, block_content.count ("linked_account"));
// Make sure linked_account is zero.
ASSERT_EQ ("0", block_content.get<std::string> ("linked_account"));
}
catch (std::runtime_error const & ex)
{
FAIL () << ex.what ();
}
}

TEST (websocket, confirmation_options_sideband)
{
nano::test::system system;
Expand Down Expand Up @@ -681,7 +852,7 @@ TEST (websocket, vote_options_type)

// Custom made votes for simplicity
auto vote = nano::test::make_vote (nano::dev::genesis_key, { nano::dev::genesis }, 0, 0);
nano::websocket::message_builder builder;
nano::websocket::message_builder builder{ node1->ledger };
auto msg (builder.vote_received (vote, nano::vote_code::replay));
node1->websocket.server->broadcast (msg);

Expand Down
24 changes: 24 additions & 0 deletions nano/lib/blocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,19 @@ nano::account nano::block::destination () const noexcept
}
}

nano::account nano::block::representative () const noexcept
{
switch (type ())
{
case nano::block_type::open:
case nano::block_type::change:
case nano::block_type::state:
return representative_field ().value ();
default:
release_assert (false);
}
}

nano::block_hash nano::block::source () const noexcept
{
release_assert (has_sideband ());
Expand All @@ -309,6 +322,17 @@ nano::block_hash nano::block::source () const noexcept
}
}

nano::link nano::block::link () const noexcept
{
switch (type ())
{
case nano::block_type::state:
return link_field ().value ();
default:
release_assert (false);
}
}

// TODO - Remove comments below and fixup usages to not need to check .is_zero ()
// std::optional<nano::block_hash> nano::block::previous () const
nano::block_hash nano::block::previous () const noexcept
Expand Down
6 changes: 5 additions & 1 deletion nano/lib/blocks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,17 @@ class block
nano::account destination () const noexcept;
// Destination account for send blocks
virtual std::optional<nano::account> destination_field () const;
// Returns the link hash or account for state blocks
nano::link link () const noexcept;
// Link field for state blocks
virtual std::optional<nano::link> link_field () const;
// Previous block if field exists or 0
nano::block_hash previous () const noexcept;
// Previous block in chain if the field exists
virtual std::optional<nano::block_hash> previous_field () const = 0;
// Representative field for open/change blocks
// Returns the representative account for open/change/state blocks
nano::account representative () const noexcept;
// Representative field for open/change/state blocks
virtual std::optional<nano::account> representative_field () const;
// Returns the source block hash for open/receive/state blocks that are receives
nano::block_hash source () const noexcept;
Expand Down
2 changes: 1 addition & 1 deletion nano/node/distributed_work.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ nano::distributed_work::~distributed_work ()
{
if (!node_l->stopped && node_l->websocket.server && node_l->websocket.server->any_subscriber (nano::websocket::topic::work))
{
nano::websocket::message_builder builder;
nano::websocket::message_builder builder{ node_l->ledger };
if (status == work_generation_status::success)
{
node_l->websocket.server->broadcast (builder.work_generation (request.version, request.root.as_block_hash (), work_result, request.difficulty, node_l->default_difficulty (request.version), elapsed.value (), winner, bad_peers));
Expand Down
39 changes: 39 additions & 0 deletions nano/node/json_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,19 @@ void nano::json_handler::block_info ()
{
auto account = block->account ();
response_l.put ("block_account", account.to_account ());
bool include_linked_account = request.get<bool> ("include_linked_account", false);
if (include_linked_account)
{
auto linked_account = node.ledger.linked_account (transaction, *block);
if (linked_account.has_value ())
{
response_l.put ("linked_account", linked_account.value ().to_account ());
}
else
{
response_l.put ("linked_account", "0");
}
}
auto amount = node.ledger.any.block_amount (transaction, hash);
if (amount)
{
Expand Down Expand Up @@ -1273,6 +1286,7 @@ void nano::json_handler::blocks_info ()
bool const receive_hash = request.get<bool> ("receive_hash", false);
bool const source = request.get<bool> ("source", false);
bool const json_block_l = request.get<bool> ("json_block", false);
bool const include_linked_account = request.get<bool> ("include_linked_account", false);
bool const include_not_found = request.get<bool> ("include_not_found", false);

boost::property_tree::ptree blocks;
Expand All @@ -1292,6 +1306,18 @@ void nano::json_handler::blocks_info ()
boost::property_tree::ptree entry;
auto account = block->account ();
entry.put ("block_account", account.to_account ());
if (include_linked_account)
{
auto linked_account = node.ledger.linked_account (transaction, *block);
if (linked_account.has_value ())
{
entry.put ("linked_account", linked_account.value ().to_account ());
}
else
{
entry.put ("linked_account", "0");
}
}
auto amount = node.ledger.any.block_amount (transaction, hash);
if (amount)
{
Expand Down Expand Up @@ -2593,6 +2619,7 @@ void nano::json_handler::account_history ()
if (!ec)
{
boost::property_tree::ptree history;
bool include_linked_account (request.get_optional<bool> ("include_linked_account") == true);
bool output_raw (request.get_optional<bool> ("raw") == true);
response_l.put ("account", account.to_account ());
auto block = node.ledger.any.block_get (transaction, hash);
Expand All @@ -2609,6 +2636,18 @@ void nano::json_handler::account_history ()
block->visit (visitor);
if (!entry.empty ())
{
if (include_linked_account)
{
auto linked_account = node.ledger.linked_account (transaction, *block);
if (linked_account.has_value ())
{
entry.put ("linked_account", linked_account.value ().to_account ());
}
else
{
entry.put ("linked_account", "0");
}
}
entry.put ("local_timestamp", std::to_string (block->sideband ().timestamp));
entry.put ("height", std::to_string (block->sideband ().height));
entry.put ("hash", hash.to_string ());
Expand Down
4 changes: 2 additions & 2 deletions nano/node/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ nano::node::node (std::shared_ptr<boost::asio::io_context> io_ctx_a, std::filesy
bootstrap_server{ *bootstrap_server_impl },
bootstrap_impl{ std::make_unique<nano::bootstrap_service> (config, ledger, ledger_notifications, block_processor, network, stats, logger) },
bootstrap{ *bootstrap_impl },
websocket_impl{ std::make_unique<nano::websocket_server> (config.websocket_config, observers, wallets, ledger, io_ctx, logger) },
websocket_impl{ std::make_unique<nano::websocket_server> (config.websocket_config, *this, observers, wallets, ledger, io_ctx, logger) },
websocket{ *websocket_impl },
epoch_upgrader_impl{ std::make_unique<nano::epoch_upgrader> (*this, ledger, store, network_params, logger) },
epoch_upgrader{ *epoch_upgrader_impl },
Expand Down Expand Up @@ -236,7 +236,7 @@ nano::node::node (std::shared_ptr<boost::asio::io_context> io_ctx_a, std::filesy
{
if (websocket.server && websocket.server->any_subscriber (nano::websocket::topic::new_unconfirmed_block))
{
websocket.server->broadcast (nano::websocket::message_builder ().new_block_arrived (*context.block));
websocket.server->broadcast (nano::websocket::message_builder (ledger).new_block_arrived (*context.block));
}
}
}
Expand Down
Loading
Loading