From 512da314a5d3accd506813b5330e3d1c376d5628 Mon Sep 17 00:00:00 2001 From: zanzibar <> Date: Fri, 6 Jan 2023 15:21:08 +0000 Subject: [PATCH] BIP155 (addrv2) Tor v3 + i2p --- configure.ac | 34 + contrib/seeds/generate-seeds.py | 165 +++-- contrib/seeds/nodes_main.txt | 17 +- contrib/seeds/nodes_test.txt | 10 +- doc/i2p.md | 161 ++++ doc/tor.md | 281 ++++--- qa/rpc-tests/httpbasics.py | 4 +- qa/rpc-tests/p2p-acceptblock.py | 4 +- qa/rpc-tests/proxy_test.py | 22 +- src/Makefile.am | 39 +- src/addrdb.cpp | 122 +++ src/addrdb.h | 90 +++ src/addrman.cpp | 323 +++++--- src/addrman.h | 222 ++++-- src/arith_uint256.cpp | 2 +- src/attributes.h | 20 + src/bitcoin-cli.cpp | 2 +- src/cc/Makefile | 25 +- src/chainparams.cpp | 6 +- src/chainparams.h | 9 +- src/chainparamsseeds.h | 43 +- src/coins.cpp | 3 + src/compat.h | 12 +- src/core_read.cpp | 2 +- src/core_write.cpp | 2 +- src/crypto/common.h | 7 + src/crypto/equihash.h | 2 +- src/crypto/sha3.cpp | 162 ++++ src/crypto/sha3.h | 42 ++ src/gtest/json_test_vectors.h | 2 +- src/gtest/test_deprecation.cpp | 2 +- src/gtest/test_merkletree.cpp | 2 +- src/gtest/test_rpc.cpp | 2 +- src/gtest/test_txid.cpp | 2 +- src/hash.h | 67 +- src/httprpc.cpp | 2 +- src/httpserver.cpp | 57 +- src/hush-tx.cpp | 2 +- src/i2p.cpp | 441 +++++++++++ src/i2p.h | 256 +++++++ src/init.cpp | 77 +- src/key_io.cpp | 2 +- src/main.cpp | 189 +++-- src/main.h | 3 + src/merkleblock.cpp | 2 +- src/metrics.cpp | 2 +- src/net.cpp | 683 +++++++++++++---- src/net.h | 140 +++- src/netaddress.cpp | 1222 +++++++++++++++++++++++++++++++ src/netaddress.h | 534 ++++++++++++++ src/netbase.cpp | 1212 ++++++------------------------ src/netbase.h | 245 ++----- src/netmessagemaker.h | 37 + src/prevector.h | 243 +++--- src/primitives/block.cpp | 2 +- src/primitives/transaction.cpp | 2 +- src/protocol.cpp | 93 ++- src/protocol.h | 313 +++++++- src/random.cpp | 2 +- src/rest.cpp | 2 +- src/rpc/net.cpp | 65 +- src/rpc/protocol.cpp | 2 +- src/rpc/server.cpp | 2 +- src/script/script.cpp | 2 +- src/script/standard.cpp | 2 +- src/serialize.h | 320 +++++++- src/span.h | 252 +++++++ src/stratum.cpp | 15 +- src/streams.h | 96 ++- src/sync.cpp | 2 +- src/test-hush/test_addrman.cpp | 2 +- src/test-hush/testutils.cpp | 2 +- src/test/base32_tests.cpp | 2 +- src/test/base58_tests.cpp | 2 +- src/test/base64_tests.cpp | 2 +- src/test/bip32_tests.cpp | 2 +- src/test/bloom_tests.cpp | 2 +- src/test/coins_tests.cpp | 2 +- src/test/convertbits_tests.cpp | 2 +- src/test/crypto_tests.cpp | 2 +- src/test/hash_tests.cpp | 2 +- src/test/key_tests.cpp | 2 +- src/test/netbase_tests.cpp | 11 - src/test/rpc_tests.cpp | 2 +- src/test/serialize_tests.cpp | 2 +- src/test/util_tests.cpp | 2 +- src/timedata.cpp | 2 +- src/torcontrol.cpp | 80 +- src/torcontrol.h | 5 +- src/uint256.cpp | 2 +- src/util.cpp | 2 +- src/util/readwritefile.cpp | 47 ++ src/util/readwritefile.h | 29 + src/util/sock.cpp | 328 +++++++++ src/util/sock.h | 184 +++++ src/util/spanparsing.cpp | 68 ++ src/util/spanparsing.h | 50 ++ src/util/strencodings.cpp | 655 +++++++++++++++++ src/util/strencodings.h | 302 ++++++++ src/util/string.cpp | 6 + src/util/string.h | 99 +++ src/utilmoneystr.cpp | 2 +- src/utilstrencodings.cpp | 2 +- src/utiltime.cpp | 28 + src/utiltime.h | 11 + src/wallet/db.cpp | 2 +- src/wallet/wallet.h | 2 +- src/zcash/Note.cpp | 1 + 108 files changed, 8210 insertions(+), 2169 deletions(-) create mode 100644 doc/i2p.md create mode 100644 src/addrdb.cpp create mode 100644 src/addrdb.h create mode 100644 src/attributes.h create mode 100644 src/crypto/sha3.cpp create mode 100644 src/crypto/sha3.h create mode 100644 src/i2p.cpp create mode 100644 src/i2p.h create mode 100644 src/netaddress.cpp create mode 100644 src/netaddress.h create mode 100644 src/netmessagemaker.h create mode 100644 src/span.h create mode 100644 src/util/readwritefile.cpp create mode 100644 src/util/readwritefile.h create mode 100644 src/util/sock.cpp create mode 100644 src/util/sock.h create mode 100644 src/util/spanparsing.cpp create mode 100644 src/util/spanparsing.h create mode 100644 src/util/strencodings.cpp create mode 100644 src/util/strencodings.h create mode 100644 src/util/string.cpp create mode 100644 src/util/string.h diff --git a/configure.ac b/configure.ac index 94f48dd31..b46abbbce 100644 --- a/configure.ac +++ b/configure.ac @@ -221,6 +221,29 @@ if test "x$CXXFLAGS_overridden" = "xno"; then AX_CHECK_COMPILE_FLAG([-Wunused-local-typedef],[CXXFLAGS="$CXXFLAGS -Wno-unused-local-typedef"],,[[$CXXFLAG_WERROR]]) AX_CHECK_COMPILE_FLAG([-Wdeprecated-register],[CXXFLAGS="$CXXFLAGS -Wno-deprecated-register"],,[[$CXXFLAG_WERROR]]) fi + +TEMP_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $SSE42_CXXFLAGS" +AC_MSG_CHECKING(for assembler crc32 support) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include + #if defined(_MSC_VER) + #include + #elif defined(__GNUC__) && defined(__SSE4_2__) + #include + #endif + ]],[[ + uint64_t l = 0; + l = _mm_crc32_u8(l, 0); + l = _mm_crc32_u32(l, 0); + l = _mm_crc32_u64(l, 0); + return l; + ]])], + [ AC_MSG_RESULT(yes); enable_hwcrc32=yes], + [ AC_MSG_RESULT(no)] +) +CXXFLAGS="$TEMP_CXXFLAGS" + CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" AC_ARG_WITH([utils], @@ -241,6 +264,16 @@ AC_ARG_WITH([daemon], [build_bitcoind=$withval], [build_bitcoind=yes]) +GCC_TARGET=`$CC -dumpmachine 2>&1` +case $GCC_TARGET in + arm*-*-*) + have_arm=true + ;; + aarch64*-*-*) + have_arm=true + ;; +esac + use_pkgconfig=yes case $host in *mingw*) @@ -815,6 +848,7 @@ AM_CONDITIONAL([TARGET_WINDOWS], [test x$TARGET_OS = xwindows]) AM_CONDITIONAL([ENABLE_WALLET],[test x$enable_wallet = xyes]) AM_CONDITIONAL([ENABLE_MINING],[test x$enable_mining = xyes]) AM_CONDITIONAL([ENABLE_TESTS],[test x$BUILD_TEST = xyes]) +AM_CONDITIONAL([ARCH_ARM], [test x$have_arm = xtrue]) AM_CONDITIONAL([USE_LCOV],[test x$use_lcov = xyes]) AM_CONDITIONAL([GLIBC_BACK_COMPAT],[test x$use_glibc_compat = xyes]) AM_CONDITIONAL([HARDEN],[test x$use_hardening = xyes]) diff --git a/contrib/seeds/generate-seeds.py b/contrib/seeds/generate-seeds.py index ab9b4c68c..f50114d01 100755 --- a/contrib/seeds/generate-seeds.py +++ b/contrib/seeds/generate-seeds.py @@ -1,5 +1,6 @@ -#!/usr/bin/env python -# Copyright (c) 2014 Wladimir J. van der Laan +#!/usr/bin/env python3 +# Copyright (c) 2016-2022 The Hush developers +# Copyright (c) 2014-2021 The Bitcoin Core developers # Distributed under the GPLv3 software license, see the accompanying # file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html ''' @@ -11,45 +12,63 @@ argument: nodes_main.txt nodes_test.txt -These files must consist of lines in the format +These files must consist of lines in the format - : - [] + []: + [] + .onion: .onion - 0xDDBBCCAA (IPv4 little-endian old pnSeeds format) + .b32.i2p: + .b32.i2p The output will be two data structures with the peers in binary format: - static SeedSpec6 pnSeed6_main[]={ - ... - } - static SeedSpec6 pnSeed6_test[]={ + static const uint8_t chainparams_seed_{main,test}[]={ ... } -These should be pasted into `src/chainparamsseeds.h`. +To update the generated code : + +./contrib/seeds/generate-seeds.py contrib/seeds > src/chainparamsseeds.h + ''' -from __future__ import print_function, division + from base64 import b32decode -from binascii import a2b_hex -import sys, os +from enum import Enum +import struct +import sys +import os import re -# ipv4 in ipv6 prefix -pchIPv4 = bytearray([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff]) -# tor-specific ipv6 prefix -pchOnionCat = bytearray([0xFD,0x87,0xD8,0x7E,0xEB,0x43]) - -def name_to_ipv6(addr): - if len(addr)>6 and addr.endswith('.onion'): +class BIP155Network(Enum): + IPV4 = 1 + IPV6 = 2 + TORV2 = 3 # no longer supported + TORV3 = 4 + I2P = 5 + CJDNS = 6 + +def name_to_bip155(addr): + '''Convert address string to BIP155 (networkID, addr) tuple.''' + if addr.endswith('.onion'): vchAddr = b32decode(addr[0:-6], True) - if len(vchAddr) != 16-len(pchOnionCat): - raise ValueError('Invalid onion %s' % s) - return pchOnionCat + vchAddr + if len(vchAddr) == 35: + assert vchAddr[34] == 3 + return (BIP155Network.TORV3, vchAddr[:32]) + elif len(vchAddr) == 10: + return (BIP155Network.TORV2, vchAddr) + else: + raise ValueError('Invalid onion %s' % vchAddr) + elif addr.endswith('.b32.i2p'): + vchAddr = b32decode(addr[0:-8] + '====', True) + if len(vchAddr) == 32: + return (BIP155Network.I2P, vchAddr) + else: + raise ValueError(f'Invalid I2P {vchAddr}') elif '.' in addr: # IPv4 - return pchIPv4 + bytearray((int(x) for x in addr.split('.'))) + return (BIP155Network.IPV4, bytes((int(x) for x in addr.split('.')))) elif ':' in addr: # IPv6 sub = [[], []] # prefix, suffix x = 0 @@ -66,14 +85,13 @@ def name_to_ipv6(addr): sub[x].append(val & 0xff) nullbytes = 16 - len(sub[0]) - len(sub[1]) assert((x == 0 and nullbytes == 0) or (x == 1 and nullbytes > 0)) - return bytearray(sub[0] + ([0] * nullbytes) + sub[1]) - elif addr.startswith('0x'): # IPv4-in-little-endian - return pchIPv4 + bytearray(reversed(a2b_hex(addr[2:]))) + return (BIP155Network.IPV6, bytes(sub[0] + ([0] * nullbytes) + sub[1])) else: raise ValueError('Could not parse address %s' % addr) -def parse_spec(s, defaultport): - match = re.match('\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s) +def parse_spec(s): + '''Convert endpoint string to BIP155 (networkID, addr, port) tuple.''' + match = re.match(r'\[([0-9a-fA-F:]+)\](?::([0-9]+))?$', s) if match: # ipv6 host = match.group(1) port = match.group(2) @@ -84,17 +102,42 @@ def parse_spec(s, defaultport): (host,_,port) = s.partition(':') if not port: - port = defaultport + port = 0 else: port = int(port) - host = name_to_ipv6(host) + host = name_to_bip155(host) - return (host,port) - -def process_nodes(g, f, structname, defaultport): - g.write('static SeedSpec6 %s[] = {\n' % structname) - first = True + if host[0] == BIP155Network.TORV2: + return None # TORV2 is no longer supported, so we ignore it + else: + return host + (port, ) + +def ser_compact_size(l): + r = b"" + if l < 253: + r = struct.pack("B", l) + elif l < 0x10000: + r = struct.pack("H', spec[2]) + return r + +def process_nodes(g, f, structname): + g.write('static const uint8_t %s[] = {\n' % structname) for line in f: comment = line.find('#') if comment != -1: @@ -102,37 +145,37 @@ def process_nodes(g, f, structname, defaultport): line = line.strip() if not line: continue - if not first: - g.write(',\n') - first = False - (host,port) = parse_spec(line, defaultport) - hoststr = ','.join(('0x%02x' % b) for b in host) - g.write(' {{%s}, %i}' % (hoststr, port)) - g.write('\n};\n') + spec = parse_spec(line) + if spec is None: # ignore this entry (e.g. no longer supported addresses like TORV2) + continue + blob = bip155_serialize(spec) + hoststr = ','.join(('0x%02x' % b) for b in blob) + g.write(f' {hoststr}, // {line}\n') + g.write('};\n') def main(): if len(sys.argv)<2: print(('Usage: %s ' % sys.argv[0]), file=sys.stderr) - exit(1) + sys.exit(1) g = sys.stdout indir = sys.argv[1] - g.write('#ifndef BITCOIN_CHAINPARAMSSEEDS_H\n') - g.write('#define BITCOIN_CHAINPARAMSSEEDS_H\n') - g.write('/**\n') - g.write(' * List of fixed seed nodes for the bitcoin network\n') - g.write(' * AUTOGENERATED by contrib/seeds/generate-seeds.py\n') - g.write(' *\n') - g.write(' * Each line contains a 16-byte IPv6 address and a port.\n') - g.write(' * IPv4 as well as onion addresses are wrapped inside a IPv6 address accordingly.\n') - g.write(' */\n') - with open(os.path.join(indir,'nodes_main.txt'),'r') as f: - process_nodes(g, f, 'pnSeed6_main', 8233) + g.write('// Copyright (c) 2016-2022 The Hush developers\n') + g.write('// Distributed under the GPLv3 software license, see the accompanying\n') + g.write('// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html\n') + g.write('// THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY\n') + g.write('// Instead, run: ./contrib/seeds/generate-seeds.py contrib/seeds\n') + g.write('#ifndef HUSH_CHAINPARAMSSEEDS_H\n') + g.write('#define HUSH_CHAINPARAMSSEEDS_H\n') + g.write('// List of fixed seed nodes for the Hush network\n') + g.write('// Each line contains a BIP155 serialized address.\n') + g.write('//\n') + with open(os.path.join(indir,'nodes_main.txt'), 'r', encoding="utf8") as f: + process_nodes(g, f, 'chainparams_seed_main') g.write('\n') - with open(os.path.join(indir,'nodes_test.txt'),'r') as f: - process_nodes(g, f, 'pnSeed6_test', 18233) - g.write('#endif // BITCOIN_CHAINPARAMSSEEDS_H\n') - + with open(os.path.join(indir,'nodes_test.txt'), 'r', encoding="utf8") as f: + process_nodes(g, f, 'chainparams_seed_test') + g.write('#endif // HUSH_CHAINPARAMSSEEDS_H\n') + if __name__ == '__main__': main() - diff --git a/contrib/seeds/nodes_main.txt b/contrib/seeds/nodes_main.txt index a6e8be207..e9e68c259 100644 --- a/contrib/seeds/nodes_main.txt +++ b/contrib/seeds/nodes_main.txt @@ -1,6 +1,11 @@ -185.25.48.236:27485 -185.25.48.236:27487 -185.64.105.111:27485 -185.64.105.111:27487 -185.25.48.72:27485 -185.25.48.72:27487 +# node1.hush.land +185.241.61.43 + +# node2.hush.is +137.74.4.198 + +# lite.hushpool.is +149.28.102.219 + +# todo: torv3 +# todo: i2p diff --git a/contrib/seeds/nodes_test.txt b/contrib/seeds/nodes_test.txt index 98365ee50..be9218b99 100644 --- a/contrib/seeds/nodes_test.txt +++ b/contrib/seeds/nodes_test.txt @@ -1,11 +1,5 @@ # List of fixed seed nodes for testnet +# note: File must be non-empty to compile +1.2.3.4 # Onion nodes -thfsmmn2jbitcoin.onion -it2pj4f7657g3rhi.onion -nkf5e6b7pl4jfd4a.onion -4zhkir2ofl7orfom.onion -t6xj6wilh4ytvcs7.onion -i6y6ivorwakd7nw3.onion -ubqj4rsu3nqtxmtp.onion - diff --git a/doc/i2p.md b/doc/i2p.md new file mode 100644 index 000000000..e01573f9d --- /dev/null +++ b/doc/i2p.md @@ -0,0 +1,161 @@ +# I2P support in Hush + +It is possible to run a Hush or HSC full node as an +[I2P (Invisible Internet Project)](https://en.wikipedia.org/wiki/I2P) +service and connect to such services. + +This [glossary](https://geti2p.net/en/about/glossary) may be useful to get +started with I2P terminology. + +## Run with an I2P router (proxy) + +A running I2P router (proxy) with [SAM](https://geti2p.net/en/docs/api/samv3) +enabled is required. Options include: + +- [i2prouter (I2P Router)](https://geti2p.net), the official implementation in + Java +- [i2pd (I2P Daemon)](https://github.com/PurpleI2P/i2pd) + ([documentation](https://i2pd.readthedocs.io/en/latest)), a lighter + alternative in C++ (successfully tested with version 2.23 and up; version 2.36 + or later recommended) +- [i2p-zero](https://github.com/i2p-zero/i2p-zero) +- [other alternatives](https://en.wikipedia.org/wiki/I2P#Routers) + +Note the IP address and port the SAM proxy is listening to; usually, it is +`127.0.0.1:7656`. + +Once an I2P router with SAM enabled is up and running, use the following +configuration options: + +``` +-i2psam= + I2P SAM proxy to reach I2P peers and accept I2P connections (default: + none) + +-i2pacceptincoming + If set and -i2psam is also set then incoming I2P connections are + accepted via the SAM proxy. If this is not set but -i2psam is set + then only outgoing connections will be made to the I2P network. + Ignored if -i2psam is not set. Listening for incoming I2P + connections is done through the SAM proxy, not by binding to a + local address and port (default: 1) +``` + +In a typical situation, this suffices: + +``` +hushd -i2psam=127.0.0.1:7656 +``` + +The first time hushd connects to the I2P router, if +`-i2pacceptincoming=1`, then it will automatically generate a persistent I2P +address and its corresponding private key. The private key will be saved in a +file named `i2p_private_key` in the Hush data directory. The persistent +I2P address is used for accepting incoming connections and for making outgoing +connections if `-i2pacceptincoming=1`. If `-i2pacceptincoming=0` then only +outbound I2P connections are made and a different transient I2P address is used +for each connection to improve privacy. + +## Persistent vs transient I2P addresses + +In I2P connections, the connection receiver sees the I2P address of the +connection initiator. This is unlike the Tor network where the recipient does +not know who is connecting to them and can't tell if two connections are from +the same peer or not. + +If an I2P node is not accepting incoming connections, then Hush uses +random, one-time, transient I2P addresses for itself for outbound connections +to make it harder to discriminate, fingerprint or analyze it based on its I2P +address. + +## Additional configuration options related to I2P + +``` +-debug=i2p +``` + +Set the `debug=i2p` config logging option to see additional information in the +debug log about your I2P configuration and connections. Run `hush-cli help +logging` for more information. + +``` +-onlynet=i2p +``` + +Make automatic outbound connections only to I2P addresses. Inbound and manual +connections are not affected by this option. It can be specified multiple times +to allow multiple networks, e.g. onlynet=onion, onlynet=i2p. + +I2P support was added to Hush in version 3.9.3 and there may be fewer I2P +peers than Tor or IP ones. Therefore, using I2P alone without other networks may +make a node more susceptible to [Sybil +attacks](https://en.bitcoin.it/wiki/Weaknesses#Sybil_attack). You can use +`hush-cli -addrinfo` to see the number of I2P addresses known to your node. + +Another consideration with `onlynet=i2p` is that the initial blocks download +phase when syncing up a new node can be very slow. This phase can be sped up by +using other networks, for instance `onlynet=onion`, at the same time. + +In general, a node can be run with both onion and I2P hidden services (or +any/all of IPv4/IPv6/onion/I2P/CJDNS), which can provide a potential fallback if +one of the networks has issues. + +## I2P-related information + +There are several ways to see your I2P address if accepting +incoming I2P connections (`-i2pacceptincoming`): +- in the "Local addresses" output of CLI `-netinfo` +- in the "localaddresses" output of RPC `getnetworkinfo` +- in the debug log (grep for `AddLocal`; the I2P address ends in `.b32.i2p`) + +To see which I2P peers your node is connected to, use `hush-cli -netinfo 4` +or the `getpeerinfo` RPC (e.g. `hush-cli getpeerinfo`). + +To see which I2P addresses your node knows, use the `getnodeaddresses 0 i2p` +RPC. + +## Compatibility + +Hush uses the [SAM v3.1](https://geti2p.net/en/docs/api/samv3) protocol +to connect to the I2P network. Any I2P router that supports it can be used. + +## Ports in I2P and Hush + +Hush uses the [SAM v3.1](https://geti2p.net/en/docs/api/samv3) +protocol. One particularity of SAM v3.1 is that it does not support ports, +unlike newer versions of SAM (v3.2 and up) that do support them and default the +port numbers to 0. From the point of view of peers that use newer versions of +SAM or other protocols that support ports, a SAM v3.1 peer is connecting to them +on port 0, from source port 0. + +To allow future upgrades to newer versions of SAM, Hush sets its +listening port to 0 when listening for incoming I2P connections and advertises +its own I2P address with port 0. Furthermore, it will not attempt to connect to +I2P addresses with a non-zero port number because with SAM v3.1 the destination +port (`TO_PORT`) is always set to 0 and is not in the control of Hush. + +## Bandwidth + +I2P routers may route a large amount of general network traffic with their +default settings. Check your router's configuration to limit the amount of this +traffic relayed, if desired. + +With `i2pd`, the amount of bandwidth being shared with the wider network can be +adjusted with the `bandwidth`, `share` and `transittunnels` options in your +`i2pd.conf` file. For example, to limit total I2P traffic to 256KB/s and share +50% of this limit for a maximum of 20 transit tunnels: + +``` +bandwidth = 256 +share = 50 + +[limits] +transittunnels = 20 +``` + +If you prefer not to relay any public I2P traffic and only permit I2P traffic +from programs which are connecting via the SAM proxy, e.g. Hush, you +can set the `notransit` option to `true`. + +Similar bandwidth configuration options for the Java I2P router can be found in +`http://127.0.0.1:7657/config` under the "Bandwidth" tab. diff --git a/doc/tor.md b/doc/tor.md index c00db2fbd..fe0608888 100644 --- a/doc/tor.md +++ b/doc/tor.md @@ -1,154 +1,225 @@ -# Warning +# Tor - Do not assume Tor support works perfectly in Hush; better Tor support is currently being worked on. - -# Hush + Tor - -It is possible to run Hush as a Tor hidden service, and connect to such services. +It is possible to run Hush as a Tor onion service, and connect to such services. The following directions assume you have a Tor proxy running on port 9050. Many distributions default to having a SOCKS proxy listening on port 9050, but others may not. In particular, the Tor Browser Bundle defaults to listening on port 9150. See [Tor Project FAQ:TBBSocksPort](https://www.torproject.org/docs/faq.html.en#TBBSocksPort) for how to properly configure Tor. +## Compatibility + +- Starting with version 3.9.3, Hush only supports Tor version 3 hidden + services (Tor v3). Tor v2 addresses are ignored by Hush and neither + relayed nor stored. + +- Tor removed v2 support beginning with version 0.4.6. + +## How to see information about your Tor configuration via Hush + +There are several ways to see your local onion address in Hush: +- in the "Local addresses" output of CLI `-netinfo` +- in the "localaddresses" output of RPC `getnetworkinfo` +- in the debug log (grep for "AddLocal"; the Tor address ends in `.onion`) + +You may set the `-debug=tor` config logging option to have additional +information in the debug log about your Tor configuration. + +CLI `-addrinfo` returns the number of addresses known to your node per +network. This can be useful to see how many onion peers your node knows, +e.g. for `-onlynet=onion`. -1. Run Hush behind a Tor proxy -------------------------------- +To fetch a number of onion addresses that your node knows, for example seven +addresses, use the `getnodeaddresses 7 onion` RPC. -The first step is running Hush behind a Tor proxy. This will already make all -outgoing connections be anonymized, but more is possible. +## 1. Run Hush behind a Tor proxy - -proxy=ip:port Set the proxy server. If SOCKS5 is selected (default), this proxy - server will be used to try to reach .onion addresses as well. +The first step is running Hush behind a Tor proxy. This will already anonymize all +outgoing connections, but more is possible. - -onion=ip:port Set the proxy server to use for Tor hidden services. You do not - need to set this if it's the same as -proxy. You can use -noonion - to explicitly disable access to hidden service. + -proxy=ip:port Set the proxy server. If SOCKS5 is selected (default), this proxy + server will be used to try to reach .onion addresses as well. + You need to use -noonion or -onion=0 to explicitly disable + outbound access to onion services. - -listen When using -proxy, listening is disabled by default. If you want - to run a hidden service (see next section), you'll need to enable - it explicitly. + -onion=ip:port Set the proxy server to use for Tor onion services. You do not + need to set this if it's the same as -proxy. You can use -onion=0 + to explicitly disable access to onion services. + ------------------------------------------------------------------ + Note: Only the -proxy option sets the proxy for DNS requests; + with -onion they will not route over Tor, so use -proxy if you + have privacy concerns. + ------------------------------------------------------------------ - -connect=X When behind a Tor proxy, you can specify .onion addresses instead - -addnode=X of IP addresses or hostnames in these parameters. It requires - -seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with - other P2P nodes. + -listen When using -proxy, listening is disabled by default. If you want + to manually configure an onion service (see section 3), you'll + need to enable it explicitly. + + -connect=X When behind a Tor proxy, you can specify .onion addresses instead + -addnode=X of IP addresses or hostnames in these parameters. It requires + -seednode=X SOCKS5. In Tor mode, such addresses can also be exchanged with + other P2P nodes. + + -onlynet=onion Make automatic outbound connections only to .onion addresses. + Inbound and manual connections are not affected by this option. + It can be specified multiple times to allow multiple networks, + e.g. onlynet=onion, onlynet=i2p, onlynet=cjdns. In a typical situation, this suffices to run behind a Tor proxy: - ./hushd -proxy=127.0.0.1:9050 + ./hushd -proxy=127.0.0.1:9050 -If using the Tor Browser Bundle: +## 2. Automatically create a Hush onion service - ./hushd -proxy=127.0.0.1:9150 +Hush makes use of Tor's control socket API to create and destroy +ephemeral onion services programmatically. This means that if Tor is running and +proper authentication has been configured, Hush automatically creates an +onion service to listen on. The goal is to increase the number of available +onion nodes. +This feature is enabled by default if Hush is listening (`-listen`) and +it requires a Tor connection to work. It can be explicitly disabled with +`-listenonion=0`. If it is not disabled, it can be configured using the +`-torcontrol` and `-torpassword` settings. +To see verbose Tor information in the hushd debug log, pass `-debug=tor`. -2. Run a Hush hidden server ----------------------------- +### Control Port -If you configure your Tor system accordingly, it is possible to make your node also -reachable from the Tor network. Add these lines to your /etc/tor/torrc (or equivalent -config file): +You may need to set up the Tor Control Port. On Linux distributions there may be +some or all of the following settings in `/etc/tor/torrc`, generally commented +out by default (if not, add them): - HiddenServiceDir /var/lib/tor/hush-service/ - HiddenServicePort 18030 127.0.0.1:18030 +``` +ControlPort 9051 +CookieAuthentication 1 +CookieAuthFileGroupReadable 1 +``` -The directory can be different of course, but (both) port numbers should be equal to -your hushd's P2P listen port (18030 by default). +Add or uncomment those, save, and restart Tor (usually `systemctl restart tor` +or `sudo systemctl restart tor` on most systemd-based systems, including recent +Debian and Ubuntu, or just restart the computer). - -externalip=X You can tell Hush about its publicly reachable address using - this option, and this can be a .onion address. Given the above - configuration, you can find your onion address in - /var/lib/tor/hush-service/hostname. Onion addresses are given - preference for your node to advertize itself with, for connections - coming from unroutable addresses (such as 127.0.0.1, where the - Tor proxy typically runs). +On some systems (such as Arch Linux), you may also need to add the following +line: - -listen You'll need to enable listening for incoming connections, as this - is off by default behind a proxy. +``` +DataDirectoryGroupReadable 1 +``` - -discover When -externalip is specified, no attempt is made to discover local - IPv4 or IPv6 addresses. If you want to run a dual stack, reachable - from both Tor and IPv4 (or IPv6), you'll need to either pass your - other addresses using -externalip, or explicitly enable -discover. - Note that both addresses of a dual-stack system may be easily - linkable using traffic analysis. +### Authentication -In a typical situation, where you're only reachable via Tor, this should suffice: +Connecting to Tor's control socket API requires one of two authentication +methods to be configured: cookie authentication or hushd's `-torpassword` +configuration option. - ./hushd -proxy=127.0.0.1:9050 -externalip=hushc0de123.onion -listen +#### Cookie authentication -(obviously, replace the Onion address with your own). Currently only v2 HS's are supported. -It should be noted that you still listen on all devices and another node could establish a clearnet connection, when knowing -your address. To mitigate this, additionally bind the address of your Tor proxy: +For cookie authentication, the user running hushd must have read access to +the `CookieAuthFile` specified in the Tor configuration. In some cases this is +preconfigured and the creation of an onion service is automatic. Don't forget to +use the `-debug=tor` hushd configuration option to enable Tor debug logging. - ./hushd ... -bind=127.0.0.1 +If a permissions problem is seen in the debug log, e.g. `tor: Authentication +cookie /run/tor/control.authcookie could not be opened (check permissions)`, it +can be resolved by adding both the user running Tor and the user running +hushd to the same Tor group and setting permissions appropriately. -If you don't care too much about hiding your node, and want to be reachable on IPv4 -as well, use `discover` instead: +On Debian-derived systems, the Tor group will likely be `debian-tor` and one way +to verify could be to list the groups and grep for a "tor" group name: - ./hushd ... -discover +``` +getent group | cut -d: -f1 | grep -i tor +``` -and open port 18030 on your firewall. +You can also check the group of the cookie file. On most Linux systems, the Tor +auth cookie will usually be `/run/tor/control.authcookie`: -If you only want to use Tor to reach onion addresses, but not use it as a proxy -for normal IPv4/IPv6 communication, use: +``` +TORGROUP=$(stat -c '%G' /run/tor/control.authcookie) +``` - ./hushd -onion=127.0.0.1:9050 -externalip=hushc0de123.onion -discover +Once you have determined the `${TORGROUP}` and selected the `${USER}` that will +run hushd, run this as root: +``` +usermod -a -G ${TORGROUP} ${USER} +``` -3. Automatically listen on Tor --------------------------------- +Then restart the computer (or log out) and log in as the `${USER}` that will run +hushd. -Starting with Tor version 0.2.7.1 it is possible, through Tor's control socket -API, to create and destroy 'ephemeral' hidden services programmatically. -Hush has been updated to make use of this. +#### `torpassword` authentication -This means that if Tor is running (and proper authentication has been configured), -Hush automatically creates a hidden service to listen on. Hush will also use Tor -automatically to connect to other .onion nodes if the control socket can be -successfully opened. This will positively affect the number of available .onion -nodes and their usage. +For the `-torpassword=password` option, the password is the clear text form that +was used when generating the hashed password for the `HashedControlPassword` +option in the Tor configuration file. -This new feature is enabled by default if Hush is listening (`-listen`), and -requires a Tor connection to work. It can be explicitly disabled with `-listenonion=0` -and, if not disabled, configured using the `-torcontrol` and `-torpassword` settings. -To show verbose debugging information, pass `-debug=tor`. +The hashed password can be obtained with the command `tor --hash-password +password` (refer to the [Tor Dev +Manual](https://2019.www.torproject.org/docs/tor-manual.html.en) for more +details). -Connecting to Tor's control socket API requires one of two authentication methods to be -configured. For cookie authentication the user running hushd must have write access -to the `CookieAuthFile` specified in Tor configuration. In some cases this is -preconfigured and the creation of a hidden service is automatic. If permission problems -are seen with `-debug=tor` they can be resolved by adding both the user running tor and -the user running hushd to the same group and setting permissions appropriately. On -Debian-based systems the user running hushd can be added to the debian-tor group, -which has the appropriate permissions. An alternative authentication method is the use -of the `-torpassword` flag and a `hash-password` which can be enabled and specified in -Tor configuration. +## 3. Manually create a Hush onion service -4. Connect to a Hush hidden server ------------------------------------ +You can also manually configure your node to be reachable from the Tor network. +Add these lines to your `/etc/tor/torrc` (or equivalent config file): -To test your set-up, you might want to try connecting via Tor on a different computer to just a -a single Hush hidden server. Launch hushd as follows: + HiddenServiceDir /var/lib/tor/hush-service/ + HiddenServicePort 18030 127.0.0.1:18032 - ./hushd -onion=127.0.0.1:9050 -connect=fuckzookoie6wxgio.onion +The directory can be different of course, but virtual port numbers should be equal to +your hushd's P2P listen port (18030 by default), and target addresses and ports +should be equal to binding address and port for inbound Tor connections (127.0.0.1:18032 by default). -Now use hush-cli to verify there is only a single peer connection. + -externalip=X You can tell hush about its publicly reachable addresses using + this option, and this can be an onion address. Given the above + configuration, you can find your onion address in + /var/lib/tor/hush-service/hostname. For connections + coming from unroutable addresses (such as 127.0.0.1, where the + Tor proxy typically runs), onion addresses are given + preference for your node to advertise itself with. - hush-cli getpeerinfo + You can set multiple local addresses with -externalip. The + one that will be rumoured to a particular peer is the most + compatible one and also using heuristics, e.g. the address + with the most incoming connections, etc. + + -listen You'll need to enable listening for incoming connections, as this + is off by default behind a proxy. + + -discover When -externalip is specified, no attempt is made to discover local + IPv4 or IPv6 addresses. If you want to run a dual stack, reachable + from both Tor and IPv4 (or IPv6), you'll need to either pass your + other addresses using -externalip, or explicitly enable -discover. + Note that both addresses of a dual-stack system may be easily + linkable using traffic analysis. + +In a typical situation, where you're only reachable via Tor, this should suffice: + + ./hushd -proxy=127.0.0.1:9050 -externalip=7zvj7a2imdgkdbg4f2dryd5rgtrn7upivr5eeij4cicjh65pooxeshid.onion -listen + +(obviously, replace the .onion address with your own). It should be noted that you still +listen on all devices and another node could establish a clearnet connection, when knowing +your address. To mitigate this, additionally bind the address of your Tor proxy: + + ./hushd ... -bind=127.0.0.1 + +If you don't care too much about hiding your node, and want to be reachable on IPv4 +as well, use `discover` instead: + + ./hushd ... -discover + +and open port 18030 on your firewall (or use port mapping, i.e., `-upnp` or `-natpmp`). + +If you only want to use Tor to reach .onion addresses, but not use it as a proxy +for normal IPv4/IPv6 communication, use: - [ - { - "id" : 1, - "addr" : "zcashhoneypot.onion:18030", - ... - "version" : 1987420, - "subver" : "/GoldenSandtrout:3.6.0/", - ... - } - ] + ./hushd -onion=127.0.0.1:9050 -externalip=7zvj7a2imdgkdbg4f2dryd5rgtrn7upivr5eeij4cicjh65pooxeshid.onion -discover -To connect to multiple Tor nodes, use: +## 4. Privacy recommendations - ./hushd -onion=127.0.0.1:9050 -addnode=hushbeef123.onion -dnsseed=0 -onlynet=onion +- Do not add anything but Hush ports to the onion service created in section 3. + If you run a web service too, create a new onion service for that. + Otherwise it is trivial to link them, which may reduce privacy. Onion + services created automatically (as in section 2) always have only one port + open. diff --git a/qa/rpc-tests/httpbasics.py b/qa/rpc-tests/httpbasics.py index ff5544a2c..ec5393fe1 100755 --- a/qa/rpc-tests/httpbasics.py +++ b/qa/rpc-tests/httpbasics.py @@ -4,9 +4,7 @@ # Distributed under the GPLv3 software license, see the accompanying # file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html -# # Test rpc http basics -# from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, start_nodes @@ -97,7 +95,7 @@ class HTTPBasicsTest (BitcoinTestFramework): conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) out1 = conn.getresponse().read() assert_equal('"error":null' in out1, True) - assert_equal(conn.sock!=None, True) # connection must be closed because bitcoind should use keep-alive by default + assert_equal(conn.sock!=None, True) # connection must be closed because hushd should use keep-alive by default if __name__ == '__main__': HTTPBasicsTest().main() diff --git a/qa/rpc-tests/p2p-acceptblock.py b/qa/rpc-tests/p2p-acceptblock.py index 4dccdfae5..af28eecfc 100755 --- a/qa/rpc-tests/p2p-acceptblock.py +++ b/qa/rpc-tests/p2p-acceptblock.py @@ -116,8 +116,8 @@ class TestNode(NodeConnCB): class AcceptBlockTest(BitcoinTestFramework): def add_options(self, parser): parser.add_option("--testbinary", dest="testbinary", - default=os.getenv("BITCOIND", "bitcoind"), - help="bitcoind binary to test") + default=os.getenv("BITCOIND", "hushd"), + help="hushd binary to test") def setup_chain(self): initialize_chain_clean(self.options.tmpdir, 2) diff --git a/qa/rpc-tests/proxy_test.py b/qa/rpc-tests/proxy_test.py index 1a200db56..eb69e6bae 100755 --- a/qa/rpc-tests/proxy_test.py +++ b/qa/rpc-tests/proxy_test.py @@ -13,10 +13,10 @@ import os ''' Test plan: -- Start bitcoind's with different proxy configurations +- Start hushd's with different proxy configurations - Use addnode to initiate connections - Verify that proxies are connected to, and the right connection command is given -- Proxy configurations to test on bitcoind side: +- Proxy configurations to test on hushd side: - `-proxy` (proxy everything) - `-onion` (proxy just onions) - `-proxyrandomize` Circuit randomization @@ -26,8 +26,8 @@ Test plan: - proxy on IPv6 - Create various proxies (as threads) -- Create bitcoinds that connect to them -- Manipulate the bitcoinds using addnode (onetry) an observe effects +- Create hushds that connect to them +- Manipulate the hushds using addnode (onetry) an observe effects addnode connect to IPv4 addnode connect to IPv6 @@ -78,7 +78,7 @@ class ProxyTest(BitcoinTestFramework): node.addnode("15.61.23.23:1234", "onetry") cmd = proxies[0].queue.get() assert(isinstance(cmd, Socks5Command)) - # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 + # Note: hushd's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 assert_equal(cmd.atyp, AddressType.DOMAINNAME) assert_equal(cmd.addr, "15.61.23.23") assert_equal(cmd.port, 1234) @@ -91,7 +91,7 @@ class ProxyTest(BitcoinTestFramework): node.addnode("[1233:3432:2434:2343:3234:2345:6546:4534]:5443", "onetry") cmd = proxies[1].queue.get() assert(isinstance(cmd, Socks5Command)) - # Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 + # Note: hushd's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6 assert_equal(cmd.atyp, AddressType.DOMAINNAME) assert_equal(cmd.addr, "1233:3432:2434:2343:3234:2345:6546:4534") assert_equal(cmd.port, 5443) @@ -102,24 +102,24 @@ class ProxyTest(BitcoinTestFramework): if test_onion: # Test: outgoing onion connection through node - node.addnode("bitcoinostk4e4re.onion:8333", "onetry") + node.addnode("hushostk4e4re.onion:18030", "onetry") cmd = proxies[2].queue.get() assert(isinstance(cmd, Socks5Command)) assert_equal(cmd.atyp, AddressType.DOMAINNAME) - assert_equal(cmd.addr, "bitcoinostk4e4re.onion") - assert_equal(cmd.port, 8333) + assert_equal(cmd.addr, "hushostk4e4re.onion") + assert_equal(cmd.port, 18030) if not auth: assert_equal(cmd.username, None) assert_equal(cmd.password, None) rv.append(cmd) # Test: outgoing DNS name connection through node - node.addnode("node.noumenon:8333", "onetry") + node.addnode("node.noumenon:18030", "onetry") cmd = proxies[3].queue.get() assert(isinstance(cmd, Socks5Command)) assert_equal(cmd.atyp, AddressType.DOMAINNAME) assert_equal(cmd.addr, "node.noumenon") - assert_equal(cmd.port, 8333) + assert_equal(cmd.port, 18030) if not auth: assert_equal(cmd.username, None) assert_equal(cmd.password, None) diff --git a/src/Makefile.am b/src/Makefile.am index 684e1a611..70f148385 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,6 +9,12 @@ AM_CXXFLAGS = $(SAN_CXXFLAGS) $(HARDENED_CXXFLAGS) $(ERROR_CXXFLAGS) AM_CPPFLAGS = $(HARDENED_CPPFLAGS) EXTRA_LIBRARIES = +if ARCH_ARM +PLATFORM_VARIANT = armv8.1-a+crypto +else +PLATFORM_VARIANT = x86-64 +endif + if EMBEDDED_LEVELDB LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/include LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/helpers/memenv @@ -28,6 +34,7 @@ BITCOIN_CONFIG_INCLUDES=-I$(builddir)/config BITCOIN_INCLUDES=-I$(builddir) -I$(builddir)/obj $(BDB_CPPFLAGS) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) $(CRYPTO_CFLAGS) $(SSL_CFLAGS) BITCOIN_INCLUDES += -I$(srcdir)/secp256k1/include +BITCOIN_INCLUDES += -I$(srcdir)/cc/includes BITCOIN_INCLUDES += -I$(srcdir)/cryptoconditions/include BITCOIN_INCLUDES += -I$(srcdir)/cryptoconditions/src BITCOIN_INCLUDES += -I$(srcdir)/cryptoconditions/src/asn @@ -63,13 +70,13 @@ LIBBITCOIN_WALLET=libbitcoin_wallet.a endif $(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*) - $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) OPTFLAGS="-O2 -g " + $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) OPTFLAGS="-O2 -march=$(PLATFORM_VARIANT) -g " $(LIBUNIVALUE): $(wildcard univalue/lib/*) - $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) OPTFLAGS="-O2 -g " + $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) OPTFLAGS="-O2 -march=$(PLATFORM_VARIANT) -g " $(LIBCRYPTOCONDITIONS): $(wildcard cryptoconditions/src/*) $(wildcard cryptoconditions/include/*) - $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) OPTFLAGS="-O2 -g " + $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) OPTFLAGS="-O2 -march=$(PLATFORM_VARIANT) -g " # Make is not made aware of per-object dependencies to avoid limiting building parallelization # But to build the less dependent modules first, we manually select their order here: @@ -122,6 +129,8 @@ BITCOIN_CORE_H = \ addressindex.h \ spentindex.h \ addrman.h \ + attributes.h \ + addrdb.h \ amount.h \ amqp/amqpabstractnotifier.h \ amqp/amqpconfig.h \ @@ -160,6 +169,7 @@ BITCOIN_CORE_H = \ hash.h \ httprpc.h \ httpserver.h \ + i2p.h \ init.h \ key.h \ key_io.h \ @@ -174,10 +184,13 @@ BITCOIN_CORE_H = \ mruset.h \ net.h \ netbase.h \ + netaddress.h \ + netmessagemaker.h \ noui.h \ policy/fees.h \ pow.h \ prevector.h \ + span.h \ primitives/block.h \ primitives/transaction.h \ protocol.h \ @@ -217,8 +230,13 @@ BITCOIN_CORE_H = \ uint252.h \ undo.h \ util.h \ + util/readwritefile.h \ + util/sock.h \ + util/string.h \ + util/spanparsing.h \ + util/strencodings.h \ utilmoneystr.h \ - utilstrencodings.h \ + # utilstrencodings.h \ utiltime.h \ validationinterface.h \ version.h \ @@ -249,6 +267,7 @@ libbitcoin_server_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ addrman.cpp \ + addrdb.cpp \ asyncrpcoperation.cpp \ asyncrpcqueue.cpp \ bloom.cpp \ @@ -283,6 +302,7 @@ libbitcoin_server_a_SOURCES = \ deprecation.cpp \ httprpc.cpp \ httpserver.cpp \ + i2p.cpp \ init.cpp \ dbwrapper.cpp \ main.cpp \ @@ -357,6 +377,8 @@ crypto_libbitcoin_crypto_a_SOURCES = \ crypto/ripemd160.h \ crypto/sha1.cpp \ crypto/sha1.h \ + crypto/sha3.cpp \ + crypto/sha3.h \ crypto/sha256.cpp \ crypto/sha256.h \ crypto/sha512.cpp \ @@ -397,6 +419,7 @@ libbitcoin_common_a_SOURCES = \ key.cpp \ key_io.cpp \ keystore.cpp \ + netaddress.cpp \ netbase.cpp \ metrics.cpp \ primitives/block.cpp \ @@ -435,9 +458,13 @@ libbitcoin_util_a_SOURCES = \ uint256.cpp \ util.cpp \ utilmoneystr.cpp \ - utilstrencodings.cpp \ utiltime.cpp \ + util/strencodings.cpp \ util/asmap.cpp \ + util/sock.cpp \ + util/spanparsing.cpp \ + util/string.cpp \ + util/readwritefile.cpp \ $(BITCOIN_CORE_H) \ $(LIBZCASH_H) @@ -667,6 +694,8 @@ clean-local: -$(MAKE) -C univalue clean rm -f leveldb/*/*.gcno leveldb/helpers/memenv/*.gcno -rm -f config.h + -rm -f *.a + -rm -f *.so .rc.o: @test -f $(WINDRES) diff --git a/src/addrdb.cpp b/src/addrdb.cpp new file mode 100644 index 000000000..e07e5cca1 --- /dev/null +++ b/src/addrdb.cpp @@ -0,0 +1,122 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2016 The Bitcoin Core developers +// Copyright (c) 2016-2022 The Hush developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html + +#include "addrdb.h" +#include "addrman.h" +#include "chainparams.h" +#include "clientversion.h" +#include "fs.h" +#include "hash.h" +#include "random.h" +#include "streams.h" +#include "tinyformat.h" +#include "util.h" + +namespace { + +template +bool SerializeDB(Stream& stream, const Data& data) +{ + // Write and commit header, data + try { + CHashWriter hasher(stream.GetType(), stream.GetVersion()); + stream << FLATDATA(Params().MessageStart()) << data; + hasher << FLATDATA(Params().MessageStart()) << data; + stream << hasher.GetHash(); + } catch (const std::exception& e) { + return error("%s: Serialize or I/O error - %s", __func__, e.what()); + } + + return true; +} + +template +bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data& data) +{ + // Generate random temporary filename + unsigned short randv = 0; + GetRandBytes((unsigned char*)&randv, sizeof(randv)); + std::string tmpfn = strprintf("%s.%04x", prefix, randv); + + // open temp output file, and associate with CAutoFile + fs::path pathTmp = GetDataDir() / tmpfn; + FILE *file = fsbridge::fopen(pathTmp, "wb"); + CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); + if (fileout.IsNull()) + return error("%s: Failed to open file %s", __func__, pathTmp.string()); + + // Serialize + if (!SerializeDB(fileout, data)) return false; + FileCommit(fileout.Get()); + fileout.fclose(); + + // replace existing file, if any, with new file + if (!RenameOver(pathTmp, path)) + return error("%s: Rename-into-place failed", __func__); + + return true; +} + +template +bool DeserializeDB(Stream& stream, Data& data, bool fCheckSum = true) +{ + try { + CHashVerifier verifier(&stream); + // de-serialize file header (network specific magic number) and .. + unsigned char pchMsgTmp[4]; + verifier >> FLATDATA(pchMsgTmp); + // ... verify the network matches ours + if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) + return error("%s: Invalid network magic number", __func__); + + // de-serialize data + verifier >> data; + + // verify checksum + if (fCheckSum) { + uint256 hashTmp; + stream >> hashTmp; + if (hashTmp != verifier.GetHash()) { + return error("%s: Checksum mismatch, data corrupted", __func__); + } + } + } + catch (const std::exception& e) { + return error("%s: Deserialize or I/O error - %s", __func__, e.what()); + } + + return true; +} + +template +bool DeserializeFileDB(const fs::path& path, Data& data) +{ + // open input file, and associate with CAutoFile + FILE *file = fsbridge::fopen(path, "rb"); + CAutoFile filein(file, SER_DISK, CLIENT_VERSION); + if (filein.IsNull()) + return error("%s: Failed to open file %s", __func__, path.string()); + + return DeserializeDB(filein, data); +} + +} + +CBanDB::CBanDB() +{ + pathBanlist = GetDataDir() / "banlist.dat"; +} + +bool CBanDB::Write(const banmap_t& banSet) +{ + return SerializeFileDB("banlist", pathBanlist, banSet); +} + +bool CBanDB::Read(banmap_t& banSet) +{ + return DeserializeFileDB(pathBanlist, banSet); +} + diff --git a/src/addrdb.h b/src/addrdb.h new file mode 100644 index 000000000..a5cf7dc97 --- /dev/null +++ b/src/addrdb.h @@ -0,0 +1,90 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2016 The Bitcoin Core developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html + +#ifndef HUSH_ADDRDB_H +#define HUSH_ADDRDB_H + +#include "fs.h" +#include "serialize.h" + +#include +#include + +class CSubNet; +class CAddrMan; +class CDataStream; + +typedef enum BanReason +{ + BanReasonUnknown = 0, + BanReasonNodeMisbehaving = 1, + BanReasonManuallyAdded = 2 +} BanReason; + +class CBanEntry +{ +public: + static const int CURRENT_VERSION=1; + int nVersion; + int64_t nCreateTime; + int64_t nBanUntil; + uint8_t banReason; + + CBanEntry() + { + SetNull(); + } + + explicit CBanEntry(int64_t nCreateTimeIn) + { + SetNull(); + nCreateTime = nCreateTimeIn; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(this->nVersion); + READWRITE(nCreateTime); + READWRITE(nBanUntil); + READWRITE(banReason); + } + + void SetNull() + { + nVersion = CBanEntry::CURRENT_VERSION; + nCreateTime = 0; + nBanUntil = 0; + banReason = BanReasonUnknown; + } + + std::string banReasonToString() const + { + switch (banReason) { + case BanReasonNodeMisbehaving: + return "node misbehaving"; + case BanReasonManuallyAdded: + return "manually added"; + default: + return "unknown"; + } + } +}; + +typedef std::map banmap_t; + +/** Access to the banlist database (banlist.dat) */ +class CBanDB +{ +private: + fs::path pathBanlist; +public: + CBanDB(); + bool Write(const banmap_t& banSet); + bool Read(banmap_t& banSet); +}; + +#endif // HUSH_ADDRDB_H diff --git a/src/addrman.cpp b/src/addrman.cpp index d9b8a07cb..1148d5d7a 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -23,6 +23,7 @@ #include "hash.h" #include "serialize.h" #include "streams.h" +#include "init.h" int CAddrInfo::GetTriedBucket(const uint256& nKey, const std::vector &asmap) const { @@ -53,6 +54,9 @@ int CAddrInfo::GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) co bool CAddrInfo::IsTerrible(int64_t nNow) const { + if (fLocal) //never remove local addresses + return false; + if (nLastTry && nLastTry >= nNow - 60) // never remove things tried in the last minute return false; @@ -71,6 +75,14 @@ bool CAddrInfo::IsTerrible(int64_t nNow) const return false; } +bool CAddrInfo::IsJustTried(int64_t nNow) const +{ + if (nLastTry && nLastTry >= nNow - 60) + return true; + + return false; +} + double CAddrInfo::GetChance(int64_t nNow) const { double fChance = 1.0; @@ -95,24 +107,30 @@ double CAddrInfo::GetChance(int64_t nNow) const CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int* pnId) { - std::map::iterator it = mapAddr.find(addr); + AssertLockHeld(cs); + + const auto it = mapAddr.find(addr); if (it == mapAddr.end()) - return NULL; + return nullptr; if (pnId) *pnId = (*it).second; - std::map::iterator it2 = mapInfo.find((*it).second); + const auto it2 = mapInfo.find((*it).second); if (it2 != mapInfo.end()) return &(*it2).second; - return NULL; + return nullptr; } CAddrInfo* CAddrMan::Create(const CAddress& addr, const CNetAddr& addrSource, int* pnId) { - int nId = nIdCount++; + AssertLockHeld(cs); + + int nId = nIdCount; mapInfo[nId] = CAddrInfo(addr, addrSource); mapAddr[addr] = nId; mapInfo[nId].nRandomPos = vRandom.size(); vRandom.push_back(nId); + nNew++; + nIdCount++; if (pnId) *pnId = nId; return &mapInfo[nId]; @@ -120,6 +138,8 @@ CAddrInfo* CAddrMan::Create(const CAddress& addr, const CNetAddr& addrSource, in void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) { + AssertLockHeld(cs); + if (nRndPos1 == nRndPos2) return; @@ -128,11 +148,13 @@ void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) int nId1 = vRandom[nRndPos1]; int nId2 = vRandom[nRndPos2]; - assert(mapInfo.count(nId1) == 1); - assert(mapInfo.count(nId2) == 1); + const auto it_1{mapInfo.find(nId1)}; + const auto it_2{mapInfo.find(nId2)}; + assert(it_1 != mapInfo.end()); + assert(it_2 != mapInfo.end()); - mapInfo[nId1].nRandomPos = nRndPos2; - mapInfo[nId2].nRandomPos = nRndPos1; + it_1->second.nRandomPos = nRndPos2; + it_2->second.nRandomPos = nRndPos1; vRandom[nRndPos1] = nId2; vRandom[nRndPos2] = nId1; @@ -140,41 +162,57 @@ void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) void CAddrMan::Delete(int nId) { - assert(mapInfo.count(nId) != 0); - CAddrInfo& info = mapInfo[nId]; - assert(!info.fInTried); - assert(info.nRefCount == 0); + AssertLockHeld(cs); + + const auto it{mapInfo.find(nId)}; + if (it != mapInfo.end()) { + CAddrInfo& info = (*it).second; + assert(!info.fInTried); + assert(info.nRefCount == 0); + + SwapRandom(info.nRandomPos, vRandom.size() - 1); + vRandom.pop_back(); + mapAddr.erase(info); + mapInfo.erase(nId); + nNew--; + } - SwapRandom(info.nRandomPos, vRandom.size() - 1); - vRandom.pop_back(); - mapAddr.erase(info); - mapInfo.erase(nId); - nNew--; } void CAddrMan::ClearNew(int nUBucket, int nUBucketPos) { + AssertLockHeld(cs); + // if there is an entry in the specified bucket, delete it. if (vvNew[nUBucket][nUBucketPos] != -1) { int nIdDelete = vvNew[nUBucket][nUBucketPos]; - CAddrInfo& infoDelete = mapInfo[nIdDelete]; - assert(infoDelete.nRefCount > 0); - infoDelete.nRefCount--; - vvNew[nUBucket][nUBucketPos] = -1; - if (infoDelete.nRefCount == 0) { - Delete(nIdDelete); + const auto it{mapInfo.find(nIdDelete)}; + if (it != mapInfo.end()) { + CAddrInfo& infoDelete = (*it).second; + assert(infoDelete.nRefCount > 0); + infoDelete.nRefCount--; + vvNew[nUBucket][nUBucketPos] = -1; + if (infoDelete.nRefCount == 0) { + Delete(nIdDelete); + } } } + } void CAddrMan::MakeTried(CAddrInfo& info, int nId) { + AssertLockHeld(cs); + // remove the entry from all new buckets - for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { - int pos = info.GetBucketPosition(nKey, true, bucket); + const int start_bucket{info.GetNewBucket(nKey, m_asmap)}; + for (int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; ++n) { + const int bucket{(start_bucket + n) % ADDRMAN_NEW_BUCKET_COUNT}; + const int pos{info.GetBucketPosition(nKey, true, bucket)}; if (vvNew[bucket][pos] == nId) { vvNew[bucket][pos] = -1; info.nRefCount--; + if (info.nRefCount == 0) break; } } nNew--; @@ -215,67 +253,6 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId) info.fInTried = true; } -void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime) { - int nId; - CAddrInfo* pinfo = Find(addr, &nId); - - // if not found, bail out - if (!pinfo) - return; - - CAddrInfo& info = *pinfo; - - // check whether we are talking about the exact same CService (including same port) - if (info != addr) - return; - - // update info - info.nLastSuccess = nTime; - info.nLastTry = nTime; - info.nAttempts = 0; - // nTime is not updated here, to avoid leaking information about - // currently-connected peers. - - // if it is already in the tried set, don't do anything else - if (info.fInTried) - return; - - // find a bucket it is in now - int nRnd = RandomInt(ADDRMAN_NEW_BUCKET_COUNT); - int nUBucket = -1; - for (unsigned int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; n++) { - int nB = (n + nRnd) % ADDRMAN_NEW_BUCKET_COUNT; - int nBpos = info.GetBucketPosition(nKey, true, nB); - if (vvNew[nB][nBpos] == nId) { - nUBucket = nB; - break; - } - } - - // if no bucket is found, something bad happened; - // TODO: maybe re-add the node, but for now, just bail out - if (nUBucket == -1) - return; - - // which tried bucket to move the entry to - int tried_bucket = info.GetTriedBucket(nKey,m_asmap); - int tried_bucket_pos = info.GetBucketPosition(nKey, false, tried_bucket); - - // Will moving this address into tried evict another entry? - if (test_before_evict && (vvTried[tried_bucket][tried_bucket_pos] != -1)) { - LogPrint("addrman", "Collision inserting element into tried table, moving %s to m_tried_collisions=%d\n", addr.ToString(), m_tried_collisions.size()); - if (m_tried_collisions.size() < ADDRMAN_SET_TRIED_COLLISION_SIZE) { - m_tried_collisions.insert(nId); - } - } else { - LogPrint("addrman", "Moving %s to tried\n", addr.ToString()); - printf("%s: Moving %s to tried\n", __func__, addr.ToString().c_str() ); - - // move nId to the tried tables - MakeTried(info, nId); - } -} - void CAddrMan::ResolveCollisions_() { for (std::set::iterator it = m_tried_collisions.begin(); it != m_tried_collisions.end();) { int id_new = *it; @@ -351,13 +328,59 @@ CAddrInfo CAddrMan::SelectTriedCollision_() { return mapInfo[id_old]; } +void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime) { + int nId; + CAddrInfo* pinfo = Find(addr, &nId); + + // if not found, bail out + if (!pinfo) + return; + + CAddrInfo& info = *pinfo; + + // check whether we are talking about the exact same CService (including same port) + if (info != addr) + return; + + // update info + info.nLastSuccess = nTime; + info.nLastTry = nTime; + info.nAttempts = 0; + // nTime is not updated here, to avoid leaking information about + // currently-connected peers. + + // if it is already in the tried set, don't do anything else + if (info.fInTried) + return; + + // find a bucket it is in now + int nRnd = RandomInt(ADDRMAN_NEW_BUCKET_COUNT); + int nUBucket = -1; + for (unsigned int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; n++) { + int nB = (n + nRnd) % ADDRMAN_NEW_BUCKET_COUNT; + int nBpos = info.GetBucketPosition(nKey, true, nB); + if (vvNew[nB][nBpos] == nId) { + nUBucket = nB; + break; + } + } + + // if no bucket is found, something bad happened; + // TODO: maybe re-add the node, but for now, just bail out + if (nUBucket == -1) + return; + + LogPrint("addrman", "Moving %s to tried\n", addr.ToString()); + + // move nId to the tried tables + MakeTried(info, nId); +} bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty) { if (!addr.IsRoutable()) return false; - bool fNew = false; int nId; CAddrInfo* pinfo = Find(addr, &nId); @@ -392,19 +415,20 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP } else { pinfo = Create(addr, source, &nId); pinfo->nTime = std::max((int64_t)0, (int64_t)pinfo->nTime - nTimePenalty); - nNew++; - fNew = true; } int nUBucket = pinfo->GetNewBucket(nKey, source, m_asmap); int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket); + bool fInsert = vvNew[nUBucket][nUBucketPos] == -1; if (vvNew[nUBucket][nUBucketPos] != nId) { - bool fInsert = vvNew[nUBucket][nUBucketPos] == -1; if (!fInsert) { - CAddrInfo& infoExisting = mapInfo[vvNew[nUBucket][nUBucketPos]]; - if (infoExisting.IsTerrible() || (infoExisting.nRefCount > 1 && pinfo->nRefCount == 0)) { - // Overwrite the existing new table entry. - fInsert = true; + const auto it{mapInfo.find(vvNew[nUBucket][nUBucketPos])}; + if (it != mapInfo.end()) { + CAddrInfo& infoExisting = (*it).second; + if (infoExisting.IsTerrible() || (infoExisting.nRefCount > 1 && pinfo->nRefCount == 0)) { + // Overwrite the existing new table entry. + fInsert = true; + } } } if (fInsert) { @@ -417,7 +441,7 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP } } } - return fNew; + return fInsert; } void CAddrMan::Attempt_(const CService& addr, int64_t nTime) @@ -454,10 +478,15 @@ CAddrInfo CAddrMan::Select_(bool newOnly) // Use a 50% chance for choosing between tried and new table entries. if (!newOnly && - (nTried > 0 && (nNew == 0 || RandomInt(2) == 0))) { + (nTried > 0 && (nNew == 0 || RandomInt(2) == 0))) { // use a tried node double fChanceFactor = 1.0; + double fReachableFactor = 1.0; + double fJustTried = 1.0; while (1) { + if (ShutdownRequested()) //break loop on shutdown request + return CAddrInfo(); + int i = 0; int nKBucket = RandomInt(ADDRMAN_TRIED_BUCKET_COUNT); int nKBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE); @@ -472,14 +501,27 @@ CAddrInfo CAddrMan::Select_(bool newOnly) int nId = vvTried[nKBucket][nKBucketPos]; assert(mapInfo.count(nId) == 1); CAddrInfo& info = mapInfo[nId]; - if (RandomInt(1 << 30) < fChanceFactor * info.GetChance() * (1 << 30)) + if (info.IsReachableNetwork()) { + //deprioritize unreachable networks + fReachableFactor = 0.25; + } + if (info.IsJustTried()) { + //deprioritize entries just tried + fJustTried = 0.10; + } + if (RandomInt(1 << 30) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * (1 << 30)) return info; fChanceFactor *= 1.2; } } else { // use a new node double fChanceFactor = 1.0; + double fReachableFactor = 1.0; + double fJustTried = 1.0; while (1) { + if (ShutdownRequested()) //break loop on shutdown request + return CAddrInfo(); + int i = 0; int nUBucket = RandomInt(ADDRMAN_NEW_BUCKET_COUNT); int nUBucketPos = RandomInt(ADDRMAN_BUCKET_SIZE); @@ -494,12 +536,20 @@ CAddrInfo CAddrMan::Select_(bool newOnly) int nId = vvNew[nUBucket][nUBucketPos]; assert(mapInfo.count(nId) == 1); CAddrInfo& info = mapInfo[nId]; - if (RandomInt(1 << 30) < fChanceFactor * info.GetChance() * (1 << 30)) + if (info.IsReachableNetwork()) { + //deprioritize unreachable networks + fReachableFactor = 0.25; + } + if (info.IsJustTried()) { + //deprioritize entries just tried + fJustTried = 0.10; + } + if (RandomInt(1 << 30) < fChanceFactor * fReachableFactor * fJustTried * info.GetChance() * (1 << 30)) return info; fChanceFactor *= 1.2; } } - + return CAddrInfo(); } @@ -581,24 +631,59 @@ int CAddrMan::Check_() } #endif -void CAddrMan::GetAddr_(std::vector& vAddr) +void CAddrMan::GetAddr_(std::vector& vAddr, bool wants_addrv2) { unsigned int nNodes = ADDRMAN_GETADDR_MAX_PCT * vRandom.size() / 100; if (nNodes > ADDRMAN_GETADDR_MAX) nNodes = ADDRMAN_GETADDR_MAX; + int addrv2Nodes = nNodes/5; + int ipv4Nodes = 0; + int ipv6Nodes = 0; + int torNodes = 0; + int i2pNodes = 0; + int cjdnsNodes = 0; + + // Randomize Nodes + for (unsigned int n = 0; n < vRandom.size(); n++) { + int nRndPos = RandomInt(vRandom.size() - n) + n; + SwapRandom(n, nRndPos); + } + // gather a list of random nodes, skipping those of low quality for (unsigned int n = 0; n < vRandom.size(); n++) { if (vAddr.size() >= nNodes) break; - int nRndPos = RandomInt(vRandom.size() - n) + n; - SwapRandom(n, nRndPos); assert(mapInfo.count(vRandom[n]) == 1); - const CAddrInfo& ai = mapInfo[vRandom[n]]; - if (!ai.IsTerrible()) - vAddr.push_back(ai); + + if (!ai.IsTerrible()) { + if (!wants_addrv2) { + vAddr.push_back(ai); + } else { + if (ai.IsIPv4() && ipv4Nodes <= addrv2Nodes) { + vAddr.push_back(ai); + ipv4Nodes++; + } + if (ai.IsIPv6() && ipv6Nodes <= addrv2Nodes) { + vAddr.push_back(ai); + ipv6Nodes++; + } + if (ai.IsCJDNS() && cjdnsNodes <= addrv2Nodes) { + vAddr.push_back(ai); + cjdnsNodes++; + } + if (ai.IsTor() && torNodes <= addrv2Nodes) { + vAddr.push_back(ai); + torNodes++; + } + if (ai.IsI2P() && i2pNodes <= addrv2Nodes) { + vAddr.push_back(ai); + i2pNodes++; + } + } + } } } @@ -622,10 +707,36 @@ void CAddrMan::Connected_(const CService& addr, int64_t nTime) info.nTime = nTime; } +void CAddrMan::SetLocal_(const CService& addr) +{ + CAddrInfo* pinfo = Find(addr); + + // if not found, bail out + if (!pinfo) + return; + + CAddrInfo& info = *pinfo; + + // check whether we are talking about the exact same CService (including same port) + if (info != addr) + return; + + // update info + info.fLocal = true; +} + int CAddrMan::RandomInt(int nMax){ return GetRandInt(nMax); } +void CAddrMan::GetAllPeers(std::map &info) { + + for(std::map::iterator it = mapInfo.begin(); it != mapInfo.end(); it++) { + info[(*it).second.ToStringIPPort()] = (*it).second.GetLastSuccess(); + } + return; +} + std::vector CAddrMan::DecodeAsmap(fs::path path) { std::vector bits; diff --git a/src/addrman.h b/src/addrman.h index 5738913d4..ab425d40e 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -24,6 +24,7 @@ #include "protocol.h" #include "random.h" #include "sync.h" +#include "streams.h" #include "timedata.h" #include "util.h" #include "fs.h" @@ -64,18 +65,17 @@ private: //! position in vRandom int nRandomPos; + //! Address is local + bool fLocal; + friend class CAddrMan; public: - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(*(CAddress*)this); - READWRITE(source); - READWRITE(nLastSuccess); - READWRITE(nAttempts); + SERIALIZE_METHODS(CAddrInfo, obj) + { + READWRITEAS(CAddress, obj); + READ_WRITE(obj.source, obj.nLastSuccess, obj.nAttempts); } void Init() @@ -86,6 +86,7 @@ public: nRefCount = 0; fInTried = false; nRandomPos = -1; + fLocal = false; } CAddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource) @@ -116,9 +117,15 @@ public: //! Determine whether the statistics about this entry are bad enough so that it can just be deleted bool IsTerrible(int64_t nNow = GetTime()) const; + //Determine if this entry was just tried + bool IsJustTried(int64_t nNow = GetTime()) const; + //! Calculate the relative chance this entry should be given when selecting nodes to connect to double GetChance(int64_t nNow = GetTime()) const; + //Returns the last successful connection + int64_t GetLastSuccess() {return nTime;} + }; /** Stochastic address manager @@ -199,8 +206,30 @@ private: //! critical section to protect the inner data structures mutable CCriticalSection cs; + //! Serialization versions. + enum Format : uint8_t { + V0_HISTORICAL = 0, //!< historic format, before commit e6b343d88 + V1_DETERMINISTIC = 1, //!< for pre-asmap files + V2_ASMAP = 2, //!< for files including asmap version + V3_BIP155 = 3, //!< same as V2_ASMAP plus addresses are in BIP155 format + }; + + //! The maximum format this software knows it can unserialize. Also, we always serialize + //! in this format. + //! The format (first byte in the serialized stream) can be higher than this and + //! still this software may be able to unserialize the file - if the second byte + //! (see `lowest_compatible` in `Unserialize()`) is less or equal to this. + static constexpr Format FILE_FORMAT = Format::V3_BIP155; + + //! The initial value of a field that is incremented every time an incompatible format + //! change is made (such that old software versions would not be able to parse and + //! understand the new file format). This is 32 because we overtook the "key size" + //! field which was 32 historically. + //! @note Don't increment this. Increment `lowest_compatible` in `Serialize()` instead. + static constexpr uint8_t INCOMPATIBILITY_BASE = 32; + //! last used nId - int nIdCount; + int nIdCount GUARDED_BY(cs){0}; //! table with information about all nIds std::map mapInfo; @@ -280,12 +309,16 @@ protected: #endif //! Select several addresses at once. - void GetAddr_(std::vector &vAddr); + void GetAddr_(std::vector &vAddr, bool wants_addrv2); //! Mark an entry as currently-connected-to. void Connected_(const CService &addr, int64_t nTime); + //! Mark an entry as local + void SetLocal_(const CService &addr); + public: + void GetAllPeers(std::map &info); // Compressed IP->ASN mapping, loaded from a file when a node starts. // Should be always empty if no file was provided. // This mapping is then used for bucketing nodes in Addrman. @@ -336,13 +369,22 @@ public: * very little in common. */ template - void Serialize(Stream &s) const + void Serialize(Stream &s_) const + EXCLUSIVE_LOCKS_REQUIRED(!cs) { LOCK(cs); - unsigned char nVersion = 2; - s << nVersion; - s << ((unsigned char)32); + // Always serialize in the latest version (FILE_FORMAT). + + OverrideStream s(&s_, s_.GetType(), s_.GetVersion() | ADDRV2_FORMAT); + + s << static_cast(FILE_FORMAT); + + // Increment `lowest_compatible` iff a newly introduced format is incompatible with + // the previous one. + static constexpr uint8_t lowest_compatible = Format::V3_BIP155; + s << static_cast(INCOMPATIBILITY_BASE + lowest_compatible); + s << nKey; s << nNew; s << nTried; @@ -393,22 +435,40 @@ public: } template - void Unserialize(Stream& s) + void Unserialize(Stream& s_) { LOCK(cs); - Clear(); - unsigned char nVersion; - s >> nVersion; - unsigned char nKeySize; - s >> nKeySize; - if (nKeySize != 32) throw std::ios_base::failure("Incorrect keysize in addrman deserialization"); + assert(vRandom.empty()); + + Format format; + s_ >> Using>(format); + + int stream_version = s_.GetVersion(); + if (format >= Format::V3_BIP155) { + // Add ADDRV2_FORMAT to the version so that the CNetAddr and CAddress + // unserialize methods know that an address in addrv2 format is coming. + stream_version |= ADDRV2_FORMAT; + } + + OverrideStream s(&s_, s_.GetType(), stream_version); + + uint8_t compat; + s >> compat; + const uint8_t lowest_compatible = compat - INCOMPATIBILITY_BASE; + if (lowest_compatible > FILE_FORMAT) { + throw std::ios_base::failure(strprintf( + "Unsupported format of addrman database: %u. It is compatible with formats >=%u, " + "but the maximum supported by this version of %s is %u.", + format, lowest_compatible, PACKAGE_NAME, static_cast(FILE_FORMAT))); + } + s >> nKey; s >> nNew; s >> nTried; int nUBuckets = 0; s >> nUBuckets; - if (nVersion != 0) { + if (format >= Format::V1_DETERMINISTIC) { nUBuckets ^= (1 << 30); } @@ -422,7 +482,7 @@ public: // Deserialize entries from the new table. for (int n = 0; n < nNew; n++) { - CAddrInfo &info = mapInfo[n]; + CAddrInfo& info = mapInfo[n]; s >> info; mapAddr[info] = n; info.nRandomPos = vRandom.size(); @@ -437,7 +497,7 @@ public: s >> info; int nKBucket = info.GetTriedBucket(nKey, m_asmap); int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); - if (vvTried[nKBucket][nKBucketPos] == -1) { + if (info.IsValid() && vvTried[nKBucket][nKBucketPos] == -1) { info.nRandomPos = vRandom.size(); info.fInTried = true; vRandom.push_back(nIdCount); @@ -452,60 +512,84 @@ public: nTried -= nLost; // Store positions in the new table buckets to apply later (if possible). - std::map entryToBucket; // Represents which entry belonged to which bucket when serializing - - for (int bucket = 0; bucket < nUBuckets; bucket++) { - int nSize = 0; - s >> nSize; - for (int n = 0; n < nSize; n++) { - int nIndex = 0; - s >> nIndex; - if (nIndex >= 0 && nIndex < nNew) { - entryToBucket[nIndex] = bucket; + // An entry may appear in up to ADDRMAN_NEW_BUCKETS_PER_ADDRESS buckets, + // so we store all bucket-entry_index pairs to iterate through later. + std::vector> bucket_entries; + + for (int bucket = 0; bucket < nUBuckets; ++bucket) { + int num_entries{0}; + s >> num_entries; + for (int n = 0; n < num_entries; ++n) { + int entry_index{0}; + s >> entry_index; + if (entry_index >= 0 && entry_index < nNew) { + bucket_entries.emplace_back(bucket, entry_index); } } } - uint256 supplied_asmap_version; + // If the bucket count and asmap checksum haven't changed, then attempt + // to restore the entries to the buckets/positions they were in before + // serialization. + uint256 supplied_asmap_checksum; if (m_asmap.size() != 0) { - supplied_asmap_version = SerializeHash(m_asmap); + supplied_asmap_checksum = SerializeHash(m_asmap); } - uint256 serialized_asmap_version; - if (nVersion > 1) { - s >> serialized_asmap_version; + uint256 serialized_asmap_checksum; + if (format >= Format::V2_ASMAP) { + s >> serialized_asmap_checksum; } + const bool restore_bucketing{nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && + serialized_asmap_checksum == supplied_asmap_checksum}; - for (int n = 0; n < nNew; n++) { - CAddrInfo &info = mapInfo[n]; - int bucket = entryToBucket[n]; - int nUBucketPos = info.GetBucketPosition(nKey, true, bucket); - if (nVersion == 2 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && - info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS && serialized_asmap_version == supplied_asmap_version) { - // Bucketing has not changed, using existing bucket positions for the new table - vvNew[bucket][nUBucketPos] = n; - info.nRefCount++; - } else { - // In case the new table data cannot be used (nVersion unknown, bucket count wrong or new asmap), - // try to give them a reference based on their primary source address. - LogPrint("addrman", "Bucketing method was updated, re-bucketing addrman entries from disk\n"); - bucket = info.GetNewBucket(nKey, m_asmap); - nUBucketPos = info.GetBucketPosition(nKey, true, bucket); - if (vvNew[bucket][nUBucketPos] == -1) { - vvNew[bucket][nUBucketPos] = n; - info.nRefCount++; + if (!restore_bucketing) { + LogPrint("addrman", "Bucketing method was updated, re-bucketing addrman entries from disk\n"); + } + + for (auto bucket_entry : bucket_entries) { + int bucket{bucket_entry.first}; + const int entry_index{bucket_entry.second}; + // CAddrInfo& info = mapInfo[entry_index]; + + const auto it{mapInfo.find(entry_index)}; + if (it != mapInfo.end()) { + CAddrInfo& info = (*it).second; + + // Don't store the entry in the new bucket if it's not a valid address for our addrman + if (!info.IsValid()) continue; + + // The entry shouldn't appear in more than + // ADDRMAN_NEW_BUCKETS_PER_ADDRESS. If it has already, just skip + // this bucket_entry. + if (info.nRefCount >= ADDRMAN_NEW_BUCKETS_PER_ADDRESS) continue; + + int bucket_position = info.GetBucketPosition(nKey, true, bucket); + if (restore_bucketing && vvNew[bucket][bucket_position] == -1) { + // Bucketing has not changed, using existing bucket positions for the new table + vvNew[bucket][bucket_position] = entry_index; + ++info.nRefCount; + } else { + // In case the new table data cannot be used (bucket count wrong or new asmap), + // try to give them a reference based on their primary source address. + bucket = info.GetNewBucket(nKey, m_asmap); + bucket_position = info.GetBucketPosition(nKey, true, bucket); + if (vvNew[bucket][bucket_position] == -1) { + vvNew[bucket][bucket_position] = entry_index; + ++info.nRefCount; + } } } } // Prune new entries with refcount 0 (as a result of collisions). int nLostUnk = 0; - for (std::map::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ) { + for (auto it = mapInfo.cbegin(); it != mapInfo.cend(); ) { if (it->second.fInTried == false && it->second.nRefCount == 0) { - std::map::const_iterator itCopy = it++; + const auto itCopy = it++; Delete(itCopy->first); - nLostUnk++; + ++nLostUnk; } else { - it++; + ++it; } } if (nLost + nLostUnk > 0) { @@ -531,7 +615,6 @@ public: } } - nIdCount = 0; nTried = 0; nNew = 0; mapInfo.clear(); @@ -657,13 +740,13 @@ public: } //! Return a bunch of addresses, selected at random. - std::vector GetAddr() + std::vector GetAddr(bool wants_addrv2 = false) { Check(); std::vector vAddr; { LOCK(cs); - GetAddr_(vAddr); + GetAddr_(vAddr, wants_addrv2); } Check(); return vAddr; @@ -680,6 +763,17 @@ public: } } + //! Mark an entry as currently-connected-to. + void SetLocal(const CService &addr) + { + { + LOCK(cs); + Check(); + SetLocal_(addr); + Check(); + } + } + }; #endif // HUSH_ADDRMAN_H diff --git a/src/arith_uint256.cpp b/src/arith_uint256.cpp index da2dcd58b..af49e0033 100644 --- a/src/arith_uint256.cpp +++ b/src/arith_uint256.cpp @@ -19,7 +19,7 @@ ******************************************************************************/ #include "arith_uint256.h" #include "uint256.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include "crypto/common.h" #include #include diff --git a/src/attributes.h b/src/attributes.h new file mode 100644 index 000000000..a0afeb2f6 --- /dev/null +++ b/src/attributes.h @@ -0,0 +1,20 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2020 The Bitcoin Core developers +// Copyright (c) 2016-2022 The Hush developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html + +#ifndef HUSH_ATTRIBUTES_H +#define HUSH_ATTRIBUTES_H + +#if defined(__clang__) +# if __has_attribute(lifetimebound) +# define LIFETIMEBOUND [[clang::lifetimebound]] +# else +# define LIFETIMEBOUND +# endif +#else +# define LIFETIMEBOUND +#endif + +#endif // HUSH_ATTRIBUTES_H diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index d6ac78c25..669a095ea 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -22,7 +22,7 @@ #include "rpc/client.h" #include "rpc/protocol.h" #include "util.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include #include #include diff --git a/src/cc/Makefile b/src/cc/Makefile index 3e988f279..24b94226a 100644 --- a/src/cc/Makefile +++ b/src/cc/Makefile @@ -2,9 +2,10 @@ SHELL = /bin/sh CC = gcc CC_DARWIN = g++-6 CC_WIN = x86_64-w64-mingw32-gcc-posix -CFLAGS_DARWIN = -std=c++11 -arch x86_64 -I/usr/local/Cellar/gcc\@6/6.4.0_2/include/c++/6.4.0/ -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../cryptoconditions/include -I../cryptoconditions/src -I../cryptoconditions/src/asn -I.. -I. -fPIC -c -Wl,-undefined -Wl,dynamic_lookup -dynamiclib -CFLAGS = -std=c++11 -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../cryptoconditions/include -I../cryptoconditions/src -I../cryptoconditions/src/asn -I.. -I. -fPIC -shared -c -CFLAGS_WIN = -std=c++11 -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../cryptoconditions/include -I../cryptoconditions/src -I../cryptoconditions/src/asn -I.. -I. -fPIC -shared -c +CFLAGS = -arch x86_64 +CXXFLAGS_DARWIN = -std=c++11 -arch x86_64 -I/usr/local/Cellar/gcc\@6/6.4.0_2/include/c++/6.4.0/ -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../cryptoconditions/include -I../cryptoconditions/src -I../cryptoconditions/src/asn -I.. -I. -fPIC -c -Wl,-undefined -Wl,dynamic_lookup -dynamiclib +CXXFLAGS = -std=c++11 -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../cryptoconditions/include -I../cryptoconditions/src -I../cryptoconditions/src/asn -I.. -I. -fPIC -shared -c +CXXFLAGS_WIN = -std=c++11 -I../../depends/$(shell echo `../..//depends/config.guess`/include) -I../univalue/include -I../cryptoconditions/include -I../cryptoconditions/src -I../cryptoconditions/src/asn -I.. -I. -fPIC -shared -c DEBUGFLAGS = -O0 -D _DEBUG RELEASEFLAGS = -O2 -D NDEBUG -combine -fwhole-program $(info $(OS)) @@ -13,21 +14,27 @@ $(info $(OS)) TARGET = ../libcc.so TARGET_DARWIN = ../libcc.dylib TARGET_WIN = ../libcc.dll -SOURCES = cclib.cpp -#HEADERS = $(shell echo ../cryptoconditions/include/*.h) +SOURCES = cclib.cpp ../cJSON.c +OBJS = cclib.o ../cJSON.o all: $(TARGET) -$(TARGET): $(SOURCES) +%.o: %.c + $(CC) -o $@ $< $(CFLAGS) $(DEBUGFLAGS) + +%.o: %.cpp + $(CC) -o $@ $< $(CXXFLAGS) $(DEBUGFLAGS) + +$(TARGET): $(OBJS) $(info Building cclib to src/) ifeq ($(OS),Darwin) - $(CC_DARWIN) $(CFLAGS_DARWIN) $(DEBUGFLAGS) -o $(TARGET_DARWIN) $(SOURCES) + $(CC_DARWIN) $(CXXFLAGS_DARWIN) $(DEBUGFLAGS) -o $(TARGET_DARWIN) $(OBJS) else ifeq ($(OS),Linux) - $(CC) $(CFLAGS) $(DEBUGFLAGS) -o $(TARGET) $(SOURCES) + $(CC) $(CXXFLAGS) $(DEBUGFLAGS) -o $(TARGET) $(OBJS) #else ifeq ($(WIN_HOST),True) - todo: pass ENV var from build.sh if WIN host else $(info WINDOWS) - $(CC_WIN) $(CFLAGS_WIN) $(DEBUGFLAGS) -o $(TARGET_WIN) $(SOURCES) + $(CC_WIN) $(CXXFLAGS_WIN) $(DEBUGFLAGS) -o $(TARGET_WIN) $(OBJS) endif clean: diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 6a7ca1ed1..b5a805607 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -24,7 +24,7 @@ #include "main.h" #include "crypto/equihash.h" #include "util.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include #include #include "chainparamsseeds.h" @@ -205,7 +205,7 @@ public: bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivks"; bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-main"; - vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); + vFixedSeeds = std::vector(std::begin(chainparams_seed_main), std::end(chainparams_seed_main)); fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; @@ -313,7 +313,7 @@ public: bech32HRPs[SAPLING_INCOMING_VIEWING_KEY] = "zivktestsapling"; bech32HRPs[SAPLING_EXTENDED_SPEND_KEY] = "secret-extended-key-test"; - vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); + vFixedSeeds = std::vector(std::begin(chainparams_seed_test), std::end(chainparams_seed_test)); //fRequireRPCPassword = true; fMiningRequiresPeers = false;//true; diff --git a/src/chainparams.h b/src/chainparams.h index e32a965ba..3813ed983 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -36,11 +36,6 @@ struct CDNSSeedData { CDNSSeedData(const std::string &strName, const std::string &strHost) : name(strName), host(strHost) {} }; -struct SeedSpec6 { - uint8_t addr[16]; - uint16_t port; -}; - typedef std::map MapCheckpoints; @@ -108,7 +103,7 @@ public: const std::vector& DNSSeeds() const { return vSeeds; } const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::string& Bech32HRP(Bech32Type type) const { return bech32HRPs[type]; } - const std::vector& FixedSeeds() const { return vFixedSeeds; } + const std::vector& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } /** Return the founder's reward address and script for a given block height */ std::string GetFoundersRewardAddressAtHeight(int height) const; @@ -146,7 +141,7 @@ protected: std::string strCurrencyUnits; uint32_t bip44CoinType; CBlock genesis; - std::vector vFixedSeeds; + std::vector vFixedSeeds; bool fMiningRequiresPeers = false; bool fDefaultConsistencyChecks = false; bool fRequireStandard = false; diff --git a/src/chainparamsseeds.h b/src/chainparamsseeds.h index 663a9bcac..887b3d602 100644 --- a/src/chainparamsseeds.h +++ b/src/chainparamsseeds.h @@ -1,37 +1,20 @@ // Copyright (c) 2016-2022 The Hush developers -/****************************************************************************** - * Copyright © 2014-2019 The SuperNET Developers. * - * * - * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * - * the top-level directory of this distribution for the individual copyright * - * holder information and the developer policies on copyright and licensing. * - * * - * Unless otherwise agreed in a custom licensing agreement, no part of the * - * SuperNET software, including this file may be copied, modified, propagated * - * or distributed except according to the terms contained in the LICENSE file * - * * - * Removal or modification of this copyright notice is prohibited. * - * * - ******************************************************************************/ - +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html +// THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY +// Instead, run: ./contrib/seeds/generate-seeds.py contrib/seeds #ifndef HUSH_CHAINPARAMSSEEDS_H #define HUSH_CHAINPARAMSSEEDS_H -/** - * List of fixed seed nodes for the bitcoin network - * AUTOGENERATED by contrib/seeds/generate-seeds.py - * - * Each line contains a 16-byte IPv6 address and a port. - * IPv4 as well as onion addresses are wrapped inside a IPv6 address accordingly. - */ -static SeedSpec6 pnSeed6_main[] = { - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x19,0x30,0xec}, 27485}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x19,0x30,0xec}, 27487}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x40,0x69,0x6f}, 27485}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x40,0x69,0x6f}, 27487}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x19,0x30,0x48}, 27485}, - {{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xb9,0x19,0x30,0x48}, 27487} +// List of fixed seed nodes for the Hush network +// Each line contains a BIP155 serialized address. +// +static const uint8_t chainparams_seed_main[] = { + 0x01,0x04,0xb9,0xf1,0x3d,0x2b,0x00,0x00, // 185.241.61.43 + 0x01,0x04,0x89,0x4a,0x04,0xc6,0x00,0x00, // 137.74.4.198 + 0x01,0x04,0x95,0x1c,0x66,0xdb,0x00,0x00, // 149.28.102.219 }; -static SeedSpec6 pnSeed6_test[] = { +static const uint8_t chainparams_seed_test[] = { + 0x01,0x04,0x01,0x02,0x03,0x04,0x00,0x00, // 1.2.3.4 }; #endif // HUSH_CHAINPARAMSSEEDS_H diff --git a/src/coins.cpp b/src/coins.cpp index 57e692444..5117bbd66 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -24,6 +24,9 @@ #include "hush_defs.h" #include "importcoin.h" #include +#include "util.h" +extern bool fZdebug; + /** * calculate number of bytes for the bitmask, and its number of non-zero bytes * each bit in the bitmask represents the availability of one output, but the diff --git a/src/compat.h b/src/compat.h index 045774e27..49eaaebae 100644 --- a/src/compat.h +++ b/src/compat.h @@ -78,6 +78,8 @@ typedef u_int SOCKET; #define SOCKET_ERROR -1 #endif +#define WSAEAGAIN EAGAIN + #ifdef _WIN32 #ifndef S_IRUSR #define S_IRUSR 0400 @@ -109,8 +111,14 @@ typedef u_int SOCKET; size_t strnlen( const char *start, size_t max_len); #endif // HAVE_DECL_STRNLEN -bool static inline IsSelectableSocket(SOCKET s) { -#ifdef _WIN32 +#ifndef WIN32 +typedef void* sockopt_arg_type; +#else +typedef char* sockopt_arg_type; +#endif + +bool static inline IsSelectableSocket(const SOCKET& s) { +#ifdef WIN32 return true; #else return (s < FD_SETSIZE); diff --git a/src/core_read.cpp b/src/core_read.cpp index 3e01a37e3..bd3be6dd0 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -27,7 +27,7 @@ #include "streams.h" #include #include "util.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include "version.h" #include diff --git a/src/core_write.cpp b/src/core_write.cpp index 8b890db21..c2932da01 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -29,7 +29,7 @@ #include #include "util.h" #include "utilmoneystr.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include #include diff --git a/src/crypto/common.h b/src/crypto/common.h index 2b6be0901..0ce6ebb40 100644 --- a/src/crypto/common.h +++ b/src/crypto/common.h @@ -62,6 +62,13 @@ void static inline WriteLE64(unsigned char* ptr, uint64_t x) memcpy(ptr, (char*)&v, 8); } +uint16_t static inline ReadBE16(const unsigned char* ptr) +{ + uint16_t x; + memcpy((char*)&x, ptr, 2); + return be16toh(x); +} + uint32_t static inline ReadBE32(const unsigned char* ptr) { uint32_t x; diff --git a/src/crypto/equihash.h b/src/crypto/equihash.h index 23530cc61..38a4946e1 100644 --- a/src/crypto/equihash.h +++ b/src/crypto/equihash.h @@ -8,7 +8,7 @@ #define HUSH_EQUIHASH_H #include "crypto/sha256.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include "sodium.h" #include "hush_nk.h" #include diff --git a/src/crypto/sha3.cpp b/src/crypto/sha3.cpp new file mode 100644 index 000000000..ad3b74f34 --- /dev/null +++ b/src/crypto/sha3.cpp @@ -0,0 +1,162 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Copyright (c) 2016-2022 The Hush developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html + +// Based on https://github.com/mjosaarinen/tiny_sha3/blob/master/sha3.c +// by Markku-Juhani O. Saarinen + +#include "crypto/sha3.h" +#include "crypto/common.h" +#include "span.h" + +#include +#include // For std::begin and std::end. + +#include + +// Internal implementation code. +namespace +{ +uint64_t Rotl(uint64_t x, int n) { return (x << n) | (x >> (64 - n)); } +} // namespace + +void KeccakF(uint64_t (&st)[25]) +{ + static constexpr uint64_t RNDC[24] = { + 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, + 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, + 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, + 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, + 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, + 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 + }; + static constexpr int ROUNDS = 24; + + for (int round = 0; round < ROUNDS; ++round) { + uint64_t bc0, bc1, bc2, bc3, bc4, t; + + // Theta + bc0 = st[0] ^ st[5] ^ st[10] ^ st[15] ^ st[20]; + bc1 = st[1] ^ st[6] ^ st[11] ^ st[16] ^ st[21]; + bc2 = st[2] ^ st[7] ^ st[12] ^ st[17] ^ st[22]; + bc3 = st[3] ^ st[8] ^ st[13] ^ st[18] ^ st[23]; + bc4 = st[4] ^ st[9] ^ st[14] ^ st[19] ^ st[24]; + t = bc4 ^ Rotl(bc1, 1); st[0] ^= t; st[5] ^= t; st[10] ^= t; st[15] ^= t; st[20] ^= t; + t = bc0 ^ Rotl(bc2, 1); st[1] ^= t; st[6] ^= t; st[11] ^= t; st[16] ^= t; st[21] ^= t; + t = bc1 ^ Rotl(bc3, 1); st[2] ^= t; st[7] ^= t; st[12] ^= t; st[17] ^= t; st[22] ^= t; + t = bc2 ^ Rotl(bc4, 1); st[3] ^= t; st[8] ^= t; st[13] ^= t; st[18] ^= t; st[23] ^= t; + t = bc3 ^ Rotl(bc0, 1); st[4] ^= t; st[9] ^= t; st[14] ^= t; st[19] ^= t; st[24] ^= t; + + // Rho Pi + t = st[1]; + bc0 = st[10]; st[10] = Rotl(t, 1); t = bc0; + bc0 = st[7]; st[7] = Rotl(t, 3); t = bc0; + bc0 = st[11]; st[11] = Rotl(t, 6); t = bc0; + bc0 = st[17]; st[17] = Rotl(t, 10); t = bc0; + bc0 = st[18]; st[18] = Rotl(t, 15); t = bc0; + bc0 = st[3]; st[3] = Rotl(t, 21); t = bc0; + bc0 = st[5]; st[5] = Rotl(t, 28); t = bc0; + bc0 = st[16]; st[16] = Rotl(t, 36); t = bc0; + bc0 = st[8]; st[8] = Rotl(t, 45); t = bc0; + bc0 = st[21]; st[21] = Rotl(t, 55); t = bc0; + bc0 = st[24]; st[24] = Rotl(t, 2); t = bc0; + bc0 = st[4]; st[4] = Rotl(t, 14); t = bc0; + bc0 = st[15]; st[15] = Rotl(t, 27); t = bc0; + bc0 = st[23]; st[23] = Rotl(t, 41); t = bc0; + bc0 = st[19]; st[19] = Rotl(t, 56); t = bc0; + bc0 = st[13]; st[13] = Rotl(t, 8); t = bc0; + bc0 = st[12]; st[12] = Rotl(t, 25); t = bc0; + bc0 = st[2]; st[2] = Rotl(t, 43); t = bc0; + bc0 = st[20]; st[20] = Rotl(t, 62); t = bc0; + bc0 = st[14]; st[14] = Rotl(t, 18); t = bc0; + bc0 = st[22]; st[22] = Rotl(t, 39); t = bc0; + bc0 = st[9]; st[9] = Rotl(t, 61); t = bc0; + bc0 = st[6]; st[6] = Rotl(t, 20); t = bc0; + st[1] = Rotl(t, 44); + + // Chi Iota + bc0 = st[0]; bc1 = st[1]; bc2 = st[2]; bc3 = st[3]; bc4 = st[4]; + st[0] = bc0 ^ (~bc1 & bc2) ^ RNDC[round]; + st[1] = bc1 ^ (~bc2 & bc3); + st[2] = bc2 ^ (~bc3 & bc4); + st[3] = bc3 ^ (~bc4 & bc0); + st[4] = bc4 ^ (~bc0 & bc1); + bc0 = st[5]; bc1 = st[6]; bc2 = st[7]; bc3 = st[8]; bc4 = st[9]; + st[5] = bc0 ^ (~bc1 & bc2); + st[6] = bc1 ^ (~bc2 & bc3); + st[7] = bc2 ^ (~bc3 & bc4); + st[8] = bc3 ^ (~bc4 & bc0); + st[9] = bc4 ^ (~bc0 & bc1); + bc0 = st[10]; bc1 = st[11]; bc2 = st[12]; bc3 = st[13]; bc4 = st[14]; + st[10] = bc0 ^ (~bc1 & bc2); + st[11] = bc1 ^ (~bc2 & bc3); + st[12] = bc2 ^ (~bc3 & bc4); + st[13] = bc3 ^ (~bc4 & bc0); + st[14] = bc4 ^ (~bc0 & bc1); + bc0 = st[15]; bc1 = st[16]; bc2 = st[17]; bc3 = st[18]; bc4 = st[19]; + st[15] = bc0 ^ (~bc1 & bc2); + st[16] = bc1 ^ (~bc2 & bc3); + st[17] = bc2 ^ (~bc3 & bc4); + st[18] = bc3 ^ (~bc4 & bc0); + st[19] = bc4 ^ (~bc0 & bc1); + bc0 = st[20]; bc1 = st[21]; bc2 = st[22]; bc3 = st[23]; bc4 = st[24]; + st[20] = bc0 ^ (~bc1 & bc2); + st[21] = bc1 ^ (~bc2 & bc3); + st[22] = bc2 ^ (~bc3 & bc4); + st[23] = bc3 ^ (~bc4 & bc0); + st[24] = bc4 ^ (~bc0 & bc1); + } +} + +SHA3_256_& SHA3_256_::Write(Span data) +{ + if (m_bufsize && m_bufsize + data.size() >= sizeof(m_buffer)) { + // Fill the buffer and process it. + std::copy(data.begin(), data.begin() + sizeof(m_buffer) - m_bufsize, m_buffer + m_bufsize); + data = data.subspan(sizeof(m_buffer) - m_bufsize); + m_state[m_pos++] ^= ReadLE64(m_buffer); + m_bufsize = 0; + if (m_pos == RATE_BUFFERS) { + KeccakF(m_state); + m_pos = 0; + } + } + while (data.size() >= sizeof(m_buffer)) { + // Process chunks directly from the buffer. + m_state[m_pos++] ^= ReadLE64(data.data()); + data = data.subspan(8); + if (m_pos == RATE_BUFFERS) { + KeccakF(m_state); + m_pos = 0; + } + } + if (data.size()) { + // Keep the remainder in the buffer. + std::copy(data.begin(), data.end(), m_buffer + m_bufsize); + m_bufsize += data.size(); + } + return *this; +} + +SHA3_256_& SHA3_256_::Finalize(Span output) +{ + assert(output.size() == OUTPUT_SIZE); + std::fill(m_buffer + m_bufsize, m_buffer + sizeof(m_buffer), 0); + m_buffer[m_bufsize] ^= 0x06; + m_state[m_pos] ^= ReadLE64(m_buffer); + m_state[RATE_BUFFERS - 1] ^= 0x8000000000000000; + KeccakF(m_state); + for (unsigned i = 0; i < 4; ++i) { + WriteLE64(output.data() + 8 * i, m_state[i]); + } + return *this; +} + +SHA3_256_& SHA3_256_::Reset() +{ + m_bufsize = 0; + m_pos = 0; + std::fill(std::begin(m_state), std::end(m_state), 0); + return *this; +} diff --git a/src/crypto/sha3.h b/src/crypto/sha3.h new file mode 100644 index 000000000..2cbd67e93 --- /dev/null +++ b/src/crypto/sha3.h @@ -0,0 +1,42 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Copyright (c) 2016-2022 The Hush developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html + +#ifndef HUSH_CRYPTO_SHA3_H +#define HUSH_CRYPTO_SHA3_H + +#include "span.h" + +#include +#include + +//! The Keccak-f[1600] transform. +void KeccakF(uint64_t (&st)[25]); + +class SHA3_256_ +{ +private: + uint64_t m_state[25] = {0}; + unsigned char m_buffer[8]; + unsigned m_bufsize = 0; + unsigned m_pos = 0; + + //! Sponge rate in bits. + static constexpr unsigned RATE_BITS = 1088; + + //! Sponge rate expressed as a multiple of the buffer size. + static constexpr unsigned RATE_BUFFERS = RATE_BITS / (8 * sizeof(m_buffer)); + + static_assert(RATE_BITS % (8 * sizeof(m_buffer)) == 0, "Rate must be a multiple of 8 bytes"); + +public: + static constexpr size_t OUTPUT_SIZE = 32; + + SHA3_256_() {} + SHA3_256_& Write(Span data); + SHA3_256_& Finalize(Span output); + SHA3_256_& Reset(); +}; + +#endif // HUSH_CRYPTO_SHA3_H diff --git a/src/gtest/json_test_vectors.h b/src/gtest/json_test_vectors.h index 82b7fda66..29049e50c 100644 --- a/src/gtest/json_test_vectors.h +++ b/src/gtest/json_test_vectors.h @@ -3,7 +3,7 @@ // file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html #include -#include "utilstrencodings.h" +#include "util/strencodings.h" #include "version.h" #include "serialize.h" #include "streams.h" diff --git a/src/gtest/test_deprecation.cpp b/src/gtest/test_deprecation.cpp index af546b860..46ccc2595 100644 --- a/src/gtest/test_deprecation.cpp +++ b/src/gtest/test_deprecation.cpp @@ -11,7 +11,7 @@ #include "init.h" #include "ui_interface.h" #include "util.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include #include diff --git a/src/gtest/test_merkletree.cpp b/src/gtest/test_merkletree.cpp index 224d69a3e..2cebf223e 100644 --- a/src/gtest/test_merkletree.cpp +++ b/src/gtest/test_merkletree.cpp @@ -19,7 +19,7 @@ #include -#include "utilstrencodings.h" +#include "util/strencodings.h" #include "version.h" #include "serialize.h" #include "streams.h" diff --git a/src/gtest/test_rpc.cpp b/src/gtest/test_rpc.cpp index c64b7604c..f6ac48d82 100644 --- a/src/gtest/test_rpc.cpp +++ b/src/gtest/test_rpc.cpp @@ -10,7 +10,7 @@ #include "primitives/block.h" #include "rpc/server.h" #include "streams.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" extern UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false); diff --git a/src/gtest/test_txid.cpp b/src/gtest/test_txid.cpp index 6cf5dbd6c..dafc660b2 100644 --- a/src/gtest/test_txid.cpp +++ b/src/gtest/test_txid.cpp @@ -8,7 +8,7 @@ #include "streams.h" #include "uint256.h" #include "util.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" /* Test that removing #1144 succeeded by verifying the hash of a transaction is over the entire serialized form. diff --git a/src/hash.h b/src/hash.h index a37c00a1d..e71fc4888 100644 --- a/src/hash.h +++ b/src/hash.h @@ -28,9 +28,7 @@ #include "serialize.h" #include "uint256.h" #include "version.h" - #include "sodium.h" - #include typedef uint256 ChainCode; @@ -48,6 +46,18 @@ public: sha.Reset().Write(buf, sha.OUTPUT_SIZE).Finalize(hash); } + CHash256& Write(Span input) { + sha.Write(input.data(), input.size()); + return *this; + } + + void Finalize(Span output) { + assert(output.size() == OUTPUT_SIZE); + unsigned char buf[CSHA256::OUTPUT_SIZE]; + sha.Finalize(buf); + sha.Reset().Write(buf, CSHA256::OUTPUT_SIZE).Finalize(output.data()); + } + CHash256& Write(const unsigned char *data, size_t len) { sha.Write(data, len); return *this; @@ -120,6 +130,23 @@ inline uint256 Hash(const T1 p1begin, const T1 p1end, return result; } +/** Compute the 256-bit hash of an object. */ +template +inline uint256 Hash(const T& in1) +{ + uint256 result; + CHash256().Write(MakeUCharSpan(in1)).Finalize(result); + return result; +} + +/** Compute the 256-bit hash of the concatenation of two objects. */ +template +inline uint256 Hash(const T1& in1, const T2& in2) { + uint256 result; + CHash256().Write(MakeUCharSpan(in1)).Write(MakeUCharSpan(in2)).Finalize(result); + return result; +} + /** Compute the 160-bit hash an object. */ template inline uint160 Hash160(const T1 pbegin, const T1 pend) @@ -178,6 +205,40 @@ public: } }; +/** Reads data from an underlying stream, while hashing the read data. */ +template +class CHashVerifier : public CHashWriter +{ +private: + Source* source; + +public: + explicit CHashVerifier(Source* source_) : CHashWriter(source_->GetType(), source_->GetVersion()), source(source_) {} + + void read(char* pch, size_t nSize) + { + source->read(pch, nSize); + this->write(pch, nSize); + } + + void ignore(size_t nSize) + { + char data[1024]; + while (nSize > 0) { + size_t now = std::min(nSize, 1024); + read(data, now); + nSize -= now; + } + } + + template + CHashVerifier& operator>>(T&& obj) + { + // Unserialize from this stream + ::Unserialize(*this, obj); + return (*this); + } +}; /** A writer stream (for serialization) that computes a 256-bit BLAKE2b hash. */ class CBLAKE2bWriter @@ -221,6 +282,7 @@ public: } }; + /** Compute the 256-bit hash of an object's serialization. */ template uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION) @@ -230,6 +292,7 @@ uint256 SerializeHash(const T& obj, int nType=SER_GETHASH, int nVersion=PROTOCOL return ss.GetHash(); } + unsigned int MurmurHash3(unsigned int nHashSeed, const std::vector& vDataToHash); void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]); diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 5b70c3215..d9dc2ca5a 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -24,7 +24,7 @@ #include "random.h" #include "sync.h" #include "util.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include "ui_interface.h" #include // boost::trim diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 2f9761a93..adcb3778d 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -7,11 +7,13 @@ #include "chainparamsbase.h" #include "compat.h" #include "util.h" +#include "util/strencodings.h" #include "netbase.h" #include "rpc/protocol.h" // For HTTP status codes #include "sync.h" #include "ui_interface.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" + #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include #include @@ -157,6 +160,7 @@ public: boost::unique_lock lock(cs); return queue.size(); } + size_t MaxDepth() { boost::unique_lock lock(cs); @@ -167,6 +171,7 @@ public: boost::unique_lock lock(cs); return numThreads; } + }; struct HTTPPathHandler @@ -196,7 +201,6 @@ std::vector pathHandlers; //! Bound listening sockets std::vector boundSockets; - int getWorkQueueDepth() { return workQueue->Depth(); @@ -227,12 +231,17 @@ static bool ClientAllowed(const CNetAddr& netaddr) static bool InitHTTPAllowList() { rpc_allow_subnets.clear(); - rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet - rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost + CNetAddr localv4; + CNetAddr localv6; + LookupHost("127.0.0.1", localv4, false); + LookupHost("::1", localv6, false); + rpc_allow_subnets.push_back(CSubNet(localv4, 8)); // always allow IPv4 local subnet + rpc_allow_subnets.push_back(CSubNet(localv6)); // always allow IPv6 localhost if (mapMultiArgs.count("-rpcallowip")) { const std::vector& vAllow = mapMultiArgs["-rpcallowip"]; BOOST_FOREACH (std::string strAllow, vAllow) { - CSubNet subnet(strAllow); + CSubNet subnet; + LookupSubNet(strAllow.c_str(), subnet); if (!subnet.IsValid()) { uiInterface.ThreadSafeMessageBox( strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow), @@ -273,6 +282,16 @@ static std::string RequestMethodString(HTTPRequest::RequestMethod m) /** HTTP request callback */ static void http_request_cb(struct evhttp_request* req, void* arg) { + // Disable reading to work around a libevent bug, fixed in 2.2.0. + if (event_get_version_number() >= 0x02010600 && event_get_version_number() < 0x02020001) { + evhttp_connection* conn = evhttp_request_get_connection(req); + if (conn) { + bufferevent* bev = evhttp_connection_get_bufferevent(conn); + if (bev) { + bufferevent_disable(bev, EV_READ); + } + } + } std::unique_ptr hreq(new HTTPRequest(req)); // Early address-based allow check @@ -315,11 +334,10 @@ static void http_request_cb(struct evhttp_request* req, void* arg) if (i != iend) { std::unique_ptr item(new HTTPWorkItem(hreq.release(), path, i->handler)); assert(workQueue); - if (workQueue->Enqueue(item.get())) { + if (workQueue->Enqueue(item.get())) item.release(); /* if true, queue took ownership */ - } else { - item->req->WriteReply(HTTP_INTERNAL, strprintf("Work queue depth %d exceeded", workQueue->Depth() )); - } + else + item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded"); } else { hreq->WriteReply(HTTP_NOTFOUND); } @@ -541,7 +559,7 @@ struct event_base* EventBase() static void httpevent_callback_fn(evutil_socket_t, short, void* data) { // Static handler: simply call inner handler - HTTPEvent *self = ((HTTPEvent*)data); + HTTPEvent *self = static_cast(data); self->handler(); if (self->deleteWhenTriggered) delete self; @@ -628,8 +646,21 @@ void HTTPRequest::WriteReply(int nStatus, const std::string& strReply) struct evbuffer* evb = evhttp_request_get_output_buffer(req); assert(evb); evbuffer_add(evb, strReply.data(), strReply.size()); - HTTPEvent* ev = new HTTPEvent(eventBase, true, - boost::bind(evhttp_send_reply, req, nStatus, (const char*)NULL, (struct evbuffer *)NULL)); + auto req_copy = req; + HTTPEvent* ev = new HTTPEvent(eventBase, true, [req_copy, nStatus]{ + evhttp_send_reply(req_copy, nStatus, (const char*)NULL, (struct evbuffer *)NULL); + // Re-enable reading from the socket. This is the second part of the libevent + // workaround above. + if (event_get_version_number() >= 0x02010600 && event_get_version_number() < 0x02020001) { + evhttp_connection* conn = evhttp_request_get_connection(req_copy); + if (conn) { + bufferevent* bev = evhttp_connection_get_bufferevent(conn); + if (bev) { + bufferevent_enable(bev, EV_READ | EV_WRITE); + } + } + } + }); ev->trigger(0); replySent = true; req = 0; // transferred back to main thread @@ -644,7 +675,7 @@ CService HTTPRequest::GetPeer() const char* address = ""; uint16_t port = 0; evhttp_connection_get_peer(con, (char**)&address, &port); - peer = CService(address, port); + peer = LookupNumeric(address, port); } return peer; } diff --git a/src/hush-tx.cpp b/src/hush-tx.cpp index 6d3f2f94c..bb0d3dab7 100644 --- a/src/hush-tx.cpp +++ b/src/hush-tx.cpp @@ -29,7 +29,7 @@ #include #include "util.h" #include "utilmoneystr.h" -#include "utilstrencodings.h" +#include "util/strencodings.h" #include #include #include diff --git a/src/i2p.cpp b/src/i2p.cpp new file mode 100644 index 000000000..e8de66a81 --- /dev/null +++ b/src/i2p.cpp @@ -0,0 +1,441 @@ +// Copyright (c) 2020-2020 The Bitcoin Core developers +// Copyright (c) 2016-2022 The Hush developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace i2p { + +/** + * Swap Standard Base64 <-> I2P Base64. + * Standard Base64 uses `+` and `/` as last two characters of its alphabet. + * I2P Base64 uses `-` and `~` respectively. + * So it is easy to detect in which one is the input and convert to the other. + * @param[in] from Input to convert. + * @return converted `from` + */ +static std::string SwapBase64(const std::string& from) +{ + std::string to; + to.resize(from.size()); + for (size_t i = 0; i < from.size(); ++i) { + switch (from[i]) { + case '-': + to[i] = '+'; + break; + case '~': + to[i] = '/'; + break; + case '+': + to[i] = '-'; + break; + case '/': + to[i] = '~'; + break; + default: + to[i] = from[i]; + break; + } + } + return to; +} + +/** + * Decode an I2P-style Base64 string. + * @param[in] i2p_b64 I2P-style Base64 string. + * @return decoded `i2p_b64` + * @throw std::runtime_error if decoding fails + */ +static Binary DecodeI2PBase64(const std::string& i2p_b64) +{ + const std::string& std_b64 = SwapBase64(i2p_b64); + bool invalid; + Binary decoded = DecodeBase64(std_b64.c_str(), &invalid); + if (invalid) { + throw std::runtime_error(strprintf("Cannot decode Base64: \"%s\"", i2p_b64)); + } + return decoded; +} + +/** + * Derive the .b32.i2p address of an I2P destination (binary). + * @param[in] dest I2P destination. + * @return the address that corresponds to `dest` + * @throw std::runtime_error if conversion fails + */ +static CNetAddr DestBinToAddr(const Binary& dest) +{ + CSHA256 hasher; + hasher.Write(dest.data(), dest.size()); + unsigned char hash[CSHA256::OUTPUT_SIZE]; + hasher.Finalize(hash); + + CNetAddr addr; + const std::string addr_str = EncodeBase32(hash, false) + ".b32.i2p"; + if (!addr.SetSpecial(addr_str)) { + throw std::runtime_error(strprintf("Cannot parse I2P address: \"%s\"", addr_str)); + } + + return addr; +} + +/** + * Derive the .b32.i2p address of an I2P destination (I2P-style Base64). + * @param[in] dest I2P destination. + * @return the address that corresponds to `dest` + * @throw std::runtime_error if conversion fails + */ +static CNetAddr DestB64ToAddr(const std::string& dest) +{ + const Binary& decoded = DecodeI2PBase64(dest); + return DestBinToAddr(decoded); +} + +namespace sam { + +Session::Session(const fs::path& private_key_file, + const CService& control_host) + : m_private_key_file(private_key_file), m_control_host(control_host), + m_control_sock(std::unique_ptr(new Sock(INVALID_SOCKET))) +{ +} + +Session::~Session() +{ +} + +bool Session::Check() +{ + try { + LOCK(cs_i2p); + CreateIfNotCreatedAlready(); + return true; + } catch (const std::runtime_error& e) { + LogPrint("i2p","I2P: Error Checking Session: %s\n", e.what()); + CheckControlSock(); + } + return false; +} + +bool Session::Listen(Connection& conn) +{ + try { + LOCK(cs_i2p); + CreateIfNotCreatedAlready(); + conn.me = m_my_addr; + conn.sock = StreamAccept(); + return true; + } catch (const std::runtime_error& e) { + LogPrint("i2p","I2P: Error listening: %s\n", e.what()); + CheckControlSock(); + } + return false; +} + +bool Session::Accept(Connection& conn) +{ + try { + while (true) { + + // boost::this_thread::interruption_point(); + if (ShutdownRequested()) { + Disconnect(); + return false; + } + + + Sock::Event occurred; + if (!conn.sock->Wait(std::chrono::milliseconds{MAX_WAIT_FOR_IO}, Sock::RECV, &occurred)) { + throw std::runtime_error("wait on socket failed"); + } + + if ((occurred & Sock::RECV) == 0) { + // Timeout, no incoming connections within MAX_WAIT_FOR_IO. + continue; + } + + const std::string& peer_dest = + conn.sock->RecvUntilTerminator('\n', std::chrono::milliseconds{MAX_WAIT_FOR_IO}, MAX_MSG_SIZE); + + if (ShutdownRequested()) { + Disconnect(); + return false; + } + + conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort()); + return true; + } + } catch (const std::runtime_error& e) { + LogPrint("i2p","I2P: Error accepting: %s\n", e.what()); + CheckControlSock(); + } + return false; +} + +bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error) +{ + proxy_error = true; + + std::string session_id; + std::unique_ptr sock; + conn.peer = to; + + try { + { + LOCK(cs_i2p); + CreateIfNotCreatedAlready(); + session_id = m_session_id; + conn.me = m_my_addr; + sock = Hello(); + } + + const Reply& lookup_reply = + SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP())); + + const std::string& dest = lookup_reply.Get("VALUE"); + + const Reply& connect_reply = SendRequestAndGetReply( + *sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest), + false); + + const std::string& result = connect_reply.Get("RESULT"); + + if (result == "OK") { + conn.sock = std::move(sock); + return true; + } + + if (result == "INVALID_ID") { + LOCK(cs_i2p); + Disconnect(); + throw std::runtime_error("Invalid session id"); + } + + if (result == "CANT_REACH_PEER" || result == "TIMEOUT" || "KEY_NOT_FOUND") { + proxy_error = false; + } + + throw std::runtime_error(strprintf("\"%s\"", connect_reply.full)); + } catch (const std::runtime_error& e) { + LogPrint("i2p","I2P: Error connecting to %s: %s\n", to.ToString(), e.what()); + CheckControlSock(); + return false; + } +} + +// Private methods + +std::string Session::Reply::Get(const std::string& key) const +{ + const auto& pos = keys.find(key); + if (pos == keys.end() || !pos->second.has_value()) { + throw std::runtime_error( + strprintf("Missing %s= in the reply to \"%s\": \"%s\"", key, request, full)); + } + return pos->second.value(); +} + +Session::Reply Session::SendRequestAndGetReply(const Sock& sock, + const std::string& request, + bool check_result_ok) const +{ + sock.SendComplete(request + "\n", std::chrono::milliseconds{MAX_WAIT_FOR_IO}); + + Reply reply; + + // Don't log the full "SESSION CREATE ..." because it contains our private key. + reply.request = request.substr(0, 14) == "SESSION CREATE" ? "SESSION CREATE ..." : request; + + // It could take a few minutes for the I2P router to reply as it is querying the I2P network + // (when doing name lookup, for example). + + reply.full = sock.RecvUntilTerminator('\n', std::chrono::minutes{3}, MAX_MSG_SIZE); + + for (const auto& kv : spanparsing::Split(reply.full, ' ')) { + const auto& pos = std::find(kv.begin(), kv.end(), '='); + if (pos != kv.end()) { + reply.keys.emplace(std::string{kv.begin(), pos}, std::string{pos + 1, kv.end()}); + } else { + reply.keys.emplace(std::string{kv.begin(), kv.end()}, boost::none); + } + } + + LogPrint("i2p","I2P: Handshake reply %s\n", reply.full); + + if (check_result_ok && reply.Get("RESULT") != "OK") { + if (!ShutdownRequested()) { + throw std::runtime_error(strprintf("Unexpected reply to \"%s\": \"%s\"", request, reply.full)); + } + } + + return reply; +} + +std::unique_ptr Session::Hello() const +{ + auto sock = CreateSock(m_control_host); + + if (!sock) { + throw std::runtime_error("Cannot create socket"); + } + + if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout)) { + throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString())); + } + + SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1"); + + return sock; +} + +void Session::CheckControlSock() +{ + LOCK(cs_i2p); + + std::string errmsg; + if (!m_control_sock->IsConnected(errmsg)) { + LogPrint("i2p","I2P: Control socket error: %s\n", errmsg); + Disconnect(); + } +} + +void Session::DestGenerate(const Sock& sock) +{ + // https://geti2p.net/spec/common-structures#key-certificates + // "7" or "EdDSA_SHA512_Ed25519" - "Recent Router Identities and Destinations". + // Use "7" because i2pd <2.24.0 does not recognize the textual form. + const Reply& reply = SendRequestAndGetReply(sock, "DEST GENERATE SIGNATURE_TYPE=7", false); + + m_private_key = DecodeI2PBase64(reply.Get("PRIV")); +} + +void Session::GenerateAndSavePrivateKey(const Sock& sock) +{ + DestGenerate(sock); + + // umask is set to 077 in init.cpp, which is ok (unless -sysperms is given) + if (!WriteBinaryFile(m_private_key_file, + std::string(m_private_key.begin(), m_private_key.end()))) { + throw std::runtime_error( + strprintf("Cannot save I2P private key to %s", m_private_key_file)); + } +} + +Binary Session::MyDestination() const +{ + // From https://geti2p.net/spec/common-structures#destination: + // "They are 387 bytes plus the certificate length specified at bytes 385-386, which may be + // non-zero" + static constexpr size_t DEST_LEN_BASE = 387; + static constexpr size_t CERT_LEN_POS = 385; + + uint16_t cert_len; + memcpy(&cert_len, &m_private_key.at(CERT_LEN_POS), sizeof(cert_len)); + cert_len = be16toh(cert_len); + + const size_t dest_len = DEST_LEN_BASE + cert_len; + + return Binary{m_private_key.begin(), m_private_key.begin() + dest_len}; +} + +void Session::CreateIfNotCreatedAlready() +{ + LOCK(cs_i2p); + + std::string errmsg; + if (m_control_sock->IsConnected(errmsg)) { + return; + } + + LogPrint("i2p","I2P: Creating SAM session with %s\n", m_control_host.ToString()); + + auto sock = Hello(); + + const std::pair i2pRead = ReadBinaryFile(m_private_key_file); + if (i2pRead.first) { + m_private_key.assign(i2pRead.second.begin(), i2pRead.second.end()); + } else { + GenerateAndSavePrivateKey(*sock); + } + + const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs + const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key)); + + SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s", + session_id, private_key_b64)); + + m_my_addr = CService(DestBinToAddr(MyDestination()), Params().GetDefaultPort()); + m_session_id = session_id; + m_control_sock = std::move(sock); + + LogPrint("i2p","I2P: SAM session created: session id=%s, my address=%s\n", m_session_id, + m_my_addr.ToString()); +} + +std::unique_ptr Session::StreamAccept() +{ + auto sock = Hello(); + + const Reply& reply = SendRequestAndGetReply( + *sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false); + + const std::string& result = reply.Get("RESULT"); + + if (result == "OK") { + return sock; + } + + if (result == "INVALID_ID") { + // If our session id is invalid, then force session re-creation on next usage. + Disconnect(); + } + + throw std::runtime_error(strprintf("\"%s\"", reply.full)); +} + +void Session::Disconnect() +{ + LOCK(cs_i2p); + try + { + if (m_control_sock->Get() != INVALID_SOCKET) { + if (m_session_id.empty()) { + LogPrint("i2p","I2P: Destroying incomplete session\n"); + } else { + LogPrint("i2p","I2P: Destroying session %s\n", m_session_id); + } + } + m_control_sock->Reset(); + m_session_id.clear(); + } + catch(std::bad_alloc&) + { + // when the node is shutting down, the call above might use invalid memory resulting in a + // std::bad_alloc exception when instantiating internal objs for handling log category + LogPrintf("(node is probably shutting down) Destroying session=%d\n", m_session_id); + } + + +} +} // namespace sam +} // namespace i2p diff --git a/src/i2p.h b/src/i2p.h new file mode 100644 index 000000000..8c1e0539c --- /dev/null +++ b/src/i2p.h @@ -0,0 +1,256 @@ +// Copyright (c) 2020-2020 The Bitcoin Core developers +// Copyright (c) 2016-2022 The Hush developers +// Distributed under the GPLv3 software license, see the accompanying +// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html + +#ifndef HUSH_I2P_H +#define HUSH_I2P_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace i2p { + +/** + * Binary data. + */ +using Binary = std::vector; + +/** + * An established connection with another peer. + */ +struct Connection { + /** Connected socket. */ + std::unique_ptr sock; + + /** Our I2P address. */ + CService me; + + /** The peer's I2P address. */ + CService peer; +}; + +namespace sam { + +/** + * The maximum size of an incoming message from the I2P SAM proxy (in bytes). + * Used to avoid a runaway proxy from sending us an "unlimited" amount of data without a terminator. + * The longest known message is ~1400 bytes, so this is high enough not to be triggered during + * normal operation, yet low enough to avoid a malicious proxy from filling our memory. + */ +static constexpr size_t MAX_MSG_SIZE{65536}; + +/** + * I2P SAM session. + */ +class Session +{ +public: + /** + * Construct a session. This will not initiate any IO, the session will be lazily created + * later when first used. + * @param[in] private_key_file Path to a private key file. If the file does not exist then the + * private key will be generated and saved into the file. + * @param[in] control_host Location of the SAM proxy. + */ + Session(const fs::path& private_key_file, + const CService& control_host); + + /** + * Destroy the session, closing the internally used sockets. The sockets that have been + * returned by `Accept()` or `Connect()` will not be closed, but they will be closed by + * the SAM proxy because the session is destroyed. So they will return an error next time + * we try to read or write to them. + */ + ~Session(); + + /** + * Check the control sock and restart if needed + */ + bool Check(); + + /** + * Start listening for an incoming connection. + * @param[out] conn Upon successful completion the `sock` and `me` members will be set + * to the listening socket and address. + * @return true on success + */ + bool Listen(Connection& conn); + + /** + * Wait for and accept a new incoming connection. + * @param[in,out] conn The `sock` member is used for waiting and accepting. Upon successful + * completion the `peer` member will be set to the address of the incoming peer. + * @return true on success + */ + bool Accept(Connection& conn); + + /** + * Connect to an I2P peer. + * @param[in] to Peer to connect to. + * @param[out] conn Established connection. Only set if `true` is returned. + * @param[out] proxy_error If an error occurs due to proxy or general network failure, then + * this is set to `true`. If an error occurs due to unreachable peer (likely peer is down), then + * it is set to `false`. Only set if `false` is returned. + * @return true on success + */ + bool Connect(const CService& to, Connection& conn, bool& proxy_error); + +protected: + + CCriticalSection cs_i2p; + +private: + /** + * A reply from the SAM proxy. + */ + struct Reply { + /** + * Full, unparsed reply. + */ + std::string full; + + /** + * Request, used for detailed error reporting. + */ + std::string request; + + /** + * A map of keywords from the parsed reply. + * For example, if the reply is "A=X B C=YZ", then the map will be + * keys["A"] == "X" + * keys["B"] == (empty std::optional) + * keys["C"] == "YZ" + */ + std::unordered_map> keys; + + /** + * Get the value of a given key. + * For example if the reply is "A=X B" then: + * Value("A") -> "X" + * Value("B") -> throws + * Value("C") -> throws + * @param[in] key Key whose value to retrieve + * @returns the key's value + * @throws std::runtime_error if the key is not present or if it has no value + */ + std::string Get(const std::string& key) const; + }; + + /** + * Send request and get a reply from the SAM proxy. + * @param[in] sock A socket that is connected to the SAM proxy. + * @param[in] request Raw request to send, a newline terminator is appended to it. + * @param[in] check_result_ok If true then after receiving the reply a check is made + * whether it contains "RESULT=OK" and an exception is thrown if it does not. + * @throws std::runtime_error if an error occurs + */ + Reply SendRequestAndGetReply(const Sock& sock, + const std::string& request, + bool check_result_ok = true) const; + + /** + * Open a new connection to the SAM proxy. + * @return a connected socket + * @throws std::runtime_error if an error occurs + */ + std::unique_ptr Hello() const EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); + + /** + * Check the control socket for errors and possibly disconnect. + */ + void CheckControlSock(); + + /** + * Generate a new destination with the SAM proxy and set `m_private_key` to it. + * @param[in] sock Socket to use for talking to the SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void DestGenerate(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); + + /** + * Generate a new destination with the SAM proxy, set `m_private_key` to it and save + * it on disk to `m_private_key_file`. + * @param[in] sock Socket to use for talking to the SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void GenerateAndSavePrivateKey(const Sock& sock) EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); + + /** + * Derive own destination from `m_private_key`. + * @see https://geti2p.net/spec/common-structures#destination + * @return an I2P destination + */ + Binary MyDestination() const EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); + + /** + * Create the session if not already created. Reads the private key file and connects to the + * SAM proxy. + * @throws std::runtime_error if an error occurs + */ + void CreateIfNotCreatedAlready() EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); + + /** + * Open a new connection to the SAM proxy and issue "STREAM ACCEPT" request using the existing + * session id. + * @return the idle socket that is waiting for a peer to connect to us + * @throws std::runtime_error if an error occurs + */ + std::unique_ptr StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); + + /** + * Destroy the session, closing the internally used sockets. + */ + void Disconnect() EXCLUSIVE_LOCKS_REQUIRED(cs_i2p); + + /** + * The name of the file where this peer's private key is stored (in binary). + */ + const fs::path m_private_key_file; + + /** + * The host and port of the SAM control service. + */ + const CService m_control_host; + + /** + * The private key of this peer. + * @see The reply to the "DEST GENERATE" command in https://geti2p.net/en/docs/api/samv3 + */ + Binary m_private_key GUARDED_BY(cs_i2p); + + /** + * SAM control socket. + * Used to connect to the I2P SAM service and create a session + * ("SESSION CREATE"). With the established session id we later open + * other connections to the SAM service to accept incoming I2P + * connections and make outgoing ones. + * See https://geti2p.net/en/docs/api/samv3 + */ + std::unique_ptr m_control_sock GUARDED_BY(cs_i2p); + + /** + * Our .b32.i2p address. + * Derived from `m_private_key`. + */ + CService m_my_addr GUARDED_BY(cs_i2p); + + /** + * SAM session id. + */ + std::string m_session_id GUARDED_BY(cs_i2p); +}; + +} // namespace sam +} // namespace i2p + +#endif /* HUSH_I2P_H */ diff --git a/src/init.cpp b/src/init.cpp index 6fe9c9299..ed9359f2c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -246,14 +246,22 @@ void Shutdown() if (pcoinsTip != NULL) { FlushStateToDisk(); } - delete pcoinsTip; - pcoinsTip = NULL; - delete pcoinscatcher; - pcoinscatcher = NULL; - delete pcoinsdbview; - pcoinsdbview = NULL; - delete pblocktree; - pblocktree = NULL; + if (pcoinsTip != NULL) { + delete pcoinsTip; + pcoinsTip = NULL; + } + if (pcoinscatcher != NULL) { + delete pcoinscatcher; + pcoinscatcher = NULL; + } + if (pcoinsdbview != NULL) { + delete pcoinsdbview; + pcoinsdbview = NULL; + } + if (pblocktree != NULL) { + delete pblocktree; + pblocktree = NULL; + } } #ifdef ENABLE_WALLET if (pwalletMain) @@ -274,7 +282,7 @@ void Shutdown() #endif globalVerifyHandle.reset(); ECC_Stop(); - CNode::NetCleanup(); + // CNode::NetCleanup(); LogPrintf("%s: done\n", __func__); } @@ -325,7 +333,7 @@ bool static InitWarning(const std::string &str) } bool static Bind(const CService &addr, unsigned int flags) { - if (!(flags & BF_EXPLICIT) && IsLimited(addr)) + if (!(flags & BF_EXPLICIT) && !IsReachable(addr)) return false; std::string strError; if (!BindListenPort(addr, strError, (flags & BF_ALLOWLIST) != 0)) { @@ -416,7 +424,14 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-maxreceivebuffer=", strprintf(_("Maximum per-connection receive buffer, *1000 bytes (default: %u)"), 5000)); strUsage += HelpMessageOpt("-maxsendbuffer=", strprintf(_("Maximum per-connection send buffer, *1000 bytes (default: %u)"), 1000)); strUsage += HelpMessageOpt("-onion=", strprintf(_("Use separate SOCKS5 proxy to reach peers via Tor hidden services (default: %s)"), "-proxy")); - strUsage += HelpMessageOpt("-onlynet=", _("Only connect to nodes in network (ipv4, ipv6 or onion)")); + strUsage += HelpMessageOpt("-nspv_msg", strprintf(_("Enable NSPV messages processing (default: true when -ac_private=1, otherwise false)"))); + + strUsage += HelpMessageOpt("-i2psam=", strprintf(_("I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)"))); + strUsage += HelpMessageOpt("-i2pacceptincoming", strprintf(_("If set and -i2psam is also set then incoming I2P connections are accepted via the SAM proxy. If this is not set but -i2psam is set then only outgoing connections will be made to the I2P network. Ignored if -i2psam is not set. Listening for incoming I2P connections is done through the SAM proxy, not by binding to a local address and port (default: 1)"))); + strUsage += HelpMessageOpt("-onlynet=", _("Only connect to nodes in network (ipv4, ipv6, onion or i2p)")); + strUsage += HelpMessageOpt("-disableipv4", _("Disable Ipv4 network connections") + " " + _("(default: 0)")); + strUsage += HelpMessageOpt("-disableipv6", _("Disable Ipv6 network connections") + " " + _("(default: 0)")); + strUsage += HelpMessageOpt("-permitbaremultisig", strprintf(_("Relay non-P2SH multisig (default: %u)"), 1)); strUsage += HelpMessageOpt("-peerbloomfilters", strprintf(_("Support filtering of blocks and transaction with Bloom filters (default: %u)"), 1)); if (showDebug) @@ -1609,14 +1624,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) for (int n = 0; n < NET_MAX; n++) { enum Network net = (enum Network)n; if (!nets.count(net)) - SetLimited(net); + SetReachable(net, false); } } //fprintf(stderr,"%s tik19\n", __FUNCTION__); if (mapArgs.count("-allowlist")) { BOOST_FOREACH(const std::string& net, mapMultiArgs["-allowlist"]) { - CSubNet subnet(net); + CSubNet subnet; + LookupSubNet(net.c_str(), subnet); if (!subnet.IsValid()) return InitError(strprintf(_("Invalid netmask specified in -allowlist: '%s'"), net)); CNode::AddAllowlistedRange(subnet); @@ -1627,9 +1643,10 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // -proxy sets a proxy for all outgoing network traffic // -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default std::string proxyArg = GetArg("-proxy", ""); - SetLimited(NET_ONION); + SetReachable(NET_ONION,false); if (proxyArg != "" && proxyArg != "0") { - proxyType addrProxy = proxyType(CService(proxyArg, 9050), proxyRandomize); + CService resolved(LookupNumeric(proxyArg.c_str(), 9050)); + proxyType addrProxy = proxyType(resolved, proxyRandomize); if (!addrProxy.IsValid()) return InitError(strprintf(_("Invalid -proxy address: '%s'"), proxyArg)); @@ -1637,9 +1654,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) SetProxy(NET_IPV6, addrProxy); SetProxy(NET_ONION, addrProxy); SetNameProxy(addrProxy); - SetLimited(NET_ONION, false); // by default, -proxy sets onion as reachable, unless -noonion later + SetReachable(NET_ONION, true); // by default, -proxy sets onion as reachable, unless -noonion later + } + + const std::string& i2psam_arg = GetArg("-i2psam", ""); + if (!i2psam_arg.empty()) { + CService addr; + if (!Lookup(i2psam_arg.c_str(), addr, 7656, fNameLookup) || !addr.IsValid()) { + return InitError(strprintf(_("Invalid -i2psam address or hostname: '%s'"), i2psam_arg)); + } + SetReachable(NET_I2P, true); + SetProxy(NET_I2P, proxyType{addr}); + } else { + SetReachable(NET_I2P, false); } - //fprintf(stderr,"%s tik20\n", __FUNCTION__); // -onion can be used to set only a proxy for .onion, or override normal proxy for .onion addresses // -noonion (or -onion=0) disables connecting to .onion entirely @@ -1647,13 +1675,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) std::string onionArg = GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 - SetLimited(NET_ONION); // set onions as unreachable + SetReachable(NET_ONION,false); // set onions as unreachable } else { - proxyType addrOnion = proxyType(CService(onionArg, 9050), proxyRandomize); + CService resolved(LookupNumeric(onionArg.c_str(), 9050)); + proxyType addrOnion = proxyType(resolved, proxyRandomize); if (!addrOnion.IsValid()) return InitError(strprintf(_("Invalid -onion address: '%s'"), onionArg)); SetProxy(NET_ONION, addrOnion); - SetLimited(NET_ONION, false); + SetReachable(NET_ONION, true); } } @@ -1692,10 +1721,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (mapArgs.count("-externalip")) { BOOST_FOREACH(const std::string& strAddr, mapMultiArgs["-externalip"]) { - CService addrLocal(strAddr, GetListenPort(), fNameLookup); - if (!addrLocal.IsValid()) + CService addrLocal; + if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid()) { + AddLocal(addrLocal, LOCAL_MANUAL); + } else { return InitError(strprintf(_("Cannot resolve -externalip address: '%s'"), strAddr)); - AddLocal(CService(strAddr, GetListenPort(), fNameLookup), LOCAL_MANUAL); + } } } diff --git a/src/key_io.cpp b/src/key_io.cpp index c3d5471d5..88477a834 100644 --- a/src/key_io.cpp +++ b/src/key_io.cpp @@ -9,7 +9,7 @@ #include #include #include