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

Showing Local Addresses in Node Window #626

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

jadijadi
Copy link
Contributor

@jadijadi jadijadi commented Jun 29, 2022

This change adds a new row to the Node Window (debugwindow.ui)
under the Network section which shows the LocalAddresses.

fixes #564

@jarolrod jarolrod added the UX All about "how to get things done" label Jun 29, 2022
@jadijadi
Copy link
Contributor Author

I've got this from the linter:

he locale dependent function std::to_string(...) appears to be used:
src/qt/rpcconsole.cpp:        localAddresses += item.first.ToString() + ":" + std::to_string(item.second.nPort) + ", ";

Unnecessary locale depedence can cause bugs that are very tricky to isolate and fix. Please avoid using locale dependent functions if possible.

Not sure how I should convert from int to string correctly. Any advice?

Copy link
Member

@jarolrod jarolrod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concept ack

full diff:

diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 01989b5a0..080f9b979 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -961,15 +961,16 @@ void RPCConsole::updateNetworkState()
     ui->numberOfConnections->setText(connections);

     // localAddressess
-    std::string localAddresses="";
+    QString local_addresses;
     LOCK(g_maplocalhost_mutex);
     for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost)
-        localAddresses += item.first.ToString() + ":" + std::to_string(item.second.nPort) + ", ";
-    if (localAddresses.length() > 2)
-        localAddresses[localAddresses.length()-2] = 0;
+        local_addresses += QString::fromStdString(item.first.ToString()) + ":" + QString::number(item.second.nPort) + ", ";
+    if (local_addresses.length() > 2)
+        local_addresses[local_addresses.length()-2] = 0;
     else
-        localAddresses = "None";
-    ui->localAddresses->setText(QString::fromStdString(localAddresses));
+        //: Signals to the user that they do not have any local addresses.
+        local_addresses = tr("None");
+    ui->localAddresses->setText(local_addresses);

 }

src/qt/rpcconsole.cpp Outdated Show resolved Hide resolved
src/qt/rpcconsole.cpp Outdated Show resolved Hide resolved
Copy link
Member

@furszy furszy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from the code comments, there is an important topic to mention:

Let's not access fields from the net layer in a GUI widget/view, the sources architecture is (should be) widget -> model -> interface -> backend.

Better to create the proper flow to access the information that you are looking for:
rpcconsole -> clientModel -> node interface -> net layer.

In this way, the field access is encapsulated, layers responsibilities are respected and the same function can be used, and tested, from other user facing end points like the RPC server equally.

src/qt/forms/debugwindow.ui Outdated Show resolved Hide resolved
src/qt/rpcconsole.cpp Outdated Show resolved Hide resolved
Comment on lines 964 to 971
std::string localAddresses="";
LOCK(g_maplocalhost_mutex);
for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost)
localAddresses += item.first.ToString() + ":" + std::to_string(item.second.nPort) + ", ";
if (localAddresses.length() > 2)
localAddresses[localAddresses.length()-2] = 0;
else
localAddresses = "None";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would suggest to write it differently:

Suggested change
std::string localAddresses="";
LOCK(g_maplocalhost_mutex);
for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost)
localAddresses += item.first.ToString() + ":" + std::to_string(item.second.nPort) + ", ";
if (localAddresses.length() > 2)
localAddresses[localAddresses.length()-2] = 0;
else
localAddresses = "None";
std::string localAddresses = "";
LOCK(g_maplocalhost_mutex);
for (auto it = mapLocalHost.begin(); it != mapLocalHost.end();) {
localAddresses += it->first.ToString() + ":" + std::to_string(it->second.nPort);
if (++it != mapLocalHost.end()) localAddresses += ", ";
}
if (localAddresses.empty()) localAddresses = "None";

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a review on an earlier state of the PR, the following would be the appropriate diff to express your idea:

diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 386b9373c..a249d7895 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -964,13 +964,12 @@ void RPCConsole::updateNetworkState()
     // localAddressess
     QString local_addresses;
     LOCK(g_maplocalhost_mutex);
