diff options
71 files changed, 1284 insertions, 454 deletions
diff --git a/contrib/linearize/example-linearize.cfg b/contrib/linearize/example-linearize.cfg index 5990b9307a..5f566261ca 100644 --- a/contrib/linearize/example-linearize.cfg +++ b/contrib/linearize/example-linearize.cfg @@ -13,6 +13,9 @@ port=8332 #regtest default #port=18443 +#signet default +#port=38332 + # bootstrap.dat hashlist settings (linearize-hashes) max_height=313000 @@ -33,6 +36,11 @@ input=/home/example/.bitcoin/blocks #genesis=0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206 #input=/home/example/.bitcoin/regtest/blocks +# signet +#netmagic=0a03cf40 +#genesis=00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6 +#input=/home/example/.bitcoin/signet/blocks + # "output" option causes blockchain files to be written to the given location, # with "output_file" ignored. If not used, "output_file" is used instead. # output=/home/example/blockchain_directory diff --git a/doc/release-notes-15367.md b/doc/release-notes-15367.md new file mode 100644 index 0000000000..598e49dcae --- /dev/null +++ b/doc/release-notes-15367.md @@ -0,0 +1,6 @@ +Configuration option changes +---------------------------- + +- The `startupnotify` option is used to specify a command to + execute when Bitcoin Core has finished with its startup + sequence. (#15367)
\ No newline at end of file diff --git a/doc/release-notes-16378.md b/doc/release-notes-16378.md index b006ea1a56..958633e780 100644 --- a/doc/release-notes-16378.md +++ b/doc/release-notes-16378.md @@ -1,5 +1,6 @@ RPC --- - A new `send` RPC with similar syntax to `walletcreatefundedpsbt`, including - support for coin selection and a custom fee rate. Using the new `send` method - is encouraged: `sendmany` and `sendtoaddress` may be deprecated in a future release. + support for coin selection and a custom fee rate. The `send` RPC is experimental + and may change in subsequent releases. Using it is encouraged once it's no + longer experimental: `sendmany` and `sendtoaddress` may be deprecated in a future release. diff --git a/doc/release-notes.md b/doc/release-notes.md index 1baff028f3..1580bbd9ae 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -105,6 +105,17 @@ will trigger BIP 125 (replace-by-fee) opt-in. (#11413) - The `testmempoolaccept` RPC returns `vsize` and a `fee` object with the `base` fee if the transaction passes validation. (#19940) +- The `getpeerinfo` RPC now returns a `connection_type` field. This indicates + the type of connection established with the peer. It will return one of six + options. For more information, see the `getpeerinfo` help documentation. + (#19725) + +- The `getpeerinfo` RPC no longer returns the `addnode` field by default. This + field will be fully removed in the next major release. It can be accessed + with the configuration option `-deprecatedrpc=getpeerinfo_addnode`. However, + it is recommended to instead use the `connection_type` field (it will return + `manual` when addnode is true). (#19725) + - The `walletcreatefundedpsbt` RPC call will now fail with `Insufficient funds` when inputs are manually selected but are not enough to cover the outputs and fee. Additional inputs can automatically be added through the diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index e94d4dff49..aec4177b19 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -51,6 +51,7 @@ static void SetupCliArgs(ArgsManager& argsman) const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); + const auto signetBaseParams = CreateBaseChainParams(CBaseChainParams::SIGNET); const auto regtestBaseParams = CreateBaseChainParams(CBaseChainParams::REGTEST); argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -66,7 +67,7 @@ static void SetupCliArgs(ArgsManager& argsman) argsman.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); + argsman.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcwait", "Wait for RPC server to start", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind). This changes the RPC endpoint used, e.g. http://127.0.0.1:8332/wallet/<walletname>", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 034e897ca6..f19e72e9ab 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -18,13 +18,13 @@ const std::string CBaseChainParams::REGTEST = "regtest"; void SetupChainParamsBaseOptions(ArgsManager& argsman) { - argsman.AddArg("-chain=<chain>", "Use the chain <chain> (default: main). Allowed values: main, test, regtest", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-chain=<chain>", "Use the chain <chain> (default: main). Allowed values: main, test, signet, regtest", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " "This is intended for regression testing tools and app development. Equivalent to -chain=regtest.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-segwitheight=<n>", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); - argsman.AddArg("-signet", "Use the signet chain. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-signet", "Use the signet chain. Equivalent to -chain=signet. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge)", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS); argsman.AddArg("-signetseednode", "Specify a seed node for the signet network, in the hostname[:port] format, e.g. sig.net:1234 (may be used multiple times to specify multiple seed nodes; defaults to the global default signet test network seed node(s))", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS); } diff --git a/src/crypto/common.h b/src/crypto/common.h index 5b4932c992..c1acf8b22e 100644 --- a/src/crypto/common.h +++ b/src/crypto/common.h @@ -53,6 +53,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/init.cpp b/src/init.cpp index 5ad807cbac..c517b04d68 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -378,9 +378,11 @@ void SetupServerArgs(NodeContext& node) const auto defaultBaseParams = CreateBaseChainParams(CBaseChainParams::MAIN); const auto testnetBaseParams = CreateBaseChainParams(CBaseChainParams::TESTNET); + const auto signetBaseParams = CreateBaseChainParams(CBaseChainParams::SIGNET); const auto regtestBaseParams = CreateBaseChainParams(CBaseChainParams::REGTEST); const auto defaultChainParams = CreateChainParams(CBaseChainParams::MAIN); const auto testnetChainParams = CreateChainParams(CBaseChainParams::TESTNET); + const auto signetChainParams = CreateChainParams(CBaseChainParams::SIGNET); const auto regtestChainParams = CreateChainParams(CBaseChainParams::REGTEST); // Hidden Options @@ -393,7 +395,7 @@ void SetupServerArgs(NodeContext& node) #if HAVE_SYSTEM argsman.AddArg("-alertnotify=<cmd>", "Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif - argsman.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-assumevalid=<hex>", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocksdir=<dir>", "Specify directory to hold blocks subdirectory for *.dat files (default: <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if HAVE_SYSTEM argsman.AddArg("-blocknotify=<cmd>", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -411,7 +413,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); + argsman.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex(), signetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-par=<n>", strprintf("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)", -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -422,6 +424,9 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-reindex-chainstate", "Rebuild chain state from the currently indexed blocks. When in pruning mode or if blocks on disk might be corrupted, use full -reindex instead.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-settings=<file>", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#if HAVE_SYSTEM + argsman.AddArg("-startupnotify=<cmd>", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); +#endif #ifndef WIN32 argsman.AddArg("-sysperms", "Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #else @@ -455,7 +460,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-port=<port>", strprintf("Listen for connections on <port> (default: %u, testnet: %u signet: %u, regtest: %u)", defaultChainParams->GetDefaultPort(), testnetChainParams->GetDefaultPort(), signetChainParams->GetDefaultPort(), regtestChainParams->GetDefaultPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-seednode=<ip>", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -565,7 +570,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-rpcbind=<addr>[:port]", "Bind to given address to listen for JSON-RPC connections. Do not expose the RPC server to untrusted networks such as the public internet! This option is ignored unless -rpcallowip is also passed. Port is optional and overrides -rpcport. Use [host]:port notation for IPv6. This option can be specified multiple times (default: 127.0.0.1 and ::1 i.e., localhost)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY | ArgsManager::SENSITIVE, OptionsCategory::RPC); argsman.AddArg("-rpccookiefile=<loc>", "Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)", ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC); - argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); + argsman.AddArg("-rpcport=<port>", strprintf("Listen for JSON-RPC connections on <port> (default: %u, testnet: %u, signet: %u, regtest: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort(), signetBaseParams->RPCPort(), regtestBaseParams->RPCPort()), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcserialversion", strprintf("Sets the serialization of raw transaction or block hex returned in non-verbose mode, non-segwit(0) or segwit(1) (default: %d)", DEFAULT_RPC_SERIALIZE_VERSION), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); argsman.AddArg("-rpcservertimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_SERVER_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC); argsman.AddArg("-rpcthreads=<n>", strprintf("Set the number of threads to service RPC calls (default: %d)", DEFAULT_HTTP_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::RPC); @@ -674,6 +679,17 @@ static void CleanupBlockRevFiles() } } +#if HAVE_SYSTEM +static void StartupNotify(const ArgsManager& args) +{ + std::string cmd = args.GetArg("-startupnotify", ""); + if (!cmd.empty()) { + std::thread t(runCommand, cmd); + t.detach(); // thread runs free + } +} +#endif + static void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFiles, const ArgsManager& args) { const CChainParams& chainparams = Params(); @@ -1176,6 +1192,10 @@ bool AppInitParameterInteraction(const ArgsManager& args) nMaxTipAge = args.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE); + if (args.IsArgSet("-proxy") && args.GetArg("-proxy", "").empty()) { + return InitError(_("No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.")); + } + return true; } @@ -1618,7 +1638,6 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA bool failed_chainstate_init = false; for (CChainState* chainstate : chainman.GetAll()) { - LogPrintf("Initializing chainstate %s\n", chainstate->ToString()); chainstate->InitCoinsDB( /* cache_size_bytes */ nCoinDBCache, /* in_memory */ false, @@ -1973,5 +1992,9 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA banman->DumpBanlist(); }, DUMP_BANS_INTERVAL); +#if HAVE_SYSTEM + StartupNotify(args); +#endif + return true; } diff --git a/src/net.cpp b/src/net.cpp index e7d3a146ff..7dec4abfb9 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -10,7 +10,6 @@ #include <net.h> #include <banman.h> -#include <chainparams.h> #include <clientversion.h> #include <consensus/consensus.h> #include <crypto/sha256.h> @@ -488,6 +487,26 @@ void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNet } } +std::string CNode::ConnectionTypeAsString() const +{ + switch (m_conn_type) { + case ConnectionType::INBOUND: + return "inbound"; + case ConnectionType::MANUAL: + return "manual"; + case ConnectionType::FEELER: + return "feeler"; + case ConnectionType::OUTBOUND_FULL_RELAY: + return "outbound-full-relay"; + case ConnectionType::BLOCK_RELAY: + return "block-relay-only"; + case ConnectionType::ADDR_FETCH: + return "addr-fetch"; + } // no default case, so the compiler can warn about missing cases + + assert(false); +} + std::string CNode::GetAddrName() const { LOCK(cs_addrName); return addrName; @@ -582,9 +601,21 @@ void CNode::copyStats(CNodeStats &stats, const std::vector<bool> &m_asmap) // Leave string empty if addrLocal invalid (not filled in yet) CService addrLocalUnlocked = GetAddrLocal(); stats.addrLocal = addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : ""; + + stats.m_conn_type_string = ConnectionTypeAsString(); } #undef X +/** + * Receive bytes from the buffer and deserialize them into messages. + * + * @param[in] pch A pointer to the raw data + * @param[in] nBytes Size of the data + * @param[out] complete Set True if at least one message has been + * deserialized and is ready to be processed + * @return True if the peer should stay connected, + * False if the peer should be disconnected from. + */ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes, bool& complete) { complete = false; @@ -595,25 +626,35 @@ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes, bool& complete while (nBytes > 0) { // absorb network data int handled = m_deserializer->Read(pch, nBytes); - if (handled < 0) return false; + if (handled < 0) { + // Serious header problem, disconnect from the peer. + return false; + } pch += handled; nBytes -= handled; if (m_deserializer->Complete()) { // decompose a transport agnostic CNetMessage from the deserializer - CNetMessage msg = m_deserializer->GetMessage(Params().MessageStart(), time); + uint32_t out_err_raw_size{0}; + Optional<CNetMessage> result{m_deserializer->GetMessage(time, out_err_raw_size)}; + if (!result) { + // Message deserialization failed. Drop the message but don't disconnect the peer. + // store the size of the corrupt message + mapRecvBytesPerMsgCmd.find(NET_MESSAGE_COMMAND_OTHER)->second += out_err_raw_size; + continue; + } //store received bytes per message command //to prevent a memory DOS, only allow valid commands - mapMsgCmdSize::iterator i = mapRecvBytesPerMsgCmd.find(msg.m_command); + mapMsgCmdSize::iterator i = mapRecvBytesPerMsgCmd.find(result->m_command); if (i == mapRecvBytesPerMsgCmd.end()) i = mapRecvBytesPerMsgCmd.find(NET_MESSAGE_COMMAND_OTHER); assert(i != mapRecvBytesPerMsgCmd.end()); - i->second += msg.m_raw_message_size; + i->second += result->m_raw_message_size; // push the message to the process queue, - vRecvMsg.push_back(std::move(msg)); + vRecvMsg.push_back(std::move(*result)); complete = true; } @@ -640,11 +681,19 @@ int V1TransportDeserializer::readHeader(const char *pch, unsigned int nBytes) hdrbuf >> hdr; } catch (const std::exception&) { + LogPrint(BCLog::NET, "HEADER ERROR - UNABLE TO DESERIALIZE, peer=%d\n", m_node_id); + return -1; + } + + // Check start string, network magic + if (memcmp(hdr.pchMessageStart, m_chain_params.MessageStart(), CMessageHeader::MESSAGE_START_SIZE) != 0) { + LogPrint(BCLog::NET, "HEADER ERROR - MESSAGESTART (%s, %u bytes), received %s, peer=%d\n", hdr.GetCommand(), hdr.nMessageSize, HexStr(hdr.pchMessageStart), m_node_id); return -1; } // reject messages larger than MAX_SIZE or MAX_PROTOCOL_MESSAGE_LENGTH if (hdr.nMessageSize > MAX_SIZE || hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) { + LogPrint(BCLog::NET, "HEADER ERROR - SIZE (%s, %u bytes), peer=%d\n", hdr.GetCommand(), hdr.nMessageSize, m_node_id); return -1; } @@ -679,36 +728,39 @@ const uint256& V1TransportDeserializer::GetMessageHash() const return data_hash; } -CNetMessage V1TransportDeserializer::GetMessage(const CMessageHeader::MessageStartChars& message_start, const std::chrono::microseconds time) +Optional<CNetMessage> V1TransportDeserializer::GetMessage(const std::chrono::microseconds time, uint32_t& out_err_raw_size) { // decompose a single CNetMessage from the TransportDeserializer - CNetMessage msg(std::move(vRecv)); + Optional<CNetMessage> msg(std::move(vRecv)); - // store state about valid header, netmagic and checksum - msg.m_valid_header = hdr.IsValid(message_start); - msg.m_valid_netmagic = (memcmp(hdr.pchMessageStart, message_start, CMessageHeader::MESSAGE_START_SIZE) == 0); - uint256 hash = GetMessageHash(); + // store command string, time, and sizes + msg->m_command = hdr.GetCommand(); + msg->m_time = time; + msg->m_message_size = hdr.nMessageSize; + msg->m_raw_message_size = hdr.nMessageSize + CMessageHeader::HEADER_SIZE; - // store command string, payload size - msg.m_command = hdr.GetCommand(); - msg.m_message_size = hdr.nMessageSize; - msg.m_raw_message_size = hdr.nMessageSize + CMessageHeader::HEADER_SIZE; + uint256 hash = GetMessageHash(); // We just received a message off the wire, harvest entropy from the time (and the message checksum) RandAddEvent(ReadLE32(hash.begin())); - msg.m_valid_checksum = (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) == 0); - if (!msg.m_valid_checksum) { - LogPrint(BCLog::NET, "CHECKSUM ERROR (%s, %u bytes), expected %s was %s\n", - SanitizeString(msg.m_command), msg.m_message_size, + // Check checksum and header command string + if (memcmp(hash.begin(), hdr.pchChecksum, CMessageHeader::CHECKSUM_SIZE) != 0) { + LogPrint(BCLog::NET, "CHECKSUM ERROR (%s, %u bytes), expected %s was %s, peer=%d\n", + SanitizeString(msg->m_command), msg->m_message_size, HexStr(Span<uint8_t>(hash.begin(), hash.begin() + CMessageHeader::CHECKSUM_SIZE)), - HexStr(hdr.pchChecksum)); - } - - // store receive time - msg.m_time = time; - - // reset the network deserializer (prepare for the next message) + HexStr(hdr.pchChecksum), + m_node_id); + out_err_raw_size = msg->m_raw_message_size; + msg = nullopt; + } else if (!hdr.IsCommandValid()) { + LogPrint(BCLog::NET, "HEADER ERROR - COMMAND (%s, %u bytes), peer=%d\n", + hdr.GetCommand(), msg->m_message_size, m_node_id); + out_err_raw_size = msg->m_raw_message_size; + msg = nullopt; + } + + // Always reset the network deserializer (prepare for the next message) Reset(); return msg; } @@ -1759,7 +1811,7 @@ void CConnman::SetTryNewOutboundPeer(bool flag) // Return the number of peers we have over our outbound connection limit // Exclude peers that are marked for disconnect, or are going to be -// disconnected soon (eg one-shots and feelers) +// disconnected soon (eg ADDR_FETCH and FEELER) // Also exclude peers that haven't finished initial connection handshake yet // (so that we don't decide we're over our desired connection limit, and then // evict some peer that has finished the handshake) @@ -2828,7 +2880,7 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn LogPrint(BCLog::NET, "Added connection peer=%d\n", id); } - m_deserializer = MakeUnique<V1TransportDeserializer>(V1TransportDeserializer(Params().MessageStart(), SER_NETWORK, INIT_PROTO_VERSION)); + m_deserializer = MakeUnique<V1TransportDeserializer>(V1TransportDeserializer(Params(), GetId(), SER_NETWORK, INIT_PROTO_VERSION)); m_serializer = MakeUnique<V1TransportSerializer>(V1TransportSerializer()); } @@ -10,12 +10,14 @@ #include <addrman.h> #include <amount.h> #include <bloom.h> +#include <chainparams.h> #include <compat.h> #include <crypto/siphash.h> #include <hash.h> #include <limitedmap.h> -#include <netaddress.h> #include <net_permissions.h> +#include <netaddress.h> +#include <optional.h> #include <policy/feerate.h> #include <protocol.h> #include <random.h> @@ -114,6 +116,14 @@ struct CSerializedNetMsg std::string m_type; }; +const std::vector<std::string> CONNECTION_TYPE_DOC{ + "outbound-full-relay (default automatic connections)", + "block-relay-only (does not relay transactions or addresses)", + "inbound (initiated by the peer)", + "manual (added via addnode RPC or -addnode/-connect configuration options)", + "addr-fetch (short-lived automatic connection for soliciting addresses)", + "feeler (short-lived automatic connection for testing addresses)"}; + /** Different types of connections to a peer. This enum encapsulates the * information we have available at the time of opening or accepting the * connection. Aside from INBOUND, all types are initiated by us. */ @@ -143,10 +153,19 @@ enum class ConnectionType { MANUAL, /** - * Feeler connections are short lived connections used to increase the - * number of connectable addresses in our AddrMan. Approximately every - * FEELER_INTERVAL, we attempt to connect to a random address from the new - * table. If successful, we add it to the tried table. + * Feeler connections are short-lived connections made to check that a node + * is alive. They can be useful for: + * - test-before-evict: if one of the peers is considered for eviction from + * our AddrMan because another peer is mapped to the same slot in the tried table, + * evict only if this longer-known peer is offline. + * - move node addresses from New to Tried table, so that we have more + * connectable addresses in our AddrMan. + * Note that in the literature ("Eclipse Attacks on Bitcoin’s Peer-to-Peer Network") + * only the latter feature is referred to as "feeler connections", + * although in our codebase feeler connections encompass test-before-evict as well. + * We make these connections approximately every FEELER_INTERVAL: + * first we resolve previously found collisions if they exist (test-before-evict), + * otherwise connect to a node from the new table. */ FEELER, @@ -691,6 +710,7 @@ public: // Bind address of our side of the connection CAddress addrBind; uint32_t m_mapped_as; + std::string m_conn_type_string; }; @@ -703,11 +723,8 @@ class CNetMessage { public: CDataStream m_recv; //!< received message data std::chrono::microseconds m_time{0}; //!< time of message receipt - bool m_valid_netmagic = false; - bool m_valid_header = false; - bool m_valid_checksum = false; - uint32_t m_message_size{0}; //!< size of the payload - uint32_t m_raw_message_size{0}; //!< used wire size of the message (including header/checksum) + uint32_t m_message_size{0}; //!< size of the payload + uint32_t m_raw_message_size{0}; //!< used wire size of the message (including header/checksum) std::string m_command; CNetMessage(CDataStream&& recv_in) : m_recv(std::move(recv_in)) {} @@ -731,13 +748,15 @@ public: // read and deserialize data virtual int Read(const char *data, unsigned int bytes) = 0; // decomposes a message from the context - virtual CNetMessage GetMessage(const CMessageHeader::MessageStartChars& message_start, std::chrono::microseconds time) = 0; + virtual Optional<CNetMessage> GetMessage(std::chrono::microseconds time, uint32_t& out_err) = 0; virtual ~TransportDeserializer() {} }; class V1TransportDeserializer final : public TransportDeserializer { private: + const CChainParams& m_chain_params; + const NodeId m_node_id; // Only for logging mutable CHash256 hasher; mutable uint256 data_hash; bool in_data; // parsing header (false) or data (true) @@ -763,8 +782,12 @@ private: } public: - - V1TransportDeserializer(const CMessageHeader::MessageStartChars& pchMessageStartIn, int nTypeIn, int nVersionIn) : hdrbuf(nTypeIn, nVersionIn), hdr(pchMessageStartIn), vRecv(nTypeIn, nVersionIn) { + V1TransportDeserializer(const CChainParams& chain_params, const NodeId node_id, int nTypeIn, int nVersionIn) + : m_chain_params(chain_params), + m_node_id(node_id), + hdrbuf(nTypeIn, nVersionIn), + vRecv(nTypeIn, nVersionIn) + { Reset(); } @@ -784,7 +807,7 @@ public: if (ret < 0) Reset(); return ret; } - CNetMessage GetMessage(const CMessageHeader::MessageStartChars& message_start, std::chrono::microseconds time) override; + Optional<CNetMessage> GetMessage(std::chrono::microseconds time, uint32_t& out_err_raw_size) override; }; /** The TransportSerializer prepares messages for the network transport @@ -1144,6 +1167,8 @@ public: std::string GetAddrName() const; //! Sets the addrName only if it was not previously set void MaybeSetAddrName(const std::string& addrNameIn); + + std::string ConnectionTypeAsString() const; }; /** Return a timestamp in the future (in microseconds) for exponentially distributed events. */ diff --git a/src/net_processing.cpp b/src/net_processing.cpp index f3f30285d8..f784a9b754 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3520,11 +3520,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // Making nodes which are behind NAT and can only make outgoing connections ignore // the getaddr message mitigates the attack. if (!pfrom.IsInboundConn()) { - LogPrint(BCLog::NET, "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom.GetId()); - return; - } - if (!pfrom.RelayAddrsWithConn()) { - LogPrint(BCLog::NET, "Ignoring \"getaddr\" from block-relay-only connection. peer=%d\n", pfrom.GetId()); + LogPrint(BCLog::NET, "Ignoring \"getaddr\" from %s connection. peer=%d\n", pfrom.ConnectionTypeAsString(), pfrom.GetId()); return; } @@ -3819,14 +3815,6 @@ bool PeerManager::MaybeDiscourageAndDisconnect(CNode& pnode) bool PeerManager::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgProc) { - // - // Message format - // (4) message start - // (12) command - // (4) size - // (4) checksum - // (x) data - // bool fMoreWork = false; if (!pfrom->vRecvGetData.empty()) @@ -3867,35 +3855,13 @@ bool PeerManager::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgP CNetMessage& msg(msgs.front()); msg.SetVersion(pfrom->GetCommonVersion()); - // Check network magic - if (!msg.m_valid_netmagic) { - LogPrint(BCLog::NET, "PROCESSMESSAGE: INVALID MESSAGESTART %s peer=%d\n", SanitizeString(msg.m_command), pfrom->GetId()); - pfrom->fDisconnect = true; - return false; - } - - // Check header - if (!msg.m_valid_header) - { - LogPrint(BCLog::NET, "PROCESSMESSAGE: ERRORS IN HEADER %s peer=%d\n", SanitizeString(msg.m_command), pfrom->GetId()); - return fMoreWork; - } const std::string& msg_type = msg.m_command; // Message size unsigned int nMessageSize = msg.m_message_size; - // Checksum - CDataStream& vRecv = msg.m_recv; - if (!msg.m_valid_checksum) - { - LogPrint(BCLog::NET, "%s(%s, %u bytes): CHECKSUM ERROR peer=%d\n", __func__, - SanitizeString(msg_type), nMessageSize, pfrom->GetId()); - return fMoreWork; - } - try { - ProcessMessage(*pfrom, msg_type, vRecv, msg.m_time, interruptMsgProc); + ProcessMessage(*pfrom, msg_type, msg.m_recv, msg.m_time, interruptMsgProc); if (interruptMsgProc) return false; if (!pfrom->vRecvGetData.empty()) diff --git a/src/netaddress.cpp b/src/netaddress.cpp index b50cf74069..08714dc2ec 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -5,18 +5,112 @@ #include <netaddress.h> +#include <crypto/common.h> +#include <crypto/sha3.h> #include <hash.h> +#include <prevector.h> #include <tinyformat.h> -#include <util/strencodings.h> #include <util/asmap.h> +#include <util/strencodings.h> +#include <util/string.h> #include <algorithm> #include <array> #include <cstdint> +#include <ios> #include <iterator> #include <tuple> constexpr size_t CNetAddr::V1_SERIALIZATION_SIZE; +constexpr size_t CNetAddr::MAX_ADDRV2_SIZE; + +CNetAddr::BIP155Network CNetAddr::GetBIP155Network() const +{ + switch (m_net) { + case NET_IPV4: + return BIP155Network::IPV4; + case NET_IPV6: + return BIP155Network::IPV6; + case NET_ONION: + switch (m_addr.size()) { + case ADDR_TORV2_SIZE: + return BIP155Network::TORV2; + case ADDR_TORV3_SIZE: + return BIP155Network::TORV3; + default: + assert(false); + } + case NET_I2P: + return BIP155Network::I2P; + case NET_CJDNS: + return BIP155Network::CJDNS; + case NET_INTERNAL: // should have been handled before calling this function + case NET_UNROUTABLE: // m_net is never and should not be set to NET_UNROUTABLE + case NET_MAX: // m_net is never and should not be set to NET_MAX + assert(false); + } // no default case, so the compiler can warn about missing cases + + assert(false); +} + +bool CNetAddr::SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t address_size) +{ + switch (possible_bip155_net) { + case BIP155Network::IPV4: + if (address_size == ADDR_IPV4_SIZE) { + m_net = NET_IPV4; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 IPv4 address with length %u (should be %u)", address_size, + ADDR_IPV4_SIZE)); + case BIP155Network::IPV6: + if (address_size == ADDR_IPV6_SIZE) { + m_net = NET_IPV6; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 IPv6 address with length %u (should be %u)", address_size, + ADDR_IPV6_SIZE)); + case BIP155Network::TORV2: + if (address_size == ADDR_TORV2_SIZE) { + m_net = NET_ONION; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 TORv2 address with length %u (should be %u)", address_size, + ADDR_TORV2_SIZE)); + case BIP155Network::TORV3: + if (address_size == ADDR_TORV3_SIZE) { + m_net = NET_ONION; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 TORv3 address with length %u (should be %u)", address_size, + ADDR_TORV3_SIZE)); + case BIP155Network::I2P: + if (address_size == ADDR_I2P_SIZE) { + m_net = NET_I2P; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 I2P address with length %u (should be %u)", address_size, + ADDR_I2P_SIZE)); + case BIP155Network::CJDNS: + if (address_size == ADDR_CJDNS_SIZE) { + m_net = NET_CJDNS; + return true; + } + throw std::ios_base::failure( + strprintf("BIP155 CJDNS address with length %u (should be %u)", address_size, + ADDR_CJDNS_SIZE)); + } + + // Don't throw on addresses with unknown network ids (maybe from the future). + // Instead silently drop them and have the unserialization code consume + // subsequent ones which may be known to us. + return false; +} /** * Construct an unspecified IPv6 network address (::/128). @@ -36,7 +130,13 @@ void CNetAddr::SetIP(const CNetAddr& ipIn) assert(ipIn.m_addr.size() == ADDR_IPV6_SIZE); break; case NET_ONION: - assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE); + assert(ipIn.m_addr.size() == ADDR_TORV2_SIZE || ipIn.m_addr.size() == ADDR_TORV3_SIZE); + break; + case NET_I2P: + assert(ipIn.m_addr.size() == ADDR_I2P_SIZE); + break; + case NET_CJDNS: + assert(ipIn.m_addr.size() == ADDR_CJDNS_SIZE); break; case NET_INTERNAL: assert(ipIn.m_addr.size() == ADDR_INTERNAL_SIZE); @@ -50,13 +150,6 @@ void CNetAddr::SetIP(const CNetAddr& ipIn) m_addr = ipIn.m_addr; } -template <typename T1, size_t PREFIX_LEN> -inline bool HasPrefix(const T1& obj, const std::array<uint8_t, PREFIX_LEN>& prefix) -{ - return obj.size() >= PREFIX_LEN && - std::equal(std::begin(prefix), std::end(prefix), std::begin(obj)); -} - void CNetAddr::SetLegacyIPv6(Span<const uint8_t> ipv6) { assert(ipv6.size() == ADDR_IPV6_SIZE); @@ -101,24 +194,80 @@ bool CNetAddr::SetInternal(const std::string &name) return true; } +namespace torv3 { +// https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n2135 +static constexpr size_t CHECKSUM_LEN = 2; +static const unsigned char VERSION[] = {3}; +static constexpr size_t TOTAL_LEN = ADDR_TORV3_SIZE + CHECKSUM_LEN + sizeof(VERSION); + +static void Checksum(Span<const uint8_t> addr_pubkey, uint8_t (&checksum)[CHECKSUM_LEN]) +{ + // TORv3 CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2] + static const unsigned char prefix[] = ".onion checksum"; + static constexpr size_t prefix_len = 15; + + SHA3_256 hasher; + + hasher.Write(MakeSpan(prefix).first(prefix_len)); + hasher.Write(addr_pubkey); + hasher.Write(VERSION); + + uint8_t checksum_full[SHA3_256::OUTPUT_SIZE]; + + hasher.Finalize(checksum_full); + + memcpy(checksum, checksum_full, sizeof(checksum)); +} + +}; // namespace torv3 + /** - * Parse a TORv2 address and set this object to it. + * Parse a TOR address and set this object to it. * * @returns Whether or not the operation was successful. * * @see CNetAddr::IsTor() */ -bool CNetAddr::SetSpecial(const std::string &strName) +bool CNetAddr::SetSpecial(const std::string& str) { - if (strName.size()>6 && strName.substr(strName.size() - 6, 6) == ".onion") { - std::vector<unsigned char> vchAddr = DecodeBase32(strName.substr(0, strName.size() - 6).c_str()); - if (vchAddr.size() != ADDR_TORV2_SIZE) { + static const char* suffix{".onion"}; + static constexpr size_t suffix_len{6}; + + if (!ValidAsCString(str) || str.size() <= suffix_len || + str.substr(str.size() - suffix_len) != suffix) { + return false; + } + + bool invalid; + const auto& input = DecodeBase32(str.substr(0, str.size() - suffix_len).c_str(), &invalid); + + if (invalid) { + return false; + } + + switch (input.size()) { + case ADDR_TORV2_SIZE: + m_net = NET_ONION; + m_addr.assign(input.begin(), input.end()); + return true; + case torv3::TOTAL_LEN: { + Span<const uint8_t> input_pubkey{input.data(), ADDR_TORV3_SIZE}; + Span<const uint8_t> input_checksum{input.data() + ADDR_TORV3_SIZE, torv3::CHECKSUM_LEN}; + Span<const uint8_t> input_version{input.data() + ADDR_TORV3_SIZE + torv3::CHECKSUM_LEN, sizeof(torv3::VERSION)}; + + uint8_t calculated_checksum[torv3::CHECKSUM_LEN]; + torv3::Checksum(input_pubkey, calculated_checksum); + + if (input_checksum != calculated_checksum || input_version != torv3::VERSION) { return false; } + m_net = NET_ONION; - m_addr.assign(vchAddr.begin(), vchAddr.end()); + m_addr.assign(input_pubkey.begin(), input_pubkey.end()); return true; } + } + return false; } @@ -235,13 +384,21 @@ bool CNetAddr::IsHeNet() const } /** - * @returns Whether or not this is a dummy address that maps an onion address - * into IPv6. - * + * Check whether this object represents a TOR address. * @see CNetAddr::SetSpecial(const std::string &) */ bool CNetAddr::IsTor() const { return m_net == NET_ONION; } +/** + * Check whether this object represents an I2P address. + */ +bool CNetAddr::IsI2P() const { return m_net == NET_I2P; } + +/** + * Check whether this object represents a CJDNS address. + */ +bool CNetAddr::IsCJDNS() const { return m_net == NET_CJDNS; } + bool CNetAddr::IsLocal() const { // IPv4 loopback (127.0.0.0/8 or 0.0.0.0/8) @@ -328,28 +485,72 @@ enum Network CNetAddr::GetNetwork() const return m_net; } +static std::string IPv6ToString(Span<const uint8_t> a) +{ + assert(a.size() == ADDR_IPV6_SIZE); + // clang-format off + return strprintf("%x:%x:%x:%x:%x:%x:%x:%x", + ReadBE16(&a[0]), + ReadBE16(&a[2]), + ReadBE16(&a[4]), + ReadBE16(&a[6]), + ReadBE16(&a[8]), + ReadBE16(&a[10]), + ReadBE16(&a[12]), + ReadBE16(&a[14])); + // clang-format on +} + std::string CNetAddr::ToStringIP() const { - if (IsTor()) - return EncodeBase32(m_addr) + ".onion"; - if (IsInternal()) + switch (m_net) { + case NET_IPV4: + case NET_IPV6: { + CService serv(*this, 0); + struct sockaddr_storage sockaddr; + socklen_t socklen = sizeof(sockaddr); + if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) { + char name[1025] = ""; + if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, + sizeof(name), nullptr, 0, NI_NUMERICHOST)) + return std::string(name); + } + if (m_net == NET_IPV4) { + return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], m_addr[3]); + } + return IPv6ToString(m_addr); + } + case NET_ONION: + switch (m_addr.size()) { + case ADDR_TORV2_SIZE: + return EncodeBase32(m_addr) + ".onion"; + case ADDR_TORV3_SIZE: { + + uint8_t checksum[torv3::CHECKSUM_LEN]; + torv3::Checksum(m_addr, checksum); + + // TORv3 onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion" + prevector<torv3::TOTAL_LEN, uint8_t> address{m_addr.begin(), m_addr.end()}; + address.insert(address.end(), checksum, checksum + torv3::CHECKSUM_LEN); + address.insert(address.end(), torv3::VERSION, torv3::VERSION + sizeof(torv3::VERSION)); + + return EncodeBase32(address) + ".onion"; + } + default: + assert(false); + } + case NET_I2P: + return EncodeBase32(m_addr, false /* don't pad with = */) + ".b32.i2p"; + case NET_CJDNS: + return IPv6ToString(m_addr); + case NET_INTERNAL: return EncodeBase32(m_addr) + ".internal"; - CService serv(*this, 0); - struct sockaddr_storage sockaddr; - socklen_t socklen = sizeof(sockaddr); - if (serv.GetSockAddr((struct sockaddr*)&sockaddr, &socklen)) { - char name[1025] = ""; - if (!getnameinfo((const struct sockaddr*)&sockaddr, socklen, name, sizeof(name), nullptr, 0, NI_NUMERICHOST)) - return std::string(name); - } - if (IsIPv4()) - return strprintf("%u.%u.%u.%u", m_addr[0], m_addr[1], m_addr[2], m_addr[3]); - assert(IsIPv6()); - return strprintf("%x:%x:%x:%x:%x:%x:%x:%x", - m_addr[0] << 8 | m_addr[1], m_addr[2] << 8 | m_addr[3], - m_addr[4] << 8 | m_addr[5], m_addr[6] << 8 | m_addr[7], - m_addr[8] << 8 | m_addr[9], m_addr[10] << 8 | m_addr[11], - m_addr[12] << 8 | m_addr[13], m_addr[14] << 8 | m_addr[15]); + case NET_UNROUTABLE: // m_net is never and should not be set to NET_UNROUTABLE + case NET_MAX: // m_net is never and should not be set to NET_MAX + assert(false); + } // no default case, so the compiler can warn about missing cases + + assert(false); } std::string CNetAddr::ToString() const @@ -428,21 +629,22 @@ uint32_t CNetAddr::GetLinkedIPv4() const assert(false); } -uint32_t CNetAddr::GetNetClass() const { - uint32_t net_class = NET_IPV6; - if (IsLocal()) { - net_class = 255; - } +uint32_t CNetAddr::GetNetClass() const +{ + // Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers expect that. + + // Check for "internal" first because such addresses are also !IsRoutable() + // and we don't want to return NET_UNROUTABLE in that case. if (IsInternal()) { - net_class = NET_INTERNAL; - } else if (!IsRoutable()) { - net_class = NET_UNROUTABLE; - } else if (HasLinkedIPv4()) { - net_class = NET_IPV4; - } else if (IsTor()) { - net_class = NET_ONION; + return NET_INTERNAL; } - return net_class; + if (!IsRoutable()) { + return NET_UNROUTABLE; + } + if (HasLinkedIPv4()) { + return NET_IPV4; + } + return m_net; } uint32_t CNetAddr::GetMappedAS(const std::vector<bool> &asmap) const { @@ -517,7 +719,7 @@ std::vector<unsigned char> CNetAddr::GetGroup(const std::vector<bool> &asmap) co vchRet.push_back((ipv4 >> 24) & 0xFF); vchRet.push_back((ipv4 >> 16) & 0xFF); return vchRet; - } else if (IsTor()) { + } else if (IsTor() || IsI2P() || IsCJDNS()) { nBits = 4; } else if (IsHeNet()) { // for he.net, use /36 groups @@ -742,7 +944,7 @@ std::string CService::ToStringPort() const std::string CService::ToStringIPPort() const { - if (IsIPv4() || IsTor() || IsInternal()) { + if (IsIPv4() || IsTor() || IsI2P() || IsInternal()) { return ToStringIP() + ":" + ToStringPort(); } else { return "[" + ToStringIP() + "]:" + ToStringPort(); diff --git a/src/netaddress.h b/src/netaddress.h index d00f5a6f55..59f1b87ad3 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -13,13 +13,25 @@ #include <compat.h> #include <prevector.h> #include <serialize.h> +#include <tinyformat.h> +#include <util/strencodings.h> +#include <util/string.h> #include <array> #include <cstdint> +#include <ios> #include <string> #include <vector> /** + * A flag that is ORed into the protocol version to designate that addresses + * should be serialized in (unserialized from) v2 format (BIP155). + * Make sure that this does not collide with any of the values in `version.h` + * or with `SERIALIZE_TRANSACTION_NO_WITNESS`. + */ +static const int ADDRV2_FORMAT = 0x20000000; + +/** * A network type. * @note An address may belong to more than one network, for example `10.0.0.1` * belongs to both `NET_UNROUTABLE` and `NET_IPV4`. @@ -39,9 +51,15 @@ enum Network /// IPv6 NET_IPV6, - /// TORv2 + /// TOR (v2 or v3) NET_ONION, + /// I2P + NET_I2P, + + /// CJDNS + NET_CJDNS, + /// A set of addresses that represent the hash of a string or FQDN. We use /// them in CAddrMan to keep track of which DNS seeds were used. NET_INTERNAL, @@ -82,6 +100,16 @@ static constexpr size_t ADDR_IPV6_SIZE = 16; /// Size of TORv2 address (in bytes). static constexpr size_t ADDR_TORV2_SIZE = 10; +/// Size of TORv3 address (in bytes). This is the length of just the address +/// as used in BIP155, without the checksum and the version byte. +static constexpr size_t ADDR_TORV3_SIZE = 32; + +/// Size of I2P address (in bytes). +static constexpr size_t ADDR_I2P_SIZE = 32; + +/// Size of CJDNS address (in bytes). +static constexpr size_t ADDR_CJDNS_SIZE = 16; + /// Size of "internal" (NET_INTERNAL) address (in bytes). static constexpr size_t ADDR_INTERNAL_SIZE = 10; @@ -139,6 +167,8 @@ class CNetAddr bool IsRFC6145() const; // IPv6 IPv4-translated address (::FFFF:0:0:0/96) (actually defined in RFC2765) bool IsHeNet() const; // IPv6 Hurricane Electric - https://he.net (2001:0470::/36) bool IsTor() const; + bool IsI2P() const; + bool IsCJDNS() const; bool IsLocal() const; bool IsRoutable() const; bool IsInternal() const; @@ -177,7 +207,11 @@ class CNetAddr template <typename Stream> void Serialize(Stream& s) const { - SerializeV1Stream(s); + if (s.GetVersion() & ADDRV2_FORMAT) { + SerializeV2Stream(s); + } else { + SerializeV1Stream(s); + } } /** @@ -186,20 +220,58 @@ class CNetAddr template <typename Stream> void Unserialize(Stream& s) { - UnserializeV1Stream(s); + if (s.GetVersion() & ADDRV2_FORMAT) { + UnserializeV2Stream(s); + } else { + UnserializeV1Stream(s); + } } friend class CSubNet; private: /** + * BIP155 network ids recognized by this software. + */ + enum BIP155Network : uint8_t { + IPV4 = 1, + IPV6 = 2, + TORV2 = 3, + TORV3 = 4, + I2P = 5, + CJDNS = 6, + }; + + /** * Size of CNetAddr when serialized as ADDRv1 (pre-BIP155) (in bytes). */ static constexpr size_t V1_SERIALIZATION_SIZE = ADDR_IPV6_SIZE; /** + * Maximum size of an address as defined in BIP155 (in bytes). + * This is only the size of the address, not the entire CNetAddr object + * when serialized. + */ + static constexpr size_t MAX_ADDRV2_SIZE = 512; + + /** + * Get the BIP155 network id of this address. + * Must not be called for IsInternal() objects. + * @returns BIP155 network id + */ + BIP155Network GetBIP155Network() const; + + /** + * Set `m_net` from the provided BIP155 network id and size after validation. + * @retval true the network was recognized, is valid and `m_net` was set + * @retval false not recognised (from future?) and should be silently ignored + * @throws std::ios_base::failure if the network is one of the BIP155 founding + * networks (id 1..6) with wrong address size. + */ + bool SetNetFromBIP155Network(uint8_t possible_bip155_net, size_t address_size); + + /** * Serialize in pre-ADDRv2/BIP155 format to an array. - * Some addresses (e.g. TORv3) cannot be serialized in pre-BIP155 format. */ void SerializeV1Array(uint8_t (&arr)[V1_SERIALIZATION_SIZE]) const { @@ -217,6 +289,9 @@ class CNetAddr memcpy(arr + prefix_size, m_addr.data(), m_addr.size()); return; case NET_ONION: + if (m_addr.size() == ADDR_TORV3_SIZE) { + break; + } prefix_size = sizeof(TORV2_IN_IPV6_PREFIX); assert(prefix_size + m_addr.size() == sizeof(arr)); memcpy(arr, TORV2_IN_IPV6_PREFIX.data(), prefix_size); @@ -228,17 +303,21 @@ class CNetAddr memcpy(arr, INTERNAL_IN_IPV6_PREFIX.data(), prefix_size); memcpy(arr + prefix_size, m_addr.data(), m_addr.size()); return; + case NET_I2P: + break; + case NET_CJDNS: + break; case NET_UNROUTABLE: case NET_MAX: assert(false); } // no default case, so the compiler can warn about missing cases - assert(false); + // Serialize TORv3, I2P and CJDNS as all-zeros. + memset(arr, 0x0, V1_SERIALIZATION_SIZE); } /** * Serialize in pre-ADDRv2/BIP155 format to a stream. - * Some addresses (e.g. TORv3) cannot be serialized in pre-BIP155 format. */ template <typename Stream> void SerializeV1Stream(Stream& s) const @@ -251,6 +330,25 @@ class CNetAddr } /** + * Serialize as ADDRv2 / BIP155. + */ + template <typename Stream> + void SerializeV2Stream(Stream& s) const + { + if (IsInternal()) { + // Serialize NET_INTERNAL as embedded in IPv6. We need to + // serialize such addresses from addrman. + s << static_cast<uint8_t>(BIP155Network::IPV6); + s << COMPACTSIZE(ADDR_IPV6_SIZE); + SerializeV1Stream(s); + return; + } + + s << static_cast<uint8_t>(GetBIP155Network()); + s << m_addr; + } + + /** * Unserialize from a pre-ADDRv2/BIP155 format from an array. */ void UnserializeV1Array(uint8_t (&arr)[V1_SERIALIZATION_SIZE]) @@ -272,6 +370,65 @@ class CNetAddr UnserializeV1Array(serialized); } + + /** + * Unserialize from a ADDRv2 / BIP155 format. + */ + template <typename Stream> + void UnserializeV2Stream(Stream& s) + { + uint8_t bip155_net; + s >> bip155_net; + + size_t address_size; + s >> COMPACTSIZE(address_size); + + if (address_size > MAX_ADDRV2_SIZE) { + throw std::ios_base::failure(strprintf( + "Address too long: %u > %u", address_size, MAX_ADDRV2_SIZE)); + } + + scopeId = 0; + + if (SetNetFromBIP155Network(bip155_net, address_size)) { + m_addr.resize(address_size); + s >> MakeSpan(m_addr); + + if (m_net != NET_IPV6) { + return; + } + + // Do some special checks on IPv6 addresses. + + // Recognize NET_INTERNAL embedded in IPv6, such addresses are not + // gossiped but could be coming from addrman, when unserializing from + // disk. + if (HasPrefix(m_addr, INTERNAL_IN_IPV6_PREFIX)) { + m_net = NET_INTERNAL; + memmove(m_addr.data(), m_addr.data() + INTERNAL_IN_IPV6_PREFIX.size(), + ADDR_INTERNAL_SIZE); + m_addr.resize(ADDR_INTERNAL_SIZE); + return; + } + + if (!HasPrefix(m_addr, IPV4_IN_IPV6_PREFIX) && + !HasPrefix(m_addr, TORV2_IN_IPV6_PREFIX)) { + return; + } + + // IPv4 and TORv2 are not supposed to be embedded in IPv6 (like in V1 + // encoding). Unserialize as !IsValid(), thus ignoring them. + } else { + // If we receive an unknown BIP155 network id (from the future?) then + // ignore the address - unserialize as !IsValid(). + s.ignore(address_size); + } + + // Mimic a default-constructed CNetAddr object which is !IsValid() and thus + // will not be gossiped, but continue reading next addresses from the stream. + m_net = NET_IPV6; + m_addr.assign(ADDR_IPV6_SIZE, 0x0); + } }; class CSubNet diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 25458eead2..0f31093dbb 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -55,7 +55,7 @@ private: // Sum the total feerate of all tx's in each bucket // Track the historical moving average of this total over blocks - std::vector<double> avg; + std::vector<double> m_feerate_avg; // Combine the conf counts with tx counts to calculate the confirmation % for each Y,X // Combine the total value with the tx counts to calculate the avg feerate per bucket @@ -114,12 +114,10 @@ public: * @param confTarget target number of confirmations * @param sufficientTxVal required average number of transactions per block in a bucket range * @param minSuccess the success probability we require - * @param requireGreater return the lowest feerate such that all higher values pass minSuccess OR - * return the highest feerate such that all lower values fail minSuccess * @param nBlockHeight the current block height */ double EstimateMedianVal(int confTarget, double sufficientTxVal, - double minSuccess, bool requireGreater, unsigned int nBlockHeight, + double minSuccess, unsigned int nBlockHeight, EstimationResult *result = nullptr) const; /** Return the max number of confirms we're tracking */ @@ -139,22 +137,18 @@ public: TxConfirmStats::TxConfirmStats(const std::vector<double>& defaultBuckets, const std::map<double, unsigned int>& defaultBucketMap, unsigned int maxPeriods, double _decay, unsigned int _scale) - : buckets(defaultBuckets), bucketMap(defaultBucketMap) + : buckets(defaultBuckets), bucketMap(defaultBucketMap), decay(_decay), scale(_scale) { - decay = _decay; assert(_scale != 0 && "_scale must be non-zero"); - scale = _scale; confAvg.resize(maxPeriods); - for (unsigned int i = 0; i < maxPeriods; i++) { - confAvg[i].resize(buckets.size()); - } failAvg.resize(maxPeriods); for (unsigned int i = 0; i < maxPeriods; i++) { + confAvg[i].resize(buckets.size()); failAvg[i].resize(buckets.size()); } txCtAvg.resize(buckets.size()); - avg.resize(buckets.size()); + m_feerate_avg.resize(buckets.size()); resizeInMemoryCounters(buckets.size()); } @@ -172,68 +166,61 @@ void TxConfirmStats::resizeInMemoryCounters(size_t newbuckets) { void TxConfirmStats::ClearCurrent(unsigned int nBlockHeight) { for (unsigned int j = 0; j < buckets.size(); j++) { - oldUnconfTxs[j] += unconfTxs[nBlockHeight%unconfTxs.size()][j]; + oldUnconfTxs[j] += unconfTxs[nBlockHeight % unconfTxs.size()][j]; unconfTxs[nBlockHeight%unconfTxs.size()][j] = 0; } } -void TxConfirmStats::Record(int blocksToConfirm, double val) +void TxConfirmStats::Record(int blocksToConfirm, double feerate) { // blocksToConfirm is 1-based if (blocksToConfirm < 1) return; - int periodsToConfirm = (blocksToConfirm + scale - 1)/scale; - unsigned int bucketindex = bucketMap.lower_bound(val)->second; + int periodsToConfirm = (blocksToConfirm + scale - 1) / scale; + unsigned int bucketindex = bucketMap.lower_bound(feerate)->second; for (size_t i = periodsToConfirm; i <= confAvg.size(); i++) { confAvg[i - 1][bucketindex]++; } txCtAvg[bucketindex]++; - avg[bucketindex] += val; + m_feerate_avg[bucketindex] += feerate; } void TxConfirmStats::UpdateMovingAverages() { + assert(confAvg.size() == failAvg.size()); for (unsigned int j = 0; j < buckets.size(); j++) { - for (unsigned int i = 0; i < confAvg.size(); i++) - confAvg[i][j] = confAvg[i][j] * decay; - for (unsigned int i = 0; i < failAvg.size(); i++) - failAvg[i][j] = failAvg[i][j] * decay; - avg[j] = avg[j] * decay; - txCtAvg[j] = txCtAvg[j] * decay; + for (unsigned int i = 0; i < confAvg.size(); i++) { + confAvg[i][j] *= decay; + failAvg[i][j] *= decay; + } + m_feerate_avg[j] *= decay; + txCtAvg[j] *= decay; } } // returns -1 on error conditions double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, - double successBreakPoint, bool requireGreater, - unsigned int nBlockHeight, EstimationResult *result) const + double successBreakPoint, unsigned int nBlockHeight, + EstimationResult *result) const { // Counters for a bucket (or range of buckets) double nConf = 0; // Number of tx's confirmed within the confTarget double totalNum = 0; // Total number of tx's that were ever confirmed int extraNum = 0; // Number of tx's still in mempool for confTarget or longer double failNum = 0; // Number of tx's that were never confirmed but removed from the mempool after confTarget - int periodTarget = (confTarget + scale - 1)/scale; - - int maxbucketindex = buckets.size() - 1; - - // requireGreater means we are looking for the lowest feerate such that all higher - // values pass, so we start at maxbucketindex (highest feerate) and look at successively - // smaller buckets until we reach failure. Otherwise, we are looking for the highest - // feerate such that all lower values fail, and we go in the opposite direction. - unsigned int startbucket = requireGreater ? maxbucketindex : 0; - int step = requireGreater ? -1 : 1; + const int periodTarget = (confTarget + scale - 1) / scale; + const int maxbucketindex = buckets.size() - 1; // We'll combine buckets until we have enough samples. // The near and far variables will define the range we've combined // The best variables are the last range we saw which still had a high // enough confirmation rate to count as success. // The cur variables are the current range we're counting. - unsigned int curNearBucket = startbucket; - unsigned int bestNearBucket = startbucket; - unsigned int curFarBucket = startbucket; - unsigned int bestFarBucket = startbucket; + unsigned int curNearBucket = maxbucketindex; + unsigned int bestNearBucket = maxbucketindex; + unsigned int curFarBucket = maxbucketindex; + unsigned int bestFarBucket = maxbucketindex; bool foundAnswer = false; unsigned int bins = unconfTxs.size(); @@ -242,8 +229,8 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, EstimatorBucket passBucket; EstimatorBucket failBucket; - // Start counting from highest(default) or lowest feerate transactions - for (int bucket = startbucket; bucket >= 0 && bucket <= maxbucketindex; bucket += step) { + // Start counting from highest feerate transactions + for (int bucket = maxbucketindex; bucket >= 0; --bucket) { if (newBucketRange) { curNearBucket = bucket; newBucketRange = false; @@ -253,7 +240,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, totalNum += txCtAvg[bucket]; failNum += failAvg[periodTarget - 1][bucket]; for (unsigned int confct = confTarget; confct < GetMaxConfirms(); confct++) - extraNum += unconfTxs[(nBlockHeight - confct)%bins][bucket]; + extraNum += unconfTxs[(nBlockHeight - confct) % bins][bucket]; extraNum += oldUnconfTxs[bucket]; // If we have enough transaction data points in this range of buckets, // we can test for success @@ -263,7 +250,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, double curPct = nConf / (totalNum + failNum + extraNum); // Check to see if we are no longer getting confirmed at the success rate - if ((requireGreater && curPct < successBreakPoint) || (!requireGreater && curPct > successBreakPoint)) { + if (curPct < successBreakPoint) { if (passing == true) { // First time we hit a failure record the failed bucket unsigned int failMinBucket = std::min(curNearBucket, curFarBucket); @@ -317,7 +304,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, if (txCtAvg[j] < txSum) txSum -= txCtAvg[j]; else { // we're in the right bucket - median = avg[j] / txCtAvg[j]; + median = m_feerate_avg[j] / txCtAvg[j]; break; } } @@ -338,13 +325,22 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, failBucket.leftMempool = failNum; } - LogPrint(BCLog::ESTIMATEFEE, "FeeEst: %d %s%.0f%% decay %.5f: feerate: %g from (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", - confTarget, requireGreater ? ">" : "<", 100.0 * successBreakPoint, decay, + float passed_within_target_perc = 0.0; + float failed_within_target_perc = 0.0; + if ((passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool)) { + passed_within_target_perc = 100 * passBucket.withinTarget / (passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool); + } + if ((failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool)) { + failed_within_target_perc = 100 * failBucket.withinTarget / (failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool); + } + + LogPrint(BCLog::ESTIMATEFEE, "FeeEst: %d > %.0f%% decay %.5f: feerate: %g from (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", + confTarget, 100.0 * successBreakPoint, decay, median, passBucket.start, passBucket.end, - 100 * passBucket.withinTarget / (passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool), + passed_within_target_perc, passBucket.withinTarget, passBucket.totalConfirmed, passBucket.inMempool, passBucket.leftMempool, failBucket.start, failBucket.end, - 100 * failBucket.withinTarget / (failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool), + failed_within_target_perc, failBucket.withinTarget, failBucket.totalConfirmed, failBucket.inMempool, failBucket.leftMempool); @@ -361,7 +357,7 @@ void TxConfirmStats::Write(CAutoFile& fileout) const { fileout << decay; fileout << scale; - fileout << avg; + fileout << m_feerate_avg; fileout << txCtAvg; fileout << confAvg; fileout << failAvg; @@ -384,8 +380,8 @@ void TxConfirmStats::Read(CAutoFile& filein, int nFileVersion, size_t numBuckets throw std::runtime_error("Corrupt estimates file. Scale must be non-zero"); } - filein >> avg; - if (avg.size() != numBuckets) { + filein >> m_feerate_avg; + if (m_feerate_avg.size() != numBuckets) { throw std::runtime_error("Corrupt estimates file. Mismatch in feerate average bucket count"); } filein >> txCtAvg; @@ -664,7 +660,7 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr if (successThreshold > 1) return CFeeRate(0); - double median = stats->EstimateMedianVal(confTarget, sufficientTxs, successThreshold, true, nBestSeenHeight, result); + double median = stats->EstimateMedianVal(confTarget, sufficientTxs, successThreshold, nBestSeenHeight, result); if (median < 0) return CFeeRate(0); @@ -725,26 +721,26 @@ double CBlockPolicyEstimator::estimateCombinedFee(unsigned int confTarget, doubl if (confTarget >= 1 && confTarget <= longStats->GetMaxConfirms()) { // Find estimate from shortest time horizon possible if (confTarget <= shortStats->GetMaxConfirms()) { // short horizon - estimate = shortStats->EstimateMedianVal(confTarget, SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, result); + estimate = shortStats->EstimateMedianVal(confTarget, SUFFICIENT_TXS_SHORT, successThreshold, nBestSeenHeight, result); } else if (confTarget <= feeStats->GetMaxConfirms()) { // medium horizon - estimate = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result); + estimate = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, result); } else { // long horizon - estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result); + estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, result); } if (checkShorterHorizon) { EstimationResult tempResult; // If a lower confTarget from a more recent horizon returns a lower answer use it. if (confTarget > feeStats->GetMaxConfirms()) { - double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, &tempResult); + double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, &tempResult); if (medMax > 0 && (estimate == -1 || medMax < estimate)) { estimate = medMax; if (result) *result = tempResult; } } if (confTarget > shortStats->GetMaxConfirms()) { - double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, &tempResult); + double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, nBestSeenHeight, &tempResult); if (shortMax > 0 && (estimate == -1 || shortMax < estimate)) { estimate = shortMax; if (result) *result = tempResult; @@ -763,10 +759,10 @@ double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget, double estimate = -1; EstimationResult tempResult; if (doubleTarget <= shortStats->GetMaxConfirms()) { - estimate = feeStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight, result); + estimate = feeStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, nBestSeenHeight, result); } if (doubleTarget <= feeStats->GetMaxConfirms()) { - double longEstimate = longStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight, &tempResult); + double longEstimate = longStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, nBestSeenHeight, &tempResult); if (longEstimate > estimate) { estimate = longEstimate; if (result) *result = tempResult; diff --git a/src/policy/fees.h b/src/policy/fees.h index e79dbc9868..8ea8816dc3 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -138,9 +138,9 @@ private: /** Decay of .962 is a half-life of 18 blocks or about 3 hours */ static constexpr double SHORT_DECAY = .962; - /** Decay of .998 is a half-life of 144 blocks or about 1 day */ + /** Decay of .9952 is a half-life of 144 blocks or about 1 day */ static constexpr double MED_DECAY = .9952; - /** Decay of .9995 is a half-life of 1008 blocks or about 1 week */ + /** Decay of .99931 is a half-life of 1008 blocks or about 1 week */ static constexpr double LONG_DECAY = .99931; /** Require greater than 60% of X feerate transactions to be confirmed within Y/2 blocks*/ diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 544bab6d9b..77cb1781a4 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -14,6 +14,12 @@ #include <tuple> +/** + * A flag that is ORed into the protocol version to designate that a transaction + * should be (un)serialized without witness data. + * Make sure that this does not collide with any of the values in `version.h` + * or with `ADDRV2_FORMAT`. + */ static const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000; /** An outpoint - a combination of a transaction hash and an index n into its vout */ diff --git a/src/protocol.cpp b/src/protocol.cpp index 48ca0c6df6..84b6e96aee 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -84,9 +84,9 @@ const static std::string allNetMessageTypes[] = { }; const static std::vector<std::string> allNetMessageTypesVec(allNetMessageTypes, allNetMessageTypes+ARRAYLEN(allNetMessageTypes)); -CMessageHeader::CMessageHeader(const MessageStartChars& pchMessageStartIn) +CMessageHeader::CMessageHeader() { - memcpy(pchMessageStart, pchMessageStartIn, MESSAGE_START_SIZE); + memset(pchMessageStart, 0, MESSAGE_START_SIZE); memset(pchCommand, 0, sizeof(pchCommand)); nMessageSize = -1; memset(pchChecksum, 0, CHECKSUM_SIZE); @@ -111,31 +111,20 @@ std::string CMessageHeader::GetCommand() const return std::string(pchCommand, pchCommand + strnlen(pchCommand, COMMAND_SIZE)); } -bool CMessageHeader::IsValid(const MessageStartChars& pchMessageStartIn) const +bool CMessageHeader::IsCommandValid() const { - // Check start string - if (memcmp(pchMessageStart, pchMessageStartIn, MESSAGE_START_SIZE) != 0) - return false; - // Check the command string for errors - for (const char* p1 = pchCommand; p1 < pchCommand + COMMAND_SIZE; p1++) - { - if (*p1 == 0) - { + for (const char* p1 = pchCommand; p1 < pchCommand + COMMAND_SIZE; ++p1) { + if (*p1 == 0) { // Must be all zeros after the first zero - for (; p1 < pchCommand + COMMAND_SIZE; p1++) - if (*p1 != 0) + for (; p1 < pchCommand + COMMAND_SIZE; ++p1) { + if (*p1 != 0) { return false; - } - else if (*p1 < ' ' || *p1 > 0x7E) + } + } + } else if (*p1 < ' ' || *p1 > 0x7E) { return false; - } - - // Message size - if (nMessageSize > MAX_SIZE) - { - LogPrintf("CMessageHeader::IsValid(): (%s, %u bytes) nMessageSize > MAX_SIZE\n", GetCommand(), nMessageSize); - return false; + } } return true; diff --git a/src/protocol.h b/src/protocol.h index 7fb84cddf1..9a44a1626c 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -37,7 +37,7 @@ public: static constexpr size_t HEADER_SIZE = MESSAGE_START_SIZE + COMMAND_SIZE + MESSAGE_SIZE_SIZE + CHECKSUM_SIZE; typedef unsigned char MessageStartChars[MESSAGE_START_SIZE]; - explicit CMessageHeader(const MessageStartChars& pchMessageStartIn); + explicit CMessageHeader(); /** Construct a P2P message header from message-start characters, a command and the size of the message. * @note Passing in a `pszCommand` longer than COMMAND_SIZE will result in a run-time assertion error. @@ -45,7 +45,7 @@ public: CMessageHeader(const MessageStartChars& pchMessageStartIn, const char* pszCommand, unsigned int nMessageSizeIn); std::string GetCommand() const; - bool IsValid(const MessageStartChars& messageStart) const; + bool IsCommandValid() const; SERIALIZE_METHODS(CMessageHeader, obj) { READWRITE(obj.pchMessageStart, obj.pchCommand, obj.nMessageSize, obj.pchChecksum); } diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 11a2d23d85..def21b119e 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -116,7 +116,9 @@ static RPCHelpMan getpeerinfo() {RPCResult::Type::NUM, "version", "The peer version, such as 70001"}, {RPCResult::Type::STR, "subver", "The string version"}, {RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"}, - {RPCResult::Type::BOOL, "addnode", "Whether connection was due to addnode/-connect or if it was an automatic/inbound connection"}, + {RPCResult::Type::BOOL, "addnode", "Whether connection was due to addnode/-connect or if it was an automatic/inbound connection\n" + "(DEPRECATED, returned only if the config option -deprecatedrpc=getpeerinfo_addnode is passed)"}, + {RPCResult::Type::STR, "connection_type", "Type of connection: \n" + Join(CONNECTION_TYPE_DOC, ",\n") + "."}, {RPCResult::Type::NUM, "startingheight", "The starting height (block) of the peer"}, {RPCResult::Type::NUM, "banscore", "The ban score (DEPRECATED, returned only if config option -deprecatedrpc=banscore is passed)"}, {RPCResult::Type::NUM, "synced_headers", "The last header we have in common with this peer"}, @@ -196,7 +198,10 @@ static RPCHelpMan getpeerinfo() // their ver message. obj.pushKV("subver", stats.cleanSubVer); obj.pushKV("inbound", stats.fInbound); - obj.pushKV("addnode", stats.m_manual_connection); + if (IsDeprecatedRPCEnabled("getpeerinfo_addnode")) { + // addnode is deprecated in v0.21 for removal in v0.22 + obj.pushKV("addnode", stats.m_manual_connection); + } obj.pushKV("startingheight", stats.nStartingHeight); if (fStateStats) { if (IsDeprecatedRPCEnabled("banscore")) { @@ -232,6 +237,7 @@ static RPCHelpMan getpeerinfo() recvPerMsgCmd.pushKV(i.first, i.second); } obj.pushKV("bytesrecv_per_msg", recvPerMsgCmd); + obj.pushKV("connection_type", stats.m_conn_type_string); ret.push_back(obj); } diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index cfe4575090..8dfbead0e4 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -21,14 +21,16 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf) { - if (outputs_in.isNull()) + if (outputs_in.isNull()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output argument must be non-null"); + } UniValue inputs; - if (inputs_in.isNull()) + if (inputs_in.isNull()) { inputs = UniValue::VARR; - else + } else { inputs = inputs_in.get_array(); + } const bool outputs_is_obj = outputs_in.isObject(); UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 7b2457a5e3..50a6192476 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1375,7 +1375,7 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn if ((nHashType & 0x1f) == SIGHASH_SINGLE) { if (nIn >= txTo.vout.size()) { // nOut out of range - return UINT256_ONE(); + return uint256::ONE; } } diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp index eedab30576..d519eca859 100644 --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -13,10 +13,13 @@ BOOST_AUTO_TEST_CASE(base32_testvectors) { static const std::string vstrIn[] = {"","f","fo","foo","foob","fooba","foobar"}; static const std::string vstrOut[] = {"","my======","mzxq====","mzxw6===","mzxw6yq=","mzxw6ytb","mzxw6ytboi======"}; + static const std::string vstrOutNoPadding[] = {"","my","mzxq","mzxw6","mzxw6yq","mzxw6ytb","mzxw6ytboi"}; for (unsigned int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++) { std::string strEnc = EncodeBase32(vstrIn[i]); BOOST_CHECK_EQUAL(strEnc, vstrOut[i]); + strEnc = EncodeBase32(vstrIn[i], false); + BOOST_CHECK_EQUAL(strEnc, vstrOutNoPadding[i]); std::string strDec = DecodeBase32(vstrOut[i]); BOOST_CHECK_EQUAL(strDec, vstrIn[i]); } diff --git a/src/test/fuzz/deserialize.cpp b/src/test/fuzz/deserialize.cpp index 54793c890f..b799d3b43b 100644 --- a/src/test/fuzz/deserialize.cpp +++ b/src/test/fuzz/deserialize.cpp @@ -189,10 +189,9 @@ void test_one_input(const std::vector<uint8_t>& buffer) DeserializeFromFuzzingInput(buffer, s); AssertEqualAfterSerializeDeserialize(s); #elif MESSAGEHEADER_DESERIALIZE - const CMessageHeader::MessageStartChars pchMessageStart = {0x00, 0x00, 0x00, 0x00}; - CMessageHeader mh(pchMessageStart); + CMessageHeader mh; DeserializeFromFuzzingInput(buffer, mh); - (void)mh.IsValid(pchMessageStart); + (void)mh.IsCommandValid(); #elif ADDRESS_DESERIALIZE CAddress a; DeserializeFromFuzzingInput(buffer, a); diff --git a/src/test/fuzz/p2p_transport_deserializer.cpp b/src/test/fuzz/p2p_transport_deserializer.cpp index 6fba2bfaba..7e216e16fe 100644 --- a/src/test/fuzz/p2p_transport_deserializer.cpp +++ b/src/test/fuzz/p2p_transport_deserializer.cpp @@ -19,7 +19,8 @@ void initialize() void test_one_input(const std::vector<uint8_t>& buffer) { - V1TransportDeserializer deserializer{Params().MessageStart(), SER_NETWORK, INIT_PROTO_VERSION}; + // Construct deserializer, with a dummy NodeId + V1TransportDeserializer deserializer{Params(), (NodeId)0, SER_NETWORK, INIT_PROTO_VERSION}; const char* pch = (const char*)buffer.data(); size_t n_bytes = buffer.size(); while (n_bytes > 0) { @@ -31,16 +32,13 @@ void test_one_input(const std::vector<uint8_t>& buffer) n_bytes -= handled; if (deserializer.Complete()) { const std::chrono::microseconds m_time{std::numeric_limits<int64_t>::max()}; - const CNetMessage msg = deserializer.GetMessage(Params().MessageStart(), m_time); - assert(msg.m_command.size() <= CMessageHeader::COMMAND_SIZE); - assert(msg.m_raw_message_size <= buffer.size()); - assert(msg.m_raw_message_size == CMessageHeader::HEADER_SIZE + msg.m_message_size); - assert(msg.m_time == m_time); - if (msg.m_valid_header) { - assert(msg.m_valid_netmagic); - } - if (!msg.m_valid_netmagic) { - assert(!msg.m_valid_header); + uint32_t out_err_raw_size{0}; + Optional<CNetMessage> result{deserializer.GetMessage(m_time, out_err_raw_size)}; + if (result) { + assert(result->m_command.size() <= CMessageHeader::COMMAND_SIZE); + assert(result->m_raw_message_size <= buffer.size()); + assert(result->m_raw_message_size == CMessageHeader::HEADER_SIZE + result->m_message_size); + assert(result->m_time == m_time); } } } diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 62a0dc4241..8686012af7 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -36,17 +36,6 @@ struct MinerTestingSetup : public TestingSetup { BOOST_FIXTURE_TEST_SUITE(miner_tests, MinerTestingSetup) -// BOOST_CHECK_EXCEPTION predicates to check the specific validation error -class HasReason { -public: - explicit HasReason(const std::string& reason) : m_reason(reason) {} - bool operator() (const std::runtime_error& e) const { - return std::string(e.what()).find(m_reason) != std::string::npos; - }; -private: - const std::string m_reason; -}; - static CFeeRate blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE); BlockAssembler MinerTestingSetup::AssemblerForTest(const CChainParams& params) diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index 85ebc89673..261396cd0c 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -10,6 +10,7 @@ #include <net.h> #include <netbase.h> #include <serialize.h> +#include <span.h> #include <streams.h> #include <test/util/setup_common.h> #include <util/memory.h> @@ -20,6 +21,7 @@ #include <boost/test/unit_test.hpp> +#include <ios> #include <memory> #include <string> @@ -245,13 +247,38 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK_EQUAL(addr.ToString(), "1122:3344:5566:7788:9900:aabb:ccdd:eeff"); // TORv2 - addr.SetSpecial("6hzph5hv6337r6p2.onion"); + BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion")); BOOST_REQUIRE(addr.IsValid()); BOOST_REQUIRE(addr.IsTor()); BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); + // TORv3 + const char* torv3_addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"; + BOOST_REQUIRE(addr.SetSpecial(torv3_addr)); + BOOST_REQUIRE(addr.IsValid()); + BOOST_REQUIRE(addr.IsTor()); + + BOOST_CHECK(!addr.IsBindAny()); + BOOST_CHECK_EQUAL(addr.ToString(), torv3_addr); + + // TORv3, broken, with wrong checksum + BOOST_CHECK(!addr.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscsad.onion")); + + // TORv3, broken, with wrong version + BOOST_CHECK(!addr.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscrye.onion")); + + // TORv3, malicious + BOOST_CHECK(!addr.SetSpecial(std::string{ + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd\0wtf.onion", 66})); + + // TOR, bogus length + BOOST_CHECK(!addr.SetSpecial(std::string{"mfrggzak.onion"})); + + // TOR, invalid base32 + BOOST_CHECK(!addr.SetSpecial(std::string{"mf*g zak.onion"})); + // Internal addr.SetInternal("esffpp"); BOOST_REQUIRE(!addr.IsValid()); // "internal" is considered invalid @@ -259,19 +286,286 @@ BOOST_AUTO_TEST_CASE(cnetaddr_basic) BOOST_CHECK(!addr.IsBindAny()); BOOST_CHECK_EQUAL(addr.ToString(), "esffpvrt3wpeaygy.internal"); + + // Totally bogus + BOOST_CHECK(!addr.SetSpecial("totally bogus")); } -BOOST_AUTO_TEST_CASE(cnetaddr_serialize) +BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v1) { CNetAddr addr; CDataStream s(SER_NETWORK, PROTOCOL_VERSION); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000000000000000"); + s.clear(); + + BOOST_REQUIRE(LookupHost("1.2.3.4", addr, false)); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000ffff01020304"); + s.clear(); + + BOOST_REQUIRE(LookupHost("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b", addr, false)); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "1a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b"); + s.clear(); + + BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion")); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "fd87d87eeb43f1f2f3f4f5f6f7f8f9fa"); + s.clear(); + + BOOST_REQUIRE(addr.SetSpecial("pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion")); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "00000000000000000000000000000000"); + s.clear(); + addr.SetInternal("a"); s << addr; BOOST_CHECK_EQUAL(HexStr(s), "fd6b88c08724ca978112ca1bbdcafac2"); s.clear(); } +BOOST_AUTO_TEST_CASE(cnetaddr_serialize_v2) +{ + CNetAddr addr; + CDataStream s(SER_NETWORK, PROTOCOL_VERSION); + // Add ADDRV2_FORMAT to the version so that the CNetAddr + // serialize method produces an address in v2 format. + s.SetVersion(s.GetVersion() | ADDRV2_FORMAT); + + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "021000000000000000000000000000000000"); + s.clear(); + + BOOST_REQUIRE(LookupHost("1.2.3.4", addr, false)); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "010401020304"); + s.clear(); + + BOOST_REQUIRE(LookupHost("1a1b:2a2b:3a3b:4a4b:5a5b:6a6b:7a7b:8a8b", addr, false)); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "02101a1b2a2b3a3b4a4b5a5b6a6b7a7b8a8b"); + s.clear(); + + BOOST_REQUIRE(addr.SetSpecial("6hzph5hv6337r6p2.onion")); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "030af1f2f3f4f5f6f7f8f9fa"); + s.clear(); + + BOOST_REQUIRE(addr.SetSpecial("kpgvmscirrdqpekbqjsvw5teanhatztpp2gl6eee4zkowvwfxwenqaid.onion")); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "042053cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88"); + s.clear(); + + BOOST_REQUIRE(addr.SetInternal("a")); + s << addr; + BOOST_CHECK_EQUAL(HexStr(s), "0210fd6b88c08724ca978112ca1bbdcafac2"); + s.clear(); +} + +BOOST_AUTO_TEST_CASE(cnetaddr_unserialize_v2) +{ + CNetAddr addr; + CDataStream s(SER_NETWORK, PROTOCOL_VERSION); + // Add ADDRV2_FORMAT to the version so that the CNetAddr + // unserialize method expects an address in v2 format. + s.SetVersion(s.GetVersion() | ADDRV2_FORMAT); + + // Valid IPv4. + s << MakeSpan(ParseHex("01" // network type (IPv4) + "04" // address length + "01020304")); // address + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK(addr.IsIPv4()); + BOOST_CHECK_EQUAL(addr.ToString(), "1.2.3.4"); + BOOST_REQUIRE(s.empty()); + + // Invalid IPv4, valid length but address itself is shorter. + s << MakeSpan(ParseHex("01" // network type (IPv4) + "04" // address length + "0102")); // address + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, HasReason("end of data")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Invalid IPv4, with bogus length. + s << MakeSpan(ParseHex("01" // network type (IPv4) + "05" // address length + "01020304")); // address + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, + HasReason("BIP155 IPv4 address with length 5 (should be 4)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Invalid IPv4, with extreme length. + s << MakeSpan(ParseHex("01" // network type (IPv4) + "fd0102" // address length (513 as CompactSize) + "01020304")); // address + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, + HasReason("Address too long: 513 > 512")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Valid IPv6. + s << MakeSpan(ParseHex("02" // network type (IPv6) + "10" // address length + "0102030405060708090a0b0c0d0e0f10")); // address + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK(addr.IsIPv6()); + BOOST_CHECK_EQUAL(addr.ToString(), "102:304:506:708:90a:b0c:d0e:f10"); + BOOST_REQUIRE(s.empty()); + + // Valid IPv6, contains embedded "internal". + s << MakeSpan(ParseHex( + "02" // network type (IPv6) + "10" // address length + "fd6b88c08724ca978112ca1bbdcafac2")); // address: 0xfd + sha256("bitcoin")[0:5] + + // sha256(name)[0:10] + s >> addr; + BOOST_CHECK(addr.IsInternal()); + BOOST_CHECK_EQUAL(addr.ToString(), "zklycewkdo64v6wc.internal"); + BOOST_REQUIRE(s.empty()); + + // Invalid IPv6, with bogus length. + s << MakeSpan(ParseHex("02" // network type (IPv6) + "04" // address length + "00")); // address + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, + HasReason("BIP155 IPv6 address with length 4 (should be 16)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Invalid IPv6, contains embedded IPv4. + s << MakeSpan(ParseHex("02" // network type (IPv6) + "10" // address length + "00000000000000000000ffff01020304")); // address + s >> addr; + BOOST_CHECK(!addr.IsValid()); + BOOST_REQUIRE(s.empty()); + + // Invalid IPv6, contains embedded TORv2. + s << MakeSpan(ParseHex("02" // network type (IPv6) + "10" // address length + "fd87d87eeb430102030405060708090a")); // address + s >> addr; + BOOST_CHECK(!addr.IsValid()); + BOOST_REQUIRE(s.empty()); + + // Valid TORv2. + s << MakeSpan(ParseHex("03" // network type (TORv2) + "0a" // address length + "f1f2f3f4f5f6f7f8f9fa")); // address + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK(addr.IsTor()); + BOOST_CHECK_EQUAL(addr.ToString(), "6hzph5hv6337r6p2.onion"); + BOOST_REQUIRE(s.empty()); + + // Invalid TORv2, with bogus length. + s << MakeSpan(ParseHex("03" // network type (TORv2) + "07" // address length + "00")); // address + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, + HasReason("BIP155 TORv2 address with length 7 (should be 10)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Valid TORv3. + s << MakeSpan(ParseHex("04" // network type (TORv3) + "20" // address length + "79bcc625184b05194975c28b66b66b04" // address + "69f7f6556fb1ac3189a79b40dda32f1f" + )); + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK(addr.IsTor()); + BOOST_CHECK_EQUAL(addr.ToString(), + "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"); + BOOST_REQUIRE(s.empty()); + + // Invalid TORv3, with bogus length. + s << MakeSpan(ParseHex("04" // network type (TORv3) + "00" // address length + "00" // address + )); + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, + HasReason("BIP155 TORv3 address with length 0 (should be 32)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Valid I2P. + s << MakeSpan(ParseHex("05" // network type (I2P) + "20" // address length + "a2894dabaec08c0051a481a6dac88b64" // address + "f98232ae42d4b6fd2fa81952dfe36a87")); + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK_EQUAL(addr.ToString(), + "ukeu3k5oycgaauneqgtnvselmt4yemvoilkln7jpvamvfx7dnkdq.b32.i2p"); + BOOST_REQUIRE(s.empty()); + + // Invalid I2P, with bogus length. + s << MakeSpan(ParseHex("05" // network type (I2P) + "03" // address length + "00" // address + )); + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, + HasReason("BIP155 I2P address with length 3 (should be 32)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Valid CJDNS. + s << MakeSpan(ParseHex("06" // network type (CJDNS) + "10" // address length + "fc000001000200030004000500060007" // address + )); + s >> addr; + BOOST_CHECK(addr.IsValid()); + BOOST_CHECK_EQUAL(addr.ToString(), "fc00:1:2:3:4:5:6:7"); + BOOST_REQUIRE(s.empty()); + + // Invalid CJDNS, with bogus length. + s << MakeSpan(ParseHex("06" // network type (CJDNS) + "01" // address length + "00" // address + )); + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, + HasReason("BIP155 CJDNS address with length 1 (should be 16)")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Unknown, with extreme length. + s << MakeSpan(ParseHex("aa" // network type (unknown) + "fe00000002" // address length (CompactSize's MAX_SIZE) + "01020304050607" // address + )); + BOOST_CHECK_EXCEPTION(s >> addr, std::ios_base::failure, + HasReason("Address too long: 33554432 > 512")); + BOOST_REQUIRE(!s.empty()); // The stream is not consumed on invalid input. + s.clear(); + + // Unknown, with reasonable length. + s << MakeSpan(ParseHex("aa" // network type (unknown) + "04" // address length + "01020304" // address + )); + s >> addr; + BOOST_CHECK(!addr.IsValid()); + BOOST_REQUIRE(s.empty()); + + // Unknown, with zero length. + s << MakeSpan(ParseHex("aa" // network type (unknown) + "00" // address length + "" // address + )); + s >> addr; + BOOST_CHECK(!addr.IsValid()); + BOOST_REQUIRE(s.empty()); +} + // prior to PR #14728, this test triggers an undefined behavior BOOST_AUTO_TEST_CASE(ipv4_peer_with_ipv6_addrMe_test) { diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index c0bb92258b..bc862de78a 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -28,7 +28,7 @@ uint256 static SignatureHashOld(CScript scriptCode, const CTransaction& txTo, un { if (nIn >= txTo.vin.size()) { - return UINT256_ONE(); + return uint256::ONE; } CMutableTransaction txTmp(txTo); @@ -58,7 +58,7 @@ uint256 static SignatureHashOld(CScript scriptCode, const CTransaction& txTo, un unsigned int nOut = nIn; if (nOut >= txTmp.vout.size()) { - return UINT256_ONE(); + return uint256::ONE; } txTmp.vout.resize(nOut+1); for (unsigned int i = 0; i < nOut; i++) diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp index c0ae2f8cf2..ae626d4613 100644 --- a/src/test/uint256_tests.cpp +++ b/src/test/uint256_tests.cpp @@ -278,4 +278,10 @@ BOOST_AUTO_TEST_CASE( operator_with_self ) BOOST_CHECK(v == UintToArith256(uint256S("0"))); } +BOOST_AUTO_TEST_CASE( check_ONE ) +{ + uint256 one = uint256S("0000000000000000000000000000000000000000000000000000000000000001"); + BOOST_CHECK_EQUAL(one, uint256::ONE); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 22f5d6d936..a09c8c122d 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -153,4 +153,20 @@ CBlock getBlock13b8a(); // define an implicit conversion here so that uint256 may be used directly in BOOST_CHECK_* std::ostream& operator<<(std::ostream& os, const uint256& num); +/** + * BOOST_CHECK_EXCEPTION predicates to check the specific validation error. + * Use as + * BOOST_CHECK_EXCEPTION(code that throws, exception type, HasReason("foo")); + */ +class HasReason { +public: + explicit HasReason(const std::string& reason) : m_reason(reason) {} + template <typename E> + bool operator() (const E& e) const { + return std::string(e.what()).find(m_reason) != std::string::npos; + }; +private: + const std::string m_reason; +}; + #endif diff --git a/src/uint256.cpp b/src/uint256.cpp index ee1b34eadd..d074df2f20 100644 --- a/src/uint256.cpp +++ b/src/uint256.cpp @@ -80,7 +80,4 @@ template std::string base_blob<256>::ToString() const; template void base_blob<256>::SetHex(const char*); template void base_blob<256>::SetHex(const std::string&); -uint256& UINT256_ONE() { - static uint256* one = new uint256(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); - return *one; -} +const uint256 uint256::ONE(1); diff --git a/src/uint256.h b/src/uint256.h index 8ab747ef49..c55cb31456 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -20,10 +20,11 @@ protected: static constexpr int WIDTH = BITS / 8; uint8_t m_data[WIDTH]; public: - base_blob() - { - memset(m_data, 0, sizeof(m_data)); - } + /* construct 0 value by default */ + constexpr base_blob() : m_data() {} + + /* constructor for constants between 1 and 255 */ + constexpr explicit base_blob(uint8_t v) : m_data{v} {} explicit base_blob(const std::vector<unsigned char>& vch); @@ -111,7 +112,7 @@ public: */ class uint160 : public base_blob<160> { public: - uint160() {} + constexpr uint160() {} explicit uint160(const std::vector<unsigned char>& vch) : base_blob<160>(vch) {} }; @@ -122,8 +123,10 @@ public: */ class uint256 : public base_blob<256> { public: - uint256() {} + constexpr uint256() {} + constexpr explicit uint256(uint8_t v) : base_blob<256>(v) {} explicit uint256(const std::vector<unsigned char>& vch) : base_blob<256>(vch) {} + static const uint256 ONE; }; /* uint256 from const char *. @@ -147,6 +150,4 @@ inline uint256 uint256S(const std::string& str) return rv; } -uint256& UINT256_ONE(); - #endif // BITCOIN_UINT256_H diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 079a4529a3..3236184b0b 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -201,20 +201,24 @@ std::string DecodeBase64(const std::string& str, bool* pf_invalid) return std::string((const char*)vchRet.data(), vchRet.size()); } -std::string EncodeBase32(Span<const unsigned char> input) +std::string EncodeBase32(Span<const unsigned char> input, bool pad) { static const char *pbase32 = "abcdefghijklmnopqrstuvwxyz234567"; std::string str; str.reserve(((input.size() + 4) / 5) * 8); ConvertBits<8, 5, true>([&](int v) { str += pbase32[v]; }, input.begin(), input.end()); - while (str.size() % 8) str += '='; + if (pad) { + while (str.size() % 8) { + str += '='; + } + } return str; } -std::string EncodeBase32(const std::string& str) +std::string EncodeBase32(const std::string& str, bool pad) { - return EncodeBase32(MakeUCharSpan(str)); + return EncodeBase32(MakeUCharSpan(str), pad); } std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 1519214140..1a217dd12d 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -52,8 +52,20 @@ std::string EncodeBase64(Span<const unsigned char> input); std::string EncodeBase64(const std::string& str); std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid = nullptr); std::string DecodeBase32(const std::string& str, bool* pf_invalid = nullptr); -std::string EncodeBase32(Span<const unsigned char> input); -std::string EncodeBase32(const std::string& str); + +/** + * Base32 encode. + * If `pad` is true, then the output will be padded with '=' so that its length + * is a multiple of 8. + */ +std::string EncodeBase32(Span<const unsigned char> input, bool pad = true); + +/** + * Base32 encode. + * If `pad` is true, then the output will be padded with '=' so that its length + * is a multiple of 8. + */ +std::string EncodeBase32(const std::string& str, bool pad = true); void SplitHostPort(std::string in, int& portOut, std::string& hostOut); int64_t atoi64(const std::string& str); diff --git a/src/util/string.h b/src/util/string.h index cdb41630c6..a0c87bd00c 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -7,6 +7,8 @@ #include <attributes.h> +#include <algorithm> +#include <array> #include <cstring> #include <locale> #include <sstream> @@ -74,4 +76,15 @@ std::string ToString(const T& t) return oss.str(); } +/** + * Check whether a container begins with the given prefix. + */ +template <typename T1, size_t PREFIX_LEN> +NODISCARD inline bool HasPrefix(const T1& obj, + const std::array<uint8_t, PREFIX_LEN>& prefix) +{ + return obj.size() >= PREFIX_LEN && + std::equal(std::begin(prefix), std::end(prefix), std::begin(obj)); +} + #endif // BITCOIN_UTIL_STRENCODINGS_H diff --git a/src/validation.cpp b/src/validation.cpp index d4463bf17b..e9c0607ced 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2449,9 +2449,9 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C } bilingual_str warning_messages; + int num_unexpected_version = 0; if (!::ChainstateActive().IsInitialBlockDownload()) { - int nUpgraded = 0; const CBlockIndex* pindex = pindexNew; for (int bit = 0; bit < VERSIONBITS_NUM_BITS; bit++) { WarningBitsConditionChecker checker(bit); @@ -2470,11 +2470,9 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C { int32_t nExpectedVersion = ComputeBlockVersion(pindex->pprev, chainParams.GetConsensus()); if (pindex->nVersion > VERSIONBITS_LAST_OLD_BLOCK_VERSION && (pindex->nVersion & ~nExpectedVersion) != 0) - ++nUpgraded; + ++num_unexpected_version; pindex = pindex->pprev; } - if (nUpgraded > 0) - AppendWarning(warning_messages, strprintf(_("%d of last 100 blocks have unexpected version"), nUpgraded)); } LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion, @@ -2482,6 +2480,10 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C FormatISO8601DateTime(pindexNew->GetBlockTime()), GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize(), !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages.original) : ""); + + if (num_unexpected_version > 0) { + LogPrint(BCLog::VALIDATION, "%d of last 100 blocks have unexpected version\n", num_unexpected_version); + } } /** Disconnect m_chain's tip. diff --git a/src/version.h b/src/version.h index b5f379e1b8..019c3a3ae7 100644 --- a/src/version.h +++ b/src/version.h @@ -38,4 +38,7 @@ static const int INVALID_CB_NO_BAN_VERSION = 70015; //! "wtxidrelay" command for wtxid-based relay starts with this version static const int WTXID_RELAY_VERSION = 70016; +// Make sure that none of the values above collide with +// `SERIALIZE_TRANSACTION_NO_WITNESS` or `ADDRV2_FORMAT`. + #endif // BITCOIN_VERSION_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 2217bc2875..70e99a308e 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3041,7 +3041,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f {"lockUnspents", UniValueType(UniValue::VBOOL)}, {"lock_unspents", UniValueType(UniValue::VBOOL)}, {"locktime", UniValueType(UniValue::VNUM)}, - {"feeRate", UniValueType()}, // will be checked below, + {"feeRate", UniValueType()}, // will be checked below {"psbt", UniValueType(UniValue::VBOOL)}, {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, {"subtract_fee_from_outputs", UniValueType(UniValue::VARR)}, @@ -3959,9 +3959,10 @@ static RPCHelpMan listlabels() static RPCHelpMan send() { return RPCHelpMan{"send", + "\nEXPERIMENTAL warning: this call may be changed in future releases.\n" "\nSend a transaction.\n", { - {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n" + {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "A JSON array with outputs (key-value pairs), where none of the keys are duplicated.\n" "That is, each address can only appear once and there can only be one 'data' object.\n" "For convenience, a dictionary, which holds the key-value pairs directly, is also accepted.", { @@ -3993,7 +3994,7 @@ static RPCHelpMan send() {"include_watching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only.\n" "Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n" "e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."}, - {"inputs", RPCArg::Type::ARR, /* default */ "empty array", "Specify inputs instead of adding them automatically. A json array of json objects", + {"inputs", RPCArg::Type::ARR, /* default */ "empty array", "Specify inputs instead of adding them automatically. A JSON array of JSON objects", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, @@ -4003,7 +4004,7 @@ static RPCHelpMan send() {"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, {"lock_unspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"}, {"psbt", RPCArg::Type::BOOL, /* default */ "automatic", "Always return a PSBT, implies add_to_wallet=false."}, - {"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n" + {"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "A JSON array of integers.\n" "The fee will be equally deducted from the amount of each specified output.\n" "Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" "If no outputs are specified here, the sender pays the fee.", @@ -4027,8 +4028,8 @@ static RPCHelpMan send() }, RPCExamples{"" "\nSend with a fee rate of 1 satoshi per byte\n" - + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 sat/b\n" + - "\nCreate a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n") + + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 sat/b\n") + + "\nCreate a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n" + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 economical '{\"add_to_wallet\": false, \"inputs\": [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\", \"vout\":1}]}'") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue @@ -4079,7 +4080,7 @@ static RPCHelpMan send() int change_position; bool rbf = pwallet->m_signal_rbf; if (options.exists("replaceable")) { - rbf = options["add_to_wallet"].get_bool(); + rbf = options["replaceable"].get_bool(); } CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf); CCoinControl coin_control; @@ -4096,7 +4097,7 @@ static RPCHelpMan send() // Make a blank psbt PartiallySignedTransaction psbtx(rawTx); - // Fill transaction with out data and sign + // Fill transaction with our data and sign bool complete = true; const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false); if (err != TransactionError::OK) { @@ -4108,13 +4109,11 @@ static RPCHelpMan send() UniValue result(UniValue::VOBJ); - // Serialize the PSBT - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << psbtx; - const std::string result_str = EncodeBase64(ssTx.str()); - if (psbt_opt_in || !complete || !add_to_wallet) { - result.pushKV("psbt", result_str); + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + result.pushKV("psbt", EncodeBase64(ssTx.str())); } if (complete) { diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 51715462c5..435716e56a 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -655,7 +655,7 @@ std::unique_ptr<CKeyMetadata> LegacyScriptPubKeyMan::GetMetadata(const CTxDestin uint256 LegacyScriptPubKeyMan::GetID() const { - return UINT256_ONE(); + return uint256::ONE; } /** diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 66857dbb39..6f320096eb 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -324,8 +324,6 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin return wallet; } -const uint256 CWalletTx::ABANDON_HASH(UINT256_ONE()); - /** @defgroup mapWallet * * @{ diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f15712dd0e..169f266980 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -275,7 +275,7 @@ private: /** Constant used in hashBlock to indicate tx has been abandoned, only used at * serialization/deserialization to avoid ambiguity with conflicted. */ - static const uint256 ABANDON_HASH; + static constexpr const uint256& ABANDON_HASH = uint256::ONE; public: /** diff --git a/test/functional/README.md b/test/functional/README.md index 0d85a74074..82b30fed51 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -87,7 +87,9 @@ P2P messages. These can be found in the following source files: #### Using the P2P interface -- [messages.py](test_framework/messages.py) contains all the definitions for objects that pass +- `P2P`s can be used to test specific P2P protocol behavior. +[p2p.py](test_framework/p2p.py) contains test framework p2p objects and +[messages.py](test_framework/messages.py) contains all the definitions for objects passed over the network (`CBlock`, `CTransaction`, etc, along with the network-level wrappers for them, `msg_block`, `msg_tx`, etc). @@ -100,8 +102,22 @@ contains the higher level logic for processing P2P payloads and connecting to the Bitcoin Core node application logic. For custom behaviour, subclass the P2PInterface object and override the callback methods. -- Can be used to write tests where specific P2P protocol behavior is tested. -Examples tests are [p2p_unrequested_blocks.py](p2p_unrequested_blocks.py), +`P2PConnection`s can be used as such: + +```python +p2p_conn = node.add_p2p_connection(P2PInterface()) +p2p_conn.send_and_ping(msg) +``` + +They can also be referenced by indexing into a `TestNode`'s `p2ps` list, which +contains the list of test framework `p2p` objects connected to itself +(it does not include any `TestNode`s): + +```python +node.p2ps[0].sync_with_ping() +``` + +More examples can be found in [p2p_unrequested_blocks.py](p2p_unrequested_blocks.py), [p2p_compactblocks.py](p2p_compactblocks.py). #### Prototyping tests @@ -157,7 +173,7 @@ way is the use the `profile_with_perf` context manager, e.g. with node.profile_with_perf("send-big-msgs"): # Perform activity on the node you're interested in profiling, e.g.: for _ in range(10000): - node.p2p.send_message(some_large_message) + node.p2ps[0].send_message(some_large_message) ``` To see useful textual output, run diff --git a/test/functional/example_test.py b/test/functional/example_test.py index 083deb6460..3b9bd3048f 100755 --- a/test/functional/example_test.py +++ b/test/functional/example_test.py @@ -136,7 +136,7 @@ class ExampleTest(BitcoinTestFramework): """Main test logic""" # Create P2P connections will wait for a verack to make sure the connection is fully up - self.nodes[0].add_p2p_connection(BaseNode()) + peer_messaging = self.nodes[0].add_p2p_connection(BaseNode()) # Generating a block on one of the nodes will get us out of IBD blocks = [int(self.nodes[0].generate(nblocks=1)[0], 16)] @@ -173,7 +173,7 @@ class ExampleTest(BitcoinTestFramework): block.solve() block_message = msg_block(block) # Send message is used to send a P2P message to the node over our P2PInterface - self.nodes[0].p2p.send_message(block_message) + peer_messaging.send_message(block_message) self.tip = block.sha256 blocks.append(self.tip) self.block_time += 1 @@ -191,25 +191,25 @@ class ExampleTest(BitcoinTestFramework): self.log.info("Add P2P connection to node2") self.nodes[0].disconnect_p2ps() - self.nodes[2].add_p2p_connection(BaseNode()) + peer_receiving = self.nodes[2].add_p2p_connection(BaseNode()) self.log.info("Test that node2 propagates all the blocks to us") getdata_request = msg_getdata() for block in blocks: getdata_request.inv.append(CInv(MSG_BLOCK, block)) - self.nodes[2].p2p.send_message(getdata_request) + peer_receiving.send_message(getdata_request) # wait_until() will loop until a predicate condition is met. Use it to test properties of the # P2PInterface objects. - self.nodes[2].p2p.wait_until(lambda: sorted(blocks) == sorted(list(self.nodes[2].p2p.block_receive_map.keys())), timeout=5) + peer_receiving.wait_until(lambda: sorted(blocks) == sorted(list(peer_receiving.block_receive_map.keys())), timeout=5) self.log.info("Check that each block was received only once") # The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving # messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking # and synchronization issues. Note p2p.wait_until() acquires this global lock internally when testing the predicate. with p2p_lock: - for block in self.nodes[2].p2p.block_receive_map.values(): + for block in peer_receiving.block_receive_map.values(): assert_equal(block, 1) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index efafcfaec3..19753d73ef 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -1386,14 +1386,14 @@ class FullBlockTest(BitcoinTestFramework): """Add a P2P connection to the node. Helper to connect and wait for version handshake.""" - self.nodes[0].add_p2p_connection(P2PDataStore()) + self.helper_peer = self.nodes[0].add_p2p_connection(P2PDataStore()) # We need to wait for the initial getheaders from the peer before we # start populating our blockstore. If we don't, then we may run ahead # to the next subtest before we receive the getheaders. We'd then send # an INV for the next block and receive two getheaders - one for the # IBD and one for the INV. We'd respond to both and could get # unexpectedly disconnected if the DoS score for that error is 50. - self.nodes[0].p2p.wait_for_getheaders(timeout=timeout) + self.helper_peer.wait_for_getheaders(timeout=timeout) def reconnect_p2p(self, timeout=60): """Tear down and bootstrap the P2P connection to the node. @@ -1407,7 +1407,7 @@ class FullBlockTest(BitcoinTestFramework): """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. Call with success = False if the tip shouldn't advance to the most recent block.""" - self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect) + self.helper_peer.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect) if reconnect: self.reconnect_p2p(timeout=timeout) diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 2919b0ea0b..aad255c4a9 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -75,7 +75,7 @@ class BIP65Test(BitcoinTestFramework): ) def run_test(self): - self.nodes[0].add_p2p_connection(P2PInterface()) + peer = self.nodes[0].add_p2p_connection(P2PInterface()) self.test_cltv_info(is_active=False) @@ -99,7 +99,7 @@ class BIP65Test(BitcoinTestFramework): block.solve() self.test_cltv_info(is_active=False) # Not active as of current tip and next block does not need to obey rules - self.nodes[0].p2p.send_and_ping(msg_block(block)) + peer.send_and_ping(msg_block(block)) self.test_cltv_info(is_active=True) # Not active as of current tip, but next block must obey rules assert_equal(self.nodes[0].getbestblockhash(), block.hash) @@ -111,9 +111,9 @@ class BIP65Test(BitcoinTestFramework): block.solve() with self.nodes[0].assert_debug_log(expected_msgs=['{}, bad-version(0x00000003)'.format(block.hash)]): - self.nodes[0].p2p.send_and_ping(msg_block(block)) + peer.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) - self.nodes[0].p2p.sync_with_ping() + peer.sync_with_ping() self.log.info("Test that invalid-according-to-cltv transactions cannot appear in a block") block.nVersion = 4 @@ -136,9 +136,9 @@ class BIP65Test(BitcoinTestFramework): block.solve() with self.nodes[0].assert_debug_log(expected_msgs=['CheckInputScripts on {} failed with non-mandatory-script-verify-flag (Negative locktime)'.format(block.vtx[-1].hash)]): - self.nodes[0].p2p.send_and_ping(msg_block(block)) + peer.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) - self.nodes[0].p2p.sync_with_ping() + peer.sync_with_ping() self.log.info("Test that a version 4 block with a valid-according-to-CLTV transaction is accepted") spendtx = cltv_validate(self.nodes[0], spendtx, CLTV_HEIGHT - 1) @@ -150,7 +150,7 @@ class BIP65Test(BitcoinTestFramework): block.solve() self.test_cltv_info(is_active=True) # Not active as of current tip, but next block must obey rules - self.nodes[0].p2p.send_and_ping(msg_block(block)) + peer.send_and_ping(msg_block(block)) self.test_cltv_info(is_active=True) # Active as of current tip assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index 34e856c1ba..b4355a6587 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -78,6 +78,12 @@ class ConfArgsTest(BitcoinTestFramework): with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf: conf.write('') # clear + def test_invalid_command_line_options(self): + self.nodes[0].assert_start_raises_init_error( + expected_msg='Error: No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.', + extra_args=['-proxy'], + ) + def test_log_buffer(self): with self.nodes[0].assert_debug_log(expected_msgs=['Warning: parsed potentially confusing double-negative -connect=0\n']): self.start_node(0, extra_args=['-noconnect=0']) @@ -146,6 +152,7 @@ class ConfArgsTest(BitcoinTestFramework): self.test_networkactive() self.test_config_file_parser() + self.test_invalid_command_line_options() # Remove the -datadir argument so it doesn't override the config file self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")] diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 38e95f00e9..39e8bca751 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -182,10 +182,10 @@ class BIP68_112_113Test(BitcoinTestFramework): """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. Call with success = False if the tip shouldn't advance to the most recent block.""" - self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason) + self.helper_peer.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason) def run_test(self): - self.nodes[0].add_p2p_connection(P2PDataStore()) + self.helper_peer = self.nodes[0].add_p2p_connection(P2PDataStore()) self.log.info("Generate blocks in the past for coinbase outputs.") long_past_time = int(time.time()) - 600 * 1000 # enough to build up to 1000 blocks 10 minutes apart without worrying about getting into the future diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index f263c93c8a..3f7efdbded 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -59,7 +59,7 @@ class BIP66Test(BitcoinTestFramework): ) def run_test(self): - self.nodes[0].add_p2p_connection(P2PInterface()) + peer = self.nodes[0].add_p2p_connection(P2PInterface()) self.test_dersig_info(is_active=False) @@ -84,7 +84,7 @@ class BIP66Test(BitcoinTestFramework): block.solve() self.test_dersig_info(is_active=False) # Not active as of current tip and next block does not need to obey rules - self.nodes[0].p2p.send_and_ping(msg_block(block)) + peer.send_and_ping(msg_block(block)) self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules assert_equal(self.nodes[0].getbestblockhash(), block.hash) @@ -97,9 +97,9 @@ class BIP66Test(BitcoinTestFramework): block.solve() with self.nodes[0].assert_debug_log(expected_msgs=['{}, bad-version(0x00000002)'.format(block.hash)]): - self.nodes[0].p2p.send_and_ping(msg_block(block)) + peer.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) - self.nodes[0].p2p.sync_with_ping() + peer.sync_with_ping() self.log.info("Test that transactions with non-DER signatures cannot appear in a block") block.nVersion = 3 @@ -123,9 +123,9 @@ class BIP66Test(BitcoinTestFramework): block.solve() with self.nodes[0].assert_debug_log(expected_msgs=['CheckInputScripts on {} failed with non-mandatory-script-verify-flag (Non-canonical DER signature)'.format(block.vtx[-1].hash)]): - self.nodes[0].p2p.send_and_ping(msg_block(block)) + peer.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) - self.nodes[0].p2p.sync_with_ping() + peer.sync_with_ping() self.log.info("Test that a version 3 block with a DERSIG-compliant transaction is accepted") block.vtx[1] = create_transaction(self.nodes[0], self.coinbase_txids[1], self.nodeaddress, amount=1.0) @@ -134,7 +134,7 @@ class BIP66Test(BitcoinTestFramework): block.solve() self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules - self.nodes[0].p2p.send_and_ping(msg_block(block)) + peer.send_and_ping(msg_block(block)) self.test_dersig_info(is_active=True) # Active as of current tip assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index e5c62d1ea7..d0a94658ff 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -145,16 +145,16 @@ class MaxUploadTest(BitcoinTestFramework): self.restart_node(0, ["-whitelist=download@127.0.0.1", "-maxuploadtarget=1"]) # Reconnect to self.nodes[0] - self.nodes[0].add_p2p_connection(TestP2PConn()) + peer = self.nodes[0].add_p2p_connection(TestP2PConn()) #retrieve 20 blocks which should be enough to break the 1MB limit getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)] for i in range(20): - self.nodes[0].p2p.send_and_ping(getdata_request) - assert_equal(self.nodes[0].p2p.block_receive_map[big_new_block], i+1) + peer.send_and_ping(getdata_request) + assert_equal(peer.block_receive_map[big_new_block], i+1) getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)] - self.nodes[0].p2p.send_and_ping(getdata_request) + peer.send_and_ping(getdata_request) self.log.info("Peer still connected after trying to download old block (download permission)") peer_info = self.nodes[0].getpeerinfo() diff --git a/test/functional/feature_versionbits_warning.py b/test/functional/feature_versionbits_warning.py index e1016e1581..2e4f4796b0 100755 --- a/test/functional/feature_versionbits_warning.py +++ b/test/functional/feature_versionbits_warning.py @@ -61,7 +61,7 @@ class VersionBitsWarningTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0] - node.add_p2p_connection(P2PInterface()) + peer = node.add_p2p_connection(P2PInterface()) node_deterministic_address = node.get_deterministic_priv_key().address # Mine one period worth of blocks @@ -69,7 +69,7 @@ class VersionBitsWarningTest(BitcoinTestFramework): self.log.info("Check that there is no warning if previous VB_BLOCKS have <VB_THRESHOLD blocks with unknown versionbits version.") # Build one period of blocks with < VB_THRESHOLD blocks signaling some unknown bit - self.send_blocks_with_version(node.p2p, VB_THRESHOLD - 1, VB_UNKNOWN_VERSION) + self.send_blocks_with_version(peer, VB_THRESHOLD - 1, VB_UNKNOWN_VERSION) node.generatetoaddress(VB_PERIOD - VB_THRESHOLD + 1, node_deterministic_address) # Check that we're not getting any versionbit-related errors in get*info() @@ -77,7 +77,7 @@ class VersionBitsWarningTest(BitcoinTestFramework): assert not VB_PATTERN.match(node.getnetworkinfo()["warnings"]) # Build one period of blocks with VB_THRESHOLD blocks signaling some unknown bit - self.send_blocks_with_version(node.p2p, VB_THRESHOLD, VB_UNKNOWN_VERSION) + self.send_blocks_with_version(peer, VB_THRESHOLD, VB_UNKNOWN_VERSION) node.generatetoaddress(VB_PERIOD - VB_THRESHOLD, node_deterministic_address) self.log.info("Check that there is a warning if previous VB_BLOCKS have >=VB_THRESHOLD blocks with unknown versionbits version.") diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index e74ef8cf16..d7cb7db9f8 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -58,7 +58,7 @@ class MempoolPackagesTest(BitcoinTestFramework): def run_test(self): # Mine some blocks and have them mature. - self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs + peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs self.nodes[0].generate(101) utxo = self.nodes[0].listunspent(10) txid = utxo[0]['txid'] @@ -80,7 +80,7 @@ class MempoolPackagesTest(BitcoinTestFramework): # Wait until mempool transactions have passed initial broadcast (sent inv and received getdata) # Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between - self.nodes[0].p2p.wait_for_broadcast(witness_chain) + peer_inv_store.wait_for_broadcast(witness_chain) # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor # count and fees should look correct diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index b13740750f..1b2c7644bd 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -234,9 +234,9 @@ class MiningTest(BitcoinTestFramework): assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=CBlockHeader(bad_block_time).serialize().hex())) # Should ask for the block from a p2p node, if they announce the header as well: - node.add_p2p_connection(P2PDataStore()) - node.p2p.wait_for_getheaders(timeout=5) # Drop the first getheaders - node.p2p.send_blocks_and_test(blocks=[block], node=node) + peer = node.add_p2p_connection(P2PDataStore()) + peer.wait_for_getheaders(timeout=5) # Drop the first getheaders + peer.send_blocks_and_test(blocks=[block], node=node) # Must be active now: assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips() diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index 3ead31c9ea..646baa1550 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -17,7 +17,7 @@ class P2PBlocksOnly(BitcoinTestFramework): self.extra_args = [["-blocksonly"]] def run_test(self): - self.nodes[0].add_p2p_connection(P2PInterface()) + block_relay_peer = self.nodes[0].add_p2p_connection(P2PInterface()) self.log.info('Check that txs from p2p are rejected and result in disconnect') prevtx = self.nodes[0].getblock(self.nodes[0].getblockhash(1), 2)['tx'][0] @@ -41,20 +41,20 @@ class P2PBlocksOnly(BitcoinTestFramework): )['hex'] assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], False) with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol peer=0']): - self.nodes[0].p2p.send_message(msg_tx(FromHex(CTransaction(), sigtx))) - self.nodes[0].p2p.wait_for_disconnect() + block_relay_peer.send_message(msg_tx(FromHex(CTransaction(), sigtx))) + block_relay_peer.wait_for_disconnect() assert_equal(self.nodes[0].getmempoolinfo()['size'], 0) # Remove the disconnected peer and add a new one. del self.nodes[0].p2ps[0] - self.nodes[0].add_p2p_connection(P2PInterface()) + tx_relay_peer = self.nodes[0].add_p2p_connection(P2PInterface()) self.log.info('Check that txs from rpc are not rejected and relayed to other peers') assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True) txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid'] with self.nodes[0].assert_debug_log(['received getdata for: wtx {} peer=1'.format(txid)]): self.nodes[0].sendrawtransaction(sigtx) - self.nodes[0].p2p.wait_for_tx(txid) + tx_relay_peer.wait_for_tx(txid) assert_equal(self.nodes[0].getmempoolinfo()['size'], 1) self.log.info('Check that txs from peers with relay-permission are not rejected and relayed to others') diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py index 7dd8c3146b..2349afa1ee 100755 --- a/test/functional/p2p_dos_header_tree.py +++ b/test/functional/p2p_dos_header_tree.py @@ -46,8 +46,8 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.headers_fork = [FromHex(CBlockHeader(), h) for h in self.headers_fork] self.log.info("Feed all non-fork headers, including and up to the first checkpoint") - self.nodes[0].add_p2p_connection(P2PInterface()) - self.nodes[0].p2p.send_and_ping(msg_headers(self.headers)) + peer_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) + peer_checkpoint.send_and_ping(msg_headers(self.headers)) assert { 'height': 546, 'hash': '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70', @@ -57,14 +57,14 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): self.log.info("Feed all fork headers (fails due to checkpoint)") with self.nodes[0].assert_debug_log(['bad-fork-prior-to-checkpoint']): - self.nodes[0].p2p.send_message(msg_headers(self.headers_fork)) - self.nodes[0].p2p.wait_for_disconnect() + peer_checkpoint.send_message(msg_headers(self.headers_fork)) + peer_checkpoint.wait_for_disconnect() self.log.info("Feed all fork headers (succeeds without checkpoint)") # On node 0 it succeeds because checkpoints are disabled self.restart_node(0, extra_args=['-nocheckpoints']) - self.nodes[0].add_p2p_connection(P2PInterface()) - self.nodes[0].p2p.send_and_ping(msg_headers(self.headers_fork)) + peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) + peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork)) assert { "height": 2, "hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", @@ -73,8 +73,8 @@ class RejectLowDifficultyHeadersTest(BitcoinTestFramework): } in self.nodes[0].getchaintips() # On node 1 it succeeds because no checkpoint has been reached yet by a chain tip - self.nodes[1].add_p2p_connection(P2PInterface()) - self.nodes[1].p2p.send_and_ping(msg_headers(self.headers_fork)) + peer_before_checkpoint = self.nodes[1].add_p2p_connection(P2PInterface()) + peer_before_checkpoint.send_and_ping(msg_headers(self.headers_fork)) assert { "height": 2, "hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py index 613d96eaad..642a217047 100755 --- a/test/functional/p2p_filter.py +++ b/test/functional/p2p_filter.py @@ -131,7 +131,7 @@ class FilterTest(BitcoinTestFramework): self.log.debug("Send a mempool msg after connecting and check that the tx is received") self.nodes[0].add_p2p_connection(filter_peer) filter_peer.send_and_ping(filter_peer.watch_filter_init) - self.nodes[0].p2p.send_message(msg_mempool()) + filter_peer.send_message(msg_mempool()) filter_peer.wait_for_tx(txid) def test_frelay_false(self, filter_peer): diff --git a/test/functional/p2p_getdata.py b/test/functional/p2p_getdata.py index 51921a8ab5..89d68d5ba0 100755 --- a/test/functional/p2p_getdata.py +++ b/test/functional/p2p_getdata.py @@ -42,7 +42,7 @@ class GetdataTest(BitcoinTestFramework): good_getdata = msg_getdata() good_getdata.inv.append(CInv(t=2, h=best_block)) p2p_block_store.send_and_ping(good_getdata) - p2p_block_store.wait_until(lambda: self.nodes[0].p2ps[0].blocks[best_block] == 1) + p2p_block_store.wait_until(lambda: p2p_block_store.blocks[best_block] == 1) if __name__ == '__main__': diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index b2c3c5d45f..483f25f48c 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -27,7 +27,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework): def run_test(self): # Add p2p connection to node0 node = self.nodes[0] # convenience reference to the node - node.add_p2p_connection(P2PDataStore()) + peer = node.add_p2p_connection(P2PDataStore()) best_block = node.getblock(node.getbestblockhash()) tip = int(node.getbestblockhash(), 16) @@ -42,7 +42,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework): # Save the coinbase for later block1 = block tip = block.sha256 - node.p2p.send_blocks_and_test([block1], node, success=True) + peer.send_blocks_and_test([block1], node, success=True) self.log.info("Mature the block.") node.generatetoaddress(100, node.get_deterministic_priv_key().address) @@ -80,7 +80,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework): assert_equal(orig_hash, block2.rehash()) assert block2_orig.vtx != block2.vtx - node.p2p.send_blocks_and_test([block2], node, success=False, reject_reason='bad-txns-duplicate') + peer.send_blocks_and_test([block2], node, success=False, reject_reason='bad-txns-duplicate') # Check transactions for duplicate inputs (CVE-2018-17144) self.log.info("Test duplicate input block.") @@ -91,7 +91,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block2_dup.hashMerkleRoot = block2_dup.calc_merkle_root() block2_dup.rehash() block2_dup.solve() - node.p2p.send_blocks_and_test([block2_dup], node, success=False, reject_reason='bad-txns-inputs-duplicate') + peer.send_blocks_and_test([block2_dup], node, success=False, reject_reason='bad-txns-inputs-duplicate') self.log.info("Test very broken block.") @@ -104,14 +104,14 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block3.rehash() block3.solve() - node.p2p.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount') + peer.send_blocks_and_test([block3], node, success=False, reject_reason='bad-cb-amount') # Complete testing of CVE-2012-2459 by sending the original block. # It should be accepted even though it has the same hash as the mutated one. self.log.info("Test accepting original block after rejecting its mutated version.") - node.p2p.send_blocks_and_test([block2_orig], node, success=True, timeout=5) + peer.send_blocks_and_test([block2_orig], node, success=True, timeout=5) # Update tip info height += 1 @@ -131,7 +131,7 @@ class InvalidBlockRequestTest(BitcoinTestFramework): block4.rehash() block4.solve() self.log.info("Test inflation by duplicating input") - node.p2p.send_blocks_and_test([block4], node, success=False, reject_reason='bad-txns-inputs-duplicate') + peer.send_blocks_and_test([block4], node, success=False, reject_reason='bad-txns-inputs-duplicate') if __name__ == '__main__': InvalidBlockRequestTest().main() diff --git a/test/functional/p2p_invalid_locator.py b/test/functional/p2p_invalid_locator.py index 24328c2919..e4fc9fd178 100755 --- a/test/functional/p2p_invalid_locator.py +++ b/test/functional/p2p_invalid_locator.py @@ -23,20 +23,20 @@ class InvalidLocatorTest(BitcoinTestFramework): block_count = node.getblockcount() for msg in [msg_getheaders(), msg_getblocks()]: self.log.info('Wait for disconnect when sending {} hashes in locator'.format(MAX_LOCATOR_SZ + 1)) - node.add_p2p_connection(P2PInterface()) + exceed_max_peer = node.add_p2p_connection(P2PInterface()) msg.locator.vHave = [int(node.getblockhash(i - 1), 16) for i in range(block_count, block_count - (MAX_LOCATOR_SZ + 1), -1)] - node.p2p.send_message(msg) - node.p2p.wait_for_disconnect() + exceed_max_peer.send_message(msg) + exceed_max_peer.wait_for_disconnect() node.disconnect_p2ps() self.log.info('Wait for response when sending {} hashes in locator'.format(MAX_LOCATOR_SZ)) - node.add_p2p_connection(P2PInterface()) + within_max_peer = node.add_p2p_connection(P2PInterface()) msg.locator.vHave = [int(node.getblockhash(i - 1), 16) for i in range(block_count, block_count - (MAX_LOCATOR_SZ), -1)] - node.p2p.send_message(msg) + within_max_peer.send_message(msg) if type(msg) == msg_getheaders: - node.p2p.wait_for_header(node.getbestblockhash()) + within_max_peer.wait_for_header(node.getbestblockhash()) else: - node.p2p.wait_for_block(int(node.getbestblockhash(), 16)) + within_max_peer.wait_for_block(int(node.getbestblockhash(), 16)) if __name__ == '__main__': diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index fe57057a83..fbe58c5e2f 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -22,12 +22,11 @@ from test_framework.p2p import ( P2PInterface, ) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import ( - assert_equal, -) +from test_framework.util import assert_equal VALID_DATA_LIMIT = MAX_PROTOCOL_MESSAGE_LENGTH - 5 # Account for the 5-byte length prefix + class msg_unrecognized: """Nonsensical message. Modeled after similar types in test_framework.messages.""" @@ -64,13 +63,13 @@ class InvalidMessagesTest(BitcoinTestFramework): conn = self.nodes[0].add_p2p_connection(P2PDataStore()) # Create valid message msg = conn.build_message(msg_ping(nonce=12345)) - cut_pos = 12 # Chosen at an arbitrary position within the header + cut_pos = 12 # Chosen at an arbitrary position within the header # Send message in two pieces - before = int(self.nodes[0].getnettotals()['totalbytesrecv']) + before = self.nodes[0].getnettotals()['totalbytesrecv'] conn.send_raw_message(msg[:cut_pos]) # Wait until node has processed the first half of the message - self.wait_until(lambda: int(self.nodes[0].getnettotals()['totalbytesrecv']) != before) - middle = int(self.nodes[0].getnettotals()['totalbytesrecv']) + self.wait_until(lambda: self.nodes[0].getnettotals()['totalbytesrecv'] != before) + middle = self.nodes[0].getnettotals()['totalbytesrecv'] # If this assert fails, we've hit an unlikely race # where the test framework sent a message in between the two halves assert_equal(middle, before + cut_pos) @@ -81,7 +80,7 @@ class InvalidMessagesTest(BitcoinTestFramework): def test_magic_bytes(self): self.log.info("Test message with invalid magic bytes disconnects peer") conn = self.nodes[0].add_p2p_connection(P2PDataStore()) - with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: INVALID MESSAGESTART badmsg']): + with self.nodes[0].assert_debug_log(['HEADER ERROR - MESSAGESTART (badmsg, 2 bytes), received ffffffff']): msg = conn.build_message(msg_unrecognized(str_data="d")) # modify magic bytes msg = b'\xff' * 4 + msg[4:] @@ -100,12 +99,14 @@ class InvalidMessagesTest(BitcoinTestFramework): msg = msg[:cut_len] + b'\xff' * 4 + msg[cut_len + 4:] conn.send_raw_message(msg) conn.sync_with_ping(timeout=1) + # Check that traffic is accounted for (24 bytes header + 2 bytes payload) + assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 26) self.nodes[0].disconnect_p2ps() def test_size(self): self.log.info("Test message with oversized payload disconnects peer") conn = self.nodes[0].add_p2p_connection(P2PDataStore()) - with self.nodes[0].assert_debug_log(['']): + with self.nodes[0].assert_debug_log(['HEADER ERROR - SIZE (badmsg, 4000001 bytes)']): msg = msg_unrecognized(str_data="d" * (VALID_DATA_LIMIT + 1)) msg = conn.build_message(msg) conn.send_raw_message(msg) @@ -115,14 +116,15 @@ class InvalidMessagesTest(BitcoinTestFramework): def test_msgtype(self): self.log.info("Test message with invalid message type logs an error") conn = self.nodes[0].add_p2p_connection(P2PDataStore()) - with self.nodes[0].assert_debug_log(['PROCESSMESSAGE: ERRORS IN HEADER']): + with self.nodes[0].assert_debug_log(['HEADER ERROR - COMMAND']): msg = msg_unrecognized(str_data="d") - msg.msgtype = b'\xff' * 12 msg = conn.build_message(msg) # Modify msgtype msg = msg[:7] + b'\x00' + msg[7 + 1:] conn.send_raw_message(msg) conn.sync_with_ping(timeout=1) + # Check that traffic is accounted for (24 bytes header + 2 bytes payload) + assert_equal(self.nodes[0].getpeerinfo()[0]['bytesrecv_per_msg']['*other*'], 26) self.nodes[0].disconnect_p2ps() def test_oversized_msg(self, msg, size): diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index a0ef6c9d6e..489d903c21 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -61,7 +61,7 @@ class InvalidTxRequestTest(BitcoinTestFramework): # Save the coinbase for later block1 = block tip = block.sha256 - node.p2p.send_blocks_and_test([block], node, success=True) + node.p2ps[0].send_blocks_and_test([block], node, success=True) self.log.info("Mature the block.") self.nodes[0].generatetoaddress(100, self.nodes[0].get_deterministic_priv_key().address) @@ -72,7 +72,7 @@ class InvalidTxRequestTest(BitcoinTestFramework): self.log.info("Testing invalid transaction: %s", BadTxTemplate.__name__) template = BadTxTemplate(spend_block=block1) tx = template.get_tx() - node.p2p.send_txs_and_test( + node.p2ps[0].send_txs_and_test( [tx], node, success=False, expect_disconnect=template.expect_disconnect, reject_reason=template.reject_reason, @@ -121,7 +121,7 @@ class InvalidTxRequestTest(BitcoinTestFramework): self.log.info('Send the orphans ... ') # Send valid orphan txs from p2ps[0] - node.p2p.send_txs_and_test([tx_orphan_1, tx_orphan_2_no_fee, tx_orphan_2_valid], node, success=False) + node.p2ps[0].send_txs_and_test([tx_orphan_1, tx_orphan_2_no_fee, tx_orphan_2_valid], node, success=False) # Send invalid tx from p2ps[1] node.p2ps[1].send_txs_and_test([tx_orphan_2_invalid], node, success=False) @@ -130,7 +130,7 @@ class InvalidTxRequestTest(BitcoinTestFramework): self.log.info('Send the withhold tx ... ') with node.assert_debug_log(expected_msgs=["bad-txns-in-belowout"]): - node.p2p.send_txs_and_test([tx_withhold], node, success=True) + node.p2ps[0].send_txs_and_test([tx_withhold], node, success=True) # Transactions that should end up in the mempool expected_mempool = { @@ -155,14 +155,14 @@ class InvalidTxRequestTest(BitcoinTestFramework): orphan_tx_pool[i].vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) with node.assert_debug_log(['mapOrphan overflow, removed 1 tx']): - node.p2p.send_txs_and_test(orphan_tx_pool, node, success=False) + node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False) rejected_parent = CTransaction() rejected_parent.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_2_invalid.sha256, 0))) rejected_parent.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)) rejected_parent.rehash() with node.assert_debug_log(['not keeping orphan with rejected parents {}'.format(rejected_parent.hash)]): - node.p2p.send_txs_and_test([rejected_parent], node, success=False) + node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False) if __name__ == '__main__': diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index dfbf5d52f4..d79ed449e5 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -89,7 +89,6 @@ from test_framework.util import ( # The versionbit bit used to signal activation of SegWit VB_WITNESS_BIT = 1 -VB_PERIOD = 144 VB_TOP_BITS = 0x20000000 MAX_SIGOP_COST = 80000 @@ -2097,14 +2096,14 @@ class SegWitTest(BitcoinTestFramework): tx = FromHex(CTransaction(), raw) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, serialize_with_bogus_witness(tx).hex()) with self.nodes[0].assert_debug_log(['Superfluous witness record']): - self.nodes[0].p2p.send_and_ping(msg_bogus_tx(tx)) + self.test_node.send_and_ping(msg_bogus_tx(tx)) raw = self.nodes[0].signrawtransactionwithwallet(raw) assert raw['complete'] raw = raw['hex'] tx = FromHex(CTransaction(), raw) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, serialize_with_bogus_witness(tx).hex()) with self.nodes[0].assert_debug_log(['Unknown transaction optional data']): - self.nodes[0].p2p.send_and_ping(msg_bogus_tx(tx)) + self.test_node.send_and_ping(msg_bogus_tx(tx)) @subtest # type: ignore def test_wtxid_relay(self): diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index c005584485..35cea85c07 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -317,7 +317,7 @@ class BlockchainTest(BitcoinTestFramework): def _test_waitforblockheight(self): self.log.info("Test waitforblockheight") node = self.nodes[0] - node.add_p2p_connection(P2PInterface()) + peer = node.add_p2p_connection(P2PInterface()) current_height = node.getblock(node.getbestblockhash())['height'] @@ -334,7 +334,7 @@ class BlockchainTest(BitcoinTestFramework): def solve_and_send_block(prevhash, height, time): b = create_block(prevhash, create_coinbase(height), time) b.solve() - node.p2p.send_and_ping(msg_block(b)) + peer.send_and_ping(msg_block(b)) return b b21f = solve_and_send_block(int(b20hash, 16), 21, b20['time'] + 1) diff --git a/test/functional/rpc_getpeerinfo_banscore_deprecation.py b/test/functional/rpc_getpeerinfo_banscore_deprecation.py deleted file mode 100755 index b830248e1e..0000000000 --- a/test/functional/rpc_getpeerinfo_banscore_deprecation.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2020 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test deprecation of getpeerinfo RPC banscore field.""" - -from test_framework.test_framework import BitcoinTestFramework - - -class GetpeerinfoBanscoreDeprecationTest(BitcoinTestFramework): - def set_test_params(self): - self.num_nodes = 2 - self.extra_args = [[], ["-deprecatedrpc=banscore"]] - - def run_test(self): - self.log.info("Test getpeerinfo by default no longer returns a banscore field") - assert "banscore" not in self.nodes[0].getpeerinfo()[0].keys() - - self.log.info("Test getpeerinfo returns banscore with -deprecatedrpc=banscore") - assert "banscore" in self.nodes[1].getpeerinfo()[0].keys() - - -if __name__ == "__main__": - GetpeerinfoBanscoreDeprecationTest().main() diff --git a/test/functional/rpc_getpeerinfo_deprecation.py b/test/functional/rpc_getpeerinfo_deprecation.py new file mode 100755 index 0000000000..287c40ae3e --- /dev/null +++ b/test/functional/rpc_getpeerinfo_deprecation.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# Copyright (c) 2020 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test deprecation of getpeerinfo RPC fields.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import connect_nodes + + +class GetpeerinfoDeprecationTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [[], ["-deprecatedrpc=banscore"]] + + def run_test(self): + self.test_banscore_deprecation() + self.test_addnode_deprecation() + + def test_banscore_deprecation(self): + self.log.info("Test getpeerinfo by default no longer returns a banscore field") + assert "banscore" not in self.nodes[0].getpeerinfo()[0].keys() + + self.log.info("Test getpeerinfo returns banscore with -deprecatedrpc=banscore") + assert "banscore" in self.nodes[1].getpeerinfo()[0].keys() + + def test_addnode_deprecation(self): + self.restart_node(1, ["-deprecatedrpc=getpeerinfo_addnode"]) + connect_nodes(self.nodes[0], 1) + + self.log.info("Test getpeerinfo by default no longer returns an addnode field") + assert "addnode" not in self.nodes[0].getpeerinfo()[0].keys() + + self.log.info("Test getpeerinfo returns addnode with -deprecatedrpc=addnode") + assert "addnode" in self.nodes[1].getpeerinfo()[0].keys() + + +if __name__ == "__main__": + GetpeerinfoDeprecationTest().main() diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index bc0e5b458e..b8a04f494d 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -175,6 +175,12 @@ class NetTest(BitcoinTestFramework): for info in peer_info: assert_net_servicesnames(int(info[0]["services"], 0x10), info[0]["servicesnames"]) + assert_equal(peer_info[0][0]['connection_type'], 'inbound') + assert_equal(peer_info[0][1]['connection_type'], 'manual') + + assert_equal(peer_info[1][0]['connection_type'], 'manual') + assert_equal(peer_info[1][1]['connection_type'], 'inbound') + def test_service_flags(self): self.log.info("Test service flags") self.nodes[0].add_p2p_connection(P2PInterface(), services=(1 << 4) | (1 << 63)) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index d034986821..046efe730e 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -542,15 +542,6 @@ class TestNode(): return p2p_conn - @property - def p2p(self): - """Return the first p2p connection - - Convenience property - most tests only use a single p2p connection to each - node, so this saves having to write node.p2ps[0] many times.""" - assert self.p2ps, self._node_msg("No p2p connection") - return self.p2ps[0] - def num_test_p2p_connections(self): """Return number of test framework p2p connections to the node.""" return len([peer for peer in self.getpeerinfo() if peer['subver'] == MY_SUBVERSION]) diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 6c3d50df93..c8cf173d5f 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -251,7 +251,7 @@ BASE_SCRIPTS = [ 'feature_config_args.py', 'feature_settings.py', 'rpc_getdescriptorinfo.py', - 'rpc_getpeerinfo_banscore_deprecation.py', + 'rpc_getpeerinfo_deprecation.py', 'rpc_help.py', 'feature_help.py', 'feature_shutdown.py', diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index aaf050ebf7..f0be271c66 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -21,8 +21,6 @@ from test_framework.util import ( get_rpc_proxy, ) -FEATURE_LATEST = 169900 - got_loading_error = False def test_load_unload(node, name): global got_loading_error diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 0327c9e070..d3c03c4764 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -21,7 +21,7 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0] # alias - node.add_p2p_connection(P2PTxInvStore()) + peer_first = node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a new transaction and wait until it's broadcast") txid = node.sendtoaddress(node.getnewaddress(), 1) @@ -33,10 +33,10 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): time.sleep(1.1) # Can take a few seconds due to transaction trickling - node.p2p.wait_for_broadcast([txid]) + peer_first.wait_for_broadcast([txid]) # Add a second peer since txs aren't rebroadcast to the same peer (see filterInventoryKnown) - node.add_p2p_connection(P2PTxInvStore()) + peer_second = node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a block") # Create and submit a block without the transaction. @@ -58,13 +58,13 @@ class ResendWalletTransactionsTest(BitcoinTestFramework): two_min = 2 * 60 node.setmocktime(now + twelve_hrs - two_min) time.sleep(2) # ensure enough time has passed for rebroadcast attempt to occur - assert_equal(int(txid, 16) in node.p2ps[1].get_invs(), False) + assert_equal(int(txid, 16) in peer_second.get_invs(), False) self.log.info("Bump time & check that transaction is rebroadcast") # Transaction should be rebroadcast approximately 24 hours in the future, # but can range from 12-36. So bump 36 hours to be sure. node.setmocktime(now + 36 * 60 * 60) - node.p2p.wait_for_broadcast([txid]) + peer_second.wait_for_broadcast([txid]) if __name__ == '__main__': diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index b64d2030a4..4fdfad01c3 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -29,9 +29,9 @@ class WalletSendTest(BitcoinTestFramework): def test_send(self, from_wallet, to_wallet=None, amount=None, data=None, arg_conf_target=None, arg_estimate_mode=None, - conf_target=None, estimate_mode=None, add_to_wallet=None,psbt=None, - inputs=None,add_inputs=None,change_address=None,change_position=None,change_type=None, - include_watching=None,locktime=None,lock_unspents=None,replaceable=None,subtract_fee_from_outputs=None, + conf_target=None, estimate_mode=None, add_to_wallet=None, psbt=None, + inputs=None, add_inputs=None, change_address=None, change_position=None, change_type=None, + include_watching=None, locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None, expect_error=None): assert (amount is None) != (data is None) @@ -92,13 +92,13 @@ class WalletSendTest(BitcoinTestFramework): res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options) else: try: - assert_raises_rpc_error(expect_error[0],expect_error[1],from_wallet.send, - outputs=outputs,conf_target=arg_conf_target,estimate_mode=arg_estimate_mode,options=options) + assert_raises_rpc_error(expect_error[0], expect_error[1], from_wallet.send, + outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options) except AssertionError: # Provide debug info if the test fails self.log.error("Unexpected successful result:") self.log.error(options) - res = from_wallet.send(outputs=outputs,conf_target=arg_conf_target,estimate_mode=arg_estimate_mode,options=options) + res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options) self.log.error(res) if "txid" in res and add_to_wallet: self.log.error("Transaction details:") @@ -131,7 +131,7 @@ class WalletSendTest(BitcoinTestFramework): assert tx assert_equal(tx["bip125-replaceable"], "yes" if replaceable else "no") # Ensure transaction exists in the mempool: - tx = from_wallet.getrawtransaction(res["txid"],True) + tx = from_wallet.getrawtransaction(res["txid"], True) assert tx if amount: if subtract_fee_from_outputs: @@ -164,7 +164,7 @@ class WalletSendTest(BitcoinTestFramework): self.nodes[1].createwallet(wallet_name="w2") w2 = self.nodes[1].get_wallet_rpc("w2") # w3 is a watch-only wallet, based on w2 - self.nodes[1].createwallet(wallet_name="w3",disable_private_keys=True) + self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True) w3 = self.nodes[1].get_wallet_rpc("w3") for _ in range(3): a2_receive = w2.getnewaddress() @@ -188,7 +188,7 @@ class WalletSendTest(BitcoinTestFramework): self.sync_blocks() # w4 has private keys enabled, but only contains watch-only keys (from w2) - self.nodes[1].createwallet(wallet_name="w4",disable_private_keys=False) + self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False) w4 = self.nodes[1].get_wallet_rpc("w4") for _ in range(3): a2_receive = w2.getnewaddress() @@ -253,7 +253,7 @@ class WalletSendTest(BitcoinTestFramework): self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=-1, estimate_mode="sat/b", expect_error=(-3, "Amount out of range")) # Fee rate of 0.1 satoshi per byte should throw an error - # TODO: error should say 1.000 sat/b + # TODO: error should use sat/b self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b", expect_error=(-4, "Fee rate (0.00000100 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)")) @@ -325,11 +325,16 @@ class WalletSendTest(BitcoinTestFramework): locked_coins = w0.listlockunspent() assert_equal(len(locked_coins), 1) # Locked coins are automatically unlocked when manually selected - self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1],add_to_wallet=False) + res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, inputs=[utxo1], add_to_wallet=False) + assert res["complete"] self.log.info("Replaceable...") - self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, replaceable=True) - self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, replaceable=False) + res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True, replaceable=True) + assert res["complete"] + assert_equal(self.nodes[0].gettransaction(res["txid"])["bip125-replaceable"], "yes") + res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=True, replaceable=False) + assert res["complete"] + assert_equal(self.nodes[0].gettransaction(res["txid"])["bip125-replaceable"], "no") self.log.info("Subtract fee from output") self.test_send(from_wallet=w0, to_wallet=w1, amount=1, subtract_fee_from_outputs=[0]) diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py index 5e1a804d33..33a2a9411e 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -25,7 +25,7 @@ class TxnMallTest(BitcoinTestFramework): parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true", help="Test double-spend of 1-confirmed transaction") parser.add_argument("--segwit", dest="segwit", default=False, action="store_true", - help="Test behaviour with SegWit txn (which should fail") + help="Test behaviour with SegWit txn (which should fail)") def setup_network(self): # Start with split network: diff --git a/test/sanitizer_suppressions/ubsan b/test/sanitizer_suppressions/ubsan index b3d9b9e6ec..75257d886b 100644 --- a/test/sanitizer_suppressions/ubsan +++ b/test/sanitizer_suppressions/ubsan @@ -1,6 +1,5 @@ # -fsanitize=undefined suppressions # ================================= -float-divide-by-zero:policy/fees.cpp float-divide-by-zero:validation.cpp float-divide-by-zero:wallet/wallet.cpp |