diff options
-rw-r--r-- | .github/workflows/gitian.yml | 49 | ||||
-rw-r--r-- | CMakeLists.txt | 12 | ||||
-rw-r--r-- | src/cryptonote_protocol/levin_notify.cpp | 7 | ||||
-rw-r--r-- | src/net/parse.cpp | 8 | ||||
-rw-r--r-- | src/net/parse.h | 10 | ||||
-rw-r--r-- | src/p2p/net_node.inl | 18 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 6 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 8 | ||||
-rw-r--r-- | tests/unit_tests/levin.cpp | 99 | ||||
-rw-r--r-- | tests/unit_tests/net.cpp | 35 |
10 files changed, 210 insertions, 42 deletions
diff --git a/.github/workflows/gitian.yml b/.github/workflows/gitian.yml new file mode 100644 index 000000000..6506e3d46 --- /dev/null +++ b/.github/workflows/gitian.yml @@ -0,0 +1,49 @@ +name: ci/gh-actions/gitian + +on: + push: + tags: + - '*' + +jobs: + build-gitian: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + operating-system: + - name: "Linux" + option: "l" + - name: "Windows" + option: "w" + - name: "Android" + option: "a" + - name: "FreeBSD" + option: "f" + - name: "macOS" + option: "m" + name: ${{ matrix.operating-system.name }} + steps: + - name: prepare + run: | + sudo apt update + curl -O https://raw.githubusercontent.com/monero-project/monero/${{ github.ref_name }}/contrib/gitian/gitian-build.py + chmod +x gitian-build.py + - name: setup + run: | + ./gitian-build.py --setup --docker github-actions ${{ github.ref_name }} + - name: build + run: | + ./gitian-build.py --docker --detach-sign --no-commit --build -j 3 -o ${{ matrix.operating-system.option }} github-actions ${{ github.ref_name }} + - name: post build + run: | + cd out/${{ github.ref_name }} + shasum -a256 * + echo \`\`\` >> $GITHUB_STEP_SUMMARY + shasum -a256 * >> $GITHUB_STEP_SUMMARY + echo \`\`\` >> $GITHUB_STEP_SUMMARY + - uses: actions/upload-artifact@v3.1.0 + with: + name: ${{ matrix.operating-system.name }} + path: | + out/${{ github.ref_name }}/* diff --git a/CMakeLists.txt b/CMakeLists.txt index b05c087cf..6ace6422d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1178,6 +1178,9 @@ find_library(NORM_LIBRARY norm) find_library(GSSAPI_LIBRARY gssapi_krb5) find_library(PROTOLIB_LIBRARY protolib) find_library(SODIUM_LIBRARY sodium) +find_library(BSD_LIBRARY bsd) +find_library(MD_LIBRARY md) +find_library(PROTOKIT_LIBRARY protokit) if(NOT ZMQ_INCLUDE_PATH) message(FATAL_ERROR "Could not find required header zmq.h") @@ -1200,6 +1203,15 @@ endif() if(SODIUM_LIBRARY) set(ZMQ_LIB "${ZMQ_LIB};${SODIUM_LIBRARY}") endif() +if(BSD_LIBRARY) + set(ZMQ_LIB "${ZMQ_LIB};${BSD_LIBRARY}") +endif() +if(MD_LIBRARY) + set(ZMQ_LIB "${ZMQ_LIB};${MD_LIBRARY}") +endif() +if(PROTOKIT_LIBRARY) + set(ZMQ_LIB "${ZMQ_LIB};${PROTOKIT_LIBRARY}") +endif() include(external/supercop/functions.cmake) # place after setting flags and before src directory inclusion add_subdirectory(contrib) diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 83f37015f..27c6d0278 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -542,6 +542,7 @@ namespace levin i_core_events* core_; std::vector<blobdata> txs_; boost::uuids::uuid source_; + relay_method tx_relay; //! \pre Called in `zone_->strand` void operator()() @@ -549,7 +550,7 @@ namespace levin if (!zone_ || !core_ || txs_.empty()) return; - if (!zone_->fluffing) + if (!zone_->fluffing || tx_relay == relay_method::local) { core_->on_transactions_relayed(epee::to_span(txs_), relay_method::stem); for (int tries = 2; 0 < tries; tries--) @@ -589,7 +590,7 @@ namespace levin change_channels(change_channels&&) = default; change_channels(const change_channels& source) - : zone_(source.zone_), map_(source.map_.clone()) + : zone_(source.zone_), map_(source.map_.clone()), fluffing_(source.fluffing_) {} //! \pre Called within `zone_->strand`. @@ -871,7 +872,7 @@ namespace levin { // this will change a local/forward tx to stem or fluff ... zone_->strand.dispatch( - dandelionpp_notify{zone_, core_, std::move(txs), source} + dandelionpp_notify{zone_, core_, std::move(txs), source, tx_relay} ); break; } diff --git a/src/net/parse.cpp b/src/net/parse.cpp index 1df6175b4..92be492a3 100644 --- a/src/net/parse.cpp +++ b/src/net/parse.cpp @@ -38,7 +38,7 @@ namespace net { void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port) { - // require ipv6 address format "[addr:addr:addr:...:addr]:port" + // If IPv6 address format with port "[addr:addr:addr:...:addr]:port" if (address.find(']') != std::string::npos) { host = address.substr(1, address.rfind(']') - 1); @@ -47,6 +47,12 @@ namespace net port = address.substr(address.rfind(':') + 1); } } + // Else if IPv6 address format without port e.g. "addr:addr:addr:...:addr" + else if (std::count(address.begin(), address.end(), ':') >= 2) + { + host = address; + } + // Else IPv4, Tor, I2P address or hostname else { host = address.substr(0, address.rfind(':')); diff --git a/src/net/parse.h b/src/net/parse.h index 648076d7b..6ece931c6 100644 --- a/src/net/parse.h +++ b/src/net/parse.h @@ -38,6 +38,16 @@ namespace net { + /*! + * \brief Takes a valid address string (IP, Tor, I2P, or DNS name) and splits it into host and port + * + * The host of an IPv6 addresses in the format "[x:x:..:x]:port" will have the braces stripped. + * For example, when the address is "[ffff::2023]", host will be set to "ffff::2023". + * + * \param address The address string one wants to split + * \param[out] host The host part of the address string. Is always set. + * \param[out] port The port part of the address string. Is only set when address string contains a port. + */ void get_network_address_host_and_port(const std::string& address, std::string& host, std::string& port); /*! diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index f33ce977d..df67734d5 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -645,20 +645,10 @@ namespace nodetool { using namespace boost::asio; - std::string host = addr; + // Split addr string into host string and port string + std::string host; std::string port = std::to_string(default_port); - size_t colon_pos = addr.find_last_of(':'); - size_t dot_pos = addr.find_last_of('.'); - size_t square_brace_pos = addr.find('['); - - // IPv6 will have colons regardless. IPv6 and IPv4 address:port will have a colon but also either a . or a [ - // as IPv6 addresses specified as address:port are to be specified as "[addr:addr:...:addr]:port" - // One may also specify an IPv6 address as simply "[addr:addr:...:addr]" without the port; in that case - // the square braces will be stripped here. - if ((std::string::npos != colon_pos && std::string::npos != dot_pos) || std::string::npos != square_brace_pos) - { - net::get_network_address_host_and_port(addr, host, port); - } + net::get_network_address_host_and_port(addr, host, port); MINFO("Resolving node address: host=" << host << ", port=" << port); io_service io_srv; @@ -2413,7 +2403,7 @@ namespace nodetool return false; } return true; - }); + }, "0.0.0.0", m_ssl_support); if(!r) { LOG_WARNING_CC(context, "Failed to call connect_async, network error."); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index c4d3856d4..085f4f9df 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -535,7 +535,7 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas view_wallet->generate(path, password, address, viewkey); // Export/Import outputs - auto outputs = m_wallet->export_outputs(); + auto outputs = m_wallet->export_outputs(true/*all*/); view_wallet->import_outputs(outputs); // Copy scanned blockchain @@ -553,7 +553,7 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas // Export/Import key images // We already know the spent status from the outputs we exported, thus no need to check them again - auto key_images = m_wallet->export_key_images(); + auto key_images = m_wallet->export_key_images(true/*all*/); uint64_t spent = 0; uint64_t unspent = 0; view_wallet->import_key_images(key_images.second, key_images.first, spent, unspent, false); @@ -1782,7 +1782,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0), m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 0), m_wallet->use_fork_rules(HF_VERSION_VIEW_TAGS, 0), - m_wallet->get_base_fee(), + m_wallet->get_base_fee(priority), m_wallet->get_fee_quantization_mask()); } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 17ff0e44b..91de4fe09 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -13326,9 +13326,7 @@ size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector< THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Imported outputs omit more outputs that we know of"); - THROW_WALLET_EXCEPTION_IF(offset >= num_outputs, error::wallet_internal_error, - "Offset is larger than total outputs"); - THROW_WALLET_EXCEPTION_IF(output_array.size() > num_outputs - offset, error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(offset + output_array.size() > num_outputs, error::wallet_internal_error, "Offset is larger than total outputs"); const size_t original_size = m_transfers.size(); @@ -13408,9 +13406,7 @@ size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector< THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Imported outputs omit more outputs that we know of. Try using export_outputs all."); - THROW_WALLET_EXCEPTION_IF(offset >= num_outputs, error::wallet_internal_error, - "Offset is larger than total outputs"); - THROW_WALLET_EXCEPTION_IF(output_array.size() > num_outputs - offset, error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(offset + output_array.size() > num_outputs, error::wallet_internal_error, "Offset is larger than total outputs"); const size_t original_size = m_transfers.size(); diff --git a/tests/unit_tests/levin.cpp b/tests/unit_tests/levin.cpp index 60d0079b6..416c14bcc 100644 --- a/tests/unit_tests/levin.cpp +++ b/tests/unit_tests/levin.cpp @@ -792,25 +792,33 @@ TEST_F(levin_notify, local_without_padding) notifier.new_out_connection(); io_service_.poll(); - std::vector<cryptonote::blobdata> txs(2); - txs[0].resize(100, 'f'); - txs[1].resize(200, 'e'); + std::vector<cryptonote::blobdata> my_txs(2); + my_txs[0].resize(100, 'f'); + my_txs[1].resize(200, 'e'); - std::vector<cryptonote::blobdata> sorted_txs = txs; - std::sort(sorted_txs.begin(), sorted_txs.end()); + std::vector<cryptonote::blobdata> their_txs{2}; + their_txs[0].resize(300, 'g'); + their_txs[1].resize(250, 'h'); + + std::vector<cryptonote::blobdata> my_sorted_txs = my_txs; + std::sort(my_sorted_txs.begin(), my_sorted_txs.end()); + + std::vector<cryptonote::blobdata> their_sorted_txs = their_txs; + std::sort(their_sorted_txs.begin(), their_sorted_txs.end()); ASSERT_EQ(10u, contexts_.size()); bool has_stemmed = false; bool has_fluffed = false; while (!has_stemmed || !has_fluffed) { + // run their "their" txes first auto context = contexts_.begin(); - EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), cryptonote::relay_method::local)); + EXPECT_TRUE(notifier.send_txs(their_txs, context->get_id(), cryptonote::relay_method::stem)); io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); const bool is_stem = events_.has_stem_txes(); - EXPECT_EQ(txs, events_.take_relayed(is_stem ? cryptonote::relay_method::stem : cryptonote::relay_method::fluff)); + EXPECT_EQ(their_txs, events_.take_relayed(is_stem ? cryptonote::relay_method::stem : cryptonote::relay_method::fluff)); if (!is_stem) { @@ -836,13 +844,41 @@ TEST_F(levin_notify, local_without_padding) { auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; if (is_stem) - EXPECT_EQ(txs, notification.txs); + EXPECT_EQ(their_txs, notification.txs); else - EXPECT_EQ(sorted_txs, notification.txs); + EXPECT_EQ(their_sorted_txs, notification.txs); EXPECT_TRUE(notification._.empty()); EXPECT_EQ(!is_stem, notification.dandelionpp_fluff); } + // run "my" txes which must always be stem + context = contexts_.begin(); + EXPECT_TRUE(notifier.send_txs(my_txs, context->get_id(), cryptonote::relay_method::local)); + + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + EXPECT_TRUE(events_.has_stem_txes()); + EXPECT_EQ(my_txs, events_.take_relayed(cryptonote::relay_method::stem)); + + send_count = 0; + EXPECT_EQ(0u, context->process_send_queue()); + for (++context; context != contexts_.end(); ++context) + { + const std::size_t sent = context->process_send_queue(); + if (sent) + { + EXPECT_EQ(1u, (context - contexts_.begin()) % 2); + } + send_count += sent; + } + + EXPECT_EQ(1u, send_count); + EXPECT_EQ(1u, receiver_.notified_size()); + auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; + EXPECT_EQ(my_txs, notification.txs); + EXPECT_TRUE(notification._.empty()); + EXPECT_TRUE(!notification.dandelionpp_fluff); + has_stemmed |= is_stem; has_fluffed |= !is_stem; notifier.run_epoch(); @@ -1170,22 +1206,27 @@ TEST_F(levin_notify, local_with_padding) notifier.new_out_connection(); io_service_.poll(); - std::vector<cryptonote::blobdata> txs(2); - txs[0].resize(100, 'e'); - txs[1].resize(200, 'f'); + std::vector<cryptonote::blobdata> my_txs(2); + my_txs[0].resize(100, 'e'); + my_txs[1].resize(200, 'f'); + + std::vector<cryptonote::blobdata> their_txs{2}; + their_txs[0].resize(300, 'g'); + their_txs[1].resize(250, 'h'); ASSERT_EQ(10u, contexts_.size()); bool has_stemmed = false; bool has_fluffed = false; while (!has_stemmed || !has_fluffed) { + // run their "their" txes first auto context = contexts_.begin(); - EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), cryptonote::relay_method::local)); + EXPECT_TRUE(notifier.send_txs(their_txs, context->get_id(), cryptonote::relay_method::stem)); io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); const bool is_stem = events_.has_stem_txes(); - EXPECT_EQ(txs, events_.take_relayed(is_stem ? cryptonote::relay_method::stem : cryptonote::relay_method::fluff)); + EXPECT_EQ(their_txs, events_.take_relayed(is_stem ? cryptonote::relay_method::stem : cryptonote::relay_method::fluff)); if (!is_stem) { @@ -1211,11 +1252,39 @@ TEST_F(levin_notify, local_with_padding) for (unsigned count = 0; count < (is_stem ? 1u : 9u); ++count) { auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; - EXPECT_EQ(txs, notification.txs); + EXPECT_EQ(their_txs, notification.txs); EXPECT_FALSE(notification._.empty()); EXPECT_EQ(!is_stem, notification.dandelionpp_fluff); } + // run "my" txes which must always be stem + context = contexts_.begin(); + EXPECT_TRUE(notifier.send_txs(my_txs, context->get_id(), cryptonote::relay_method::local)); + + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + EXPECT_TRUE(events_.has_stem_txes()); + EXPECT_EQ(my_txs, events_.take_relayed(cryptonote::relay_method::stem)); + + send_count = 0; + EXPECT_EQ(0u, context->process_send_queue()); + for (++context; context != contexts_.end(); ++context) + { + const std::size_t sent = context->process_send_queue(); + if (sent) + { + EXPECT_EQ(1u, (context - contexts_.begin()) % 2); + } + send_count += sent; + } + + EXPECT_EQ(1u, send_count); + EXPECT_EQ(1u, receiver_.notified_size()); + auto notification = receiver_.get_notification<cryptonote::NOTIFY_NEW_TRANSACTIONS>().second; + EXPECT_EQ(my_txs, notification.txs); + EXPECT_FALSE(notification._.empty()); + EXPECT_TRUE(!notification.dandelionpp_fluff); + has_stemmed |= is_stem; has_fluffed |= !is_stem; notifier.run_epoch(); diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp index 838f1af37..af0f07db0 100644 --- a/tests/unit_tests/net.cpp +++ b/tests/unit_tests/net.cpp @@ -938,6 +938,41 @@ TEST(get_network_address, ipv4subnet) namespace { + void na_host_and_port_test(std::string addr, std::string exp_host, std::string exp_port) + { + std::string host{"xxxxx"}; + std::string port{"xxxxx"}; + net::get_network_address_host_and_port(addr, host, port); + EXPECT_EQ(exp_host, host); + EXPECT_EQ(exp_port, port); + } +} // anonymous namespace + +TEST(get_network_address_host_and_port, ipv4) +{ + na_host_and_port_test("9.9.9.9", "9.9.9.9", "xxxxx"); + na_host_and_port_test("9.9.9.9:18081", "9.9.9.9", "18081"); +} + +TEST(get_network_address_host_and_port, ipv6) +{ + na_host_and_port_test("::ffff", "::ffff", "xxxxx"); + na_host_and_port_test("[::ffff]", "::ffff", "xxxxx"); + na_host_and_port_test("[::ffff]:00231", "::ffff", "00231"); + na_host_and_port_test("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "xxxxx"); + na_host_and_port_test("[7777:7777:7777:7777:7777:7777:7777:7777]", "7777:7777:7777:7777:7777:7777:7777:7777", "xxxxx"); + na_host_and_port_test("[7777:7777:7777:7777:7777:7777:7777:7777]:48080", "7777:7777:7777:7777:7777:7777:7777:7777", "48080"); +} + +TEST(get_network_address_host_and_port, hostname) +{ + na_host_and_port_test("localhost", "localhost", "xxxxx"); + na_host_and_port_test("bar:29080", "bar", "29080"); // Issue https://github.com/monero-project/monero/issues/8633 + na_host_and_port_test("xmrchain.net:18081", "xmrchain.net", "18081"); +} + +namespace +{ using stream_type = boost::asio::ip::tcp; struct io_thread |