-    for (const std::pair<const CNetAddr, LocalServiceInfo> &item : mapLocalHost)
-        local_addresses += QString::fromStdString(item.first.ToString()) + ":" + QString::number(item.second.nPort) + ", ";
-    if (local_addresses.length() > 2)
-        local_addresses[local_addresses.length()-2] = 0;
-    else
-        // Signals to the user that they do not have any local addresses.
-        local_addresses = tr("None");
+    for (auto it = mapLocalHost.begin(); it != mapLocalHost.end();) {
+        local_addresses += QString::fromStdString(it->first.ToString()) + ":" + QString::number(it->second.nPort) + ", ";
+        if (++it != mapLocalHost.end()) local_addresses += ", ";
+    }
+    //: Signals to the user that they do not have any local addresses.
+    if (local_addresses.isEmpty()) local_addresses = tr("None");
     ui->localAddresses->setText(local_addresses);
 }

Copy link
Member

@furszy furszy Jul 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually pointed to the previous state on purpose. My main suggestion was to encapsulate the mutex and mapLocalHost access in the backend, which is Qt/GUI independent. So cannot use QString from there. Need to craft a plain std::string, then convert it to QString once it passes through the model class or directly in the widget.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dear @furszy thanks for the valuable input. Applied most of them except this encapsulation part. I understand your rational and I think it's a better design. But since the number of connections are also calculated here, I did the same with Local Address.
Since I'm leaving for a 10 day off the grid trip, I submitted a new commit (squashed) with these chagnes:

  1. removed the extra new line
  2. used your better loop for ", " additions
  3. removed unnecessary comments (kept the one from @jarolrod)

Thanks @jarolrod for the inputs and fixes and reviews.

Copy link
Member

@furszy furszy Jul 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dear @furszy thanks for the valuable input. Applied most of them except this encapsulation part. I understand your rational and I think it's a better design. But since the number of connections are also calculated here, I did the same with Local Address.

The connections aren't calculated here (on the widget, as you are doing for the external addresses). It's encapsulated inside the connections manager. The flow is the following one: widget -> model -> node -> conman.

updateNetworkState calls clientModel->getNumConnections which calls the interface node.getNodeCount, which calls the connection manager.

Let's try to respect the different layers, encapsulating access where is possible, to get cleaner code to work with. (If you need a hand with it, feel very welcome to ping me on IRC)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Did the flow as you said. Now we are querying layer by layer. Please comment if it still needs improvement. I decided to pass the std::map<CNetAddr, LocalServiceInfo> to keep it flexible enough for different use cases; for example in this specific usecase (showing IPs in GUI), i was able to do check for "IsI2P" on CNetAddrs.

src/qt/rpcconsole.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK

  • I agree with showing the Local Address on the Information Tab.
  • I successfully compiled the PR on Ubuntu 22.04 with Qt version 5.15.3.
  • I was able to verify that the Local Address option is displayed correctly on the Information Tab.

Screenshot:

Screenshot from 2022-07-05 17-09-17

Currently, I am figuring out how to add local addresses to check if they are displayed correctly. It would be awesome if someone could point towards documentation on how to do so.

@@ -249,6 +249,29 @@
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_14">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
<widget class="QLabel" name="label_14">
<widget class="QLabel" name="labelLocalAddresses">

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the checks & ACK @shaavan . I kept the label_14 in accordance in most of other fields which had the same pattern.

regarding the listening port, you can test with something like (for testnet):

testnet=1
blocksonly=1
[test]
#prune=1000 # this has no effect during syncing
externalip=100.200.100.10
rpcbind=127.0.0.1
rpcallowip=0.0.0.0/0
rpcuser=user
rpcpassword=pass

as your bitcoin.conf to force a listening IP.

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK c47f01b

Continuing on my previous review:

I was able to add local addresses successfully and tested that those were displayed perfectly on the GUI under the Local Address option.

Method of testing:

I added the following options in the bitcoin.conf file.

[signet]
externalip=100.200.100.11
externalip=100.200.100.10
externalip=100.200.100.12

The IP addresses, along with the port number, appeared in the GUI.

Screenshot:
Screenshot from 2022-07-09 19-36-14

Note: The IP:port appears in the sorted order irrespective of the order provided in the bitcoin.conf file. This behavior is the same as the -netinfo RPC command.

Screenshot:
image

Thanks, @jadijadi, for the help on adding the listening ports!

@hebasto hebasto changed the title gui: Showing Local Addresses in Node Window Showing Local Addresses in Node Window Jul 10, 2022
Copy link
Member

@jarolrod jarolrod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review ACK c47f01b

Some nits

@@ -249,6 +249,29 @@
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Local Address</string>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could add a translator comment here by adding in the extracomment field:

Suggested change
<string>Local Address</string>
<string extracomment="nice translator comment">Local Address</string>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While here, if there's more than one local address; I guess this should take the plural form, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or could be just changed to "Local Address(es)" maybe?

@DrahtBot
Copy link
Contributor

DrahtBot commented Mar 7, 2023

qt/rpcconsole.cpp: In member function ‘void RPCConsole::updateNetworkState()’:
qt/rpcconsole.cpp:965:61: error: ‘const class CNetAddr’ has no member named ‘ToString’; did you mean ‘ToStringAddr’?
         local_addresses += QString::fromStdString(it->first.ToString()) + ":" + QString::number(it->second.nPort);
                                                             ^~~~~~~~
                                                             ToStringAddr

@hebasto
Copy link
Member

hebasto commented Mar 27, 2023

Please rebase due to the conflict with the current master branch (b968424).

@DrahtBot
Copy link
Contributor

DrahtBot commented Mar 27, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK RandyMcMillan, pablomartin4btc, kristapsk
Stale ACK shaavan, jarolrod

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

No conflicts as of last run.

@RandyMcMillan
Copy link
Contributor

Concept ACK

Will test on macos x86/Arm64 today

LOCK(g_maplocalhost_mutex);
for (auto it = mapLocalHost.begin(); it != mapLocalHost.end();) {
local_addresses += QString::fromStdString(it->first.ToString()) + ":" + QString::number(it->second.nPort);
if (++it != mapLocalHost.end()) local_addresses += ", ";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda ugly to do the increment here. Maybe make the loop normal, and check for it != mapLocalHost.begin() at the start of it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to a normal loop and checking for .begin().

Copy link
Contributor

@pablomartin4btc pablomartin4btc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK and tested c47f01b

image

image

Thanks @furszy for emphasizing the importance of a good code design and architectural integrity. These principles are crucial for the long-term maintainability and scalability of our project. I totally agree with his code change recommendation in terms of the flow/ pattern to follow.

In terms of the UI itself, I'd use the plural form "Local Addresses", as @jarolrod also pointed it out, to be consistent with the getnetworkinfo rpc command output.

image

image

@kristapsk
Copy link
Contributor

Concept ACK, kinda works.

Minor nit noticed - could avoid adding ":0" as a port for I2P addresses.

@kristapsk
Copy link
Contributor

kristapsk commented Sep 22, 2023

Another thing - list is currently comma separated, without wrapping. Wouldn't it be too long if somebody has IPv4, IPv6, CJDNS, Tor and I2P addresses at the same time?

@hebasto
Copy link
Member

hebasto commented May 15, 2024

This PR still have a couple of unaddressed comments: #626 (comment) and #626 (comment).

@jadijadi Are you still working on this?

@jadijadi
Copy link
Contributor Author

jadijadi commented May 18, 2024

This PR still have a couple of unaddressed comments: #626 (comment) and #626 (comment).

@jadijadi Are you still working on this?

Sorry for forgetting this... will check and comment soon

@jadijadi
Copy link
Contributor Author

Concept ACK, kinda works.

Minor nit noticed - could avoid adding ":0" as a port for I2P addresses.

thanks for the suggestion, did this.

@jadijadi
Copy link
Contributor Author

Another thing - list is currently comma separated, without wrapping. Wouldn't it be too long if somebody has IPv4, IPv6, CJDNS, Tor and I2P addresses at the same time?

Thanks for this point, it is fixed and the widget worswraps and extends when needed.

@jadijadi
Copy link
Contributor Author

This PR still have a couple of unaddressed comments: #626 (comment) and #626 (comment).

Dear @hebasto , addressed all issues, updated the code & replied to queries. Thanks for the follow up.

Copy link
Member

@furszy furszy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the encapsulation jadijadi.

Comment on lines 171 to 172
//! Get num blocks.
virtual std::map<CNetAddr, LocalServiceInfo> getLocalAddresses() = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong description

Comment on lines 286 to 288
std::map<CNetAddr, LocalServiceInfo> empty;

return m_context->connman ? m_context->connman->getLocalAddresses() : empty;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::map<CNetAddr, LocalServiceInfo> empty;
return m_context->connman ? m_context->connman->getLocalAddresses() : empty;
return m_context->connman ? m_context->connman->getLocalAddresses() : {};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the support and patient. Applying them. The {} was also my first try but I got:
error: initializer list cannot be used on the right hand side of operator ':'
Now I changed it to

        if (m_context->connman)
            return m_context->connman->getLocalAddresses();
        else
            return {};

to prevent that ugly empty variable of mine.

Comment on lines 978 to 985

QString local_addresses;
std::map<CNetAddr, LocalServiceInfo> mapHosts = clientModel->getLocalAddresses();
for (auto it = mapHosts.begin(); it != mapHosts.end(); it++) {
if (it != mapHosts.begin()) local_addresses += ", ";
if (it->first.IsI2P())
local_addresses += QString::fromStdString(it->first.ToStringAddr());
else
local_addresses += QString::fromStdString(it->first.ToStringAddr()) + ":" + QString::number(it->second.nPort);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe?

QString local_addresses;
std::map<CNetAddr, LocalServiceInfo> hosts = clientModel->getLocalAddresses();
for (const auto& [addr, info] : hosts) {
    local_addresses += QString::fromStdString(it->first.ToStringAddr());
    if (!addr.IsPrivacyNet()) local_addresses += ":" + QString::number(it->second.nPort);
    local_addresses += ", ";
}
local_addresses.chop(2); // remove last ", "

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more beautiful and understandable. Replaced the it->first & it->second with addr & info:

    for (const auto& [addr, info] : hosts) {
        local_addresses += QString::fromStdString(addr.ToStringAddr());
        if (!addr.IsPrivacyNet()) local_addresses += ":" + QString::number(info.nPort);
        local_addresses += ", ";
    }
    local_addresses.chop(2); // remove last ", "

@@ -168,6 +168,9 @@ class Node
//! Get num blocks.
virtual int getNumBlocks() = 0;

//! Get local addresses.
virtual std::map<CNetAddr, LocalServiceInfo> getLocalAddresses() = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
virtual std::map<CNetAddr, LocalServiceInfo> getLocalAddresses() = 0;
virtual std::map<CNetAddr, LocalServiceInfo> getNetLocalAddresses() = 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with this. Will change because "LocalAddress" can be confusing in BTC context.

std::map<CNetAddr, LocalServiceInfo> hosts = clientModel->getLocalAddresses();
for (const auto& [addr, info] : hosts) {
local_addresses += QString::fromStdString(addr.ToStringAddr());
if (!addr.IsPrivacyNet()) local_addresses += ":" + QString::number(info.nPort);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK Tor hidden services do still have port numbers

@luke-jr
Copy link
Member

luke-jr commented May 23, 2024

Also, please split the interfaces changes into a different commit than the GUI changes.

@luke-jr
Copy link
Member

luke-jr commented May 23, 2024

Might be a good improvement to cherry-pick e4fddc9f395819d0bdf74003676aa563929756fa and use it to update; in testing, I found myself watching for None to change, but it never did because the string isn't updated.

Contributes to bitcoin-core#564 by providing an interface for mapLocalHost
through net -> node interface -> clientModel. Later this value can be
read by GUI to show the local addresses.
@jadijadi
Copy link
Contributor Author

Also, please split the interfaces changes into a different commit than the GUI changes.

did that. cleaner and atomic. thanks.

@luke-jr
Copy link
Member

luke-jr commented May 23, 2024

did that. cleaner and atomic. thanks.

Hmm, it's not that way in your new push tho

@jadijadi
Copy link
Contributor Author

did that. cleaner and atomic. thanks.

Hmm, it's not that way in your new push tho

Ouch... pushed. thanks.

@luke-jr
Copy link
Member

luke-jr commented May 23, 2024

Somehow you introduced a typo in the commit message >_<

Adds a new row to the Node Window (debugwindow.ui)
under the Network section which shows the LocalAddresses.

fixes bitcoin-core#564
@jadijadi
Copy link
Contributor Author

Somehow you introduced a typo in the commit message >_<

Oh.. sorry. fixed and added a spellchecker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI failed UX All about "how to get things done"
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Display "local addresses" in "Node window"
10 participants