diff --git a/doc/bips.md b/doc/bips.md index bc8dcb6fb..fbff94a32 100644 --- a/doc/bips.md +++ b/doc/bips.md @@ -33,3 +33,4 @@ BIPs that are implemented by Bitcoin Core (up-to-date up to **v0.13.0**): * [`BIP 145`](https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki): getblocktemplate updates for Segregated Witness as of **v0.13.0** ([PR 8149](https://github.com/bitcoin/bitcoin/pull/8149)). * [`BIP 147`](https://github.com/bitcoin/bips/blob/master/bip-0147.mediawiki): NULLDUMMY softfork as of **v0.13.1** ([PR 8636](https://github.com/bitcoin/bitcoin/pull/8636) and [PR 8937](https://github.com/bitcoin/bitcoin/pull/8937)). * [`BIP 152`](https://github.com/bitcoin/bips/blob/master/bip-0152.mediawiki): Compact block transfer and related optimizations are used as of **v0.13.0** ([PR 8068](https://github.com/bitcoin/bitcoin/pull/8068)). +* [`BIP 159`](https://github.com/bitcoin/bips/blob/master/bip-0159.mediawiki): NODE_NETWORK_LIMITED service bit [signaling only] is supported as of **v0.16.0** ([PR 10740](https://github.com/bitcoin/bitcoin/pull/10740)). diff --git a/src/init.cpp b/src/init.cpp index 871a58526..67e01c9ba 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -816,7 +816,7 @@ namespace { // Variables internal to initialization process only int nMaxConnections; int nUserMaxConnections; int nFD; -ServiceFlags nLocalServices = NODE_NETWORK; +ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED); } // namespace diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 442cd00c9..85e6d9c73 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1091,6 +1091,16 @@ void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParam pfrom->fDisconnect = true; send = false; } + // Avoid leaking prune-height by never sending blocks below the NODE_NETWORK_LIMITED threshold + if (send && !pfrom->fWhitelisted && ( + (((pfrom->GetLocalServices() & NODE_NETWORK_LIMITED) == NODE_NETWORK_LIMITED) && ((pfrom->GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) && (chainActive.Tip()->nHeight - mi->second->nHeight > (int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2 /* add two blocks buffer extension for possible races */) ) + )) { + LogPrint(BCLog::NET, "Ignore block request below NODE_NETWORK_LIMITED threshold from peer=%d\n", pfrom->GetId()); + + //disconnect node and prevent it from stalling (would otherwise wait for the missing block) + pfrom->fDisconnect = true; + send = false; + } // Pruned nodes may have deleted the block, so check whether // it's available before trying to send. if (send && (mi->second->nStatus & BLOCK_HAVE_DATA)) diff --git a/src/protocol.h b/src/protocol.h index bc3143451..cf1d40db7 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -246,9 +246,8 @@ const std::vector &getAllNetMessageTypes(); enum ServiceFlags : uint64_t { // Nothing NODE_NONE = 0, - // NODE_NETWORK means that the node is capable of serving the block chain. It is currently - // set by all Bitcoin Core nodes, and is unset by SPV clients or other peers that just want - // network services but don't provide them. + // NODE_NETWORK means that the node is capable of serving the complete block chain. It is currently + // set by all Bitcoin Core non pruned nodes, and is unset by SPV clients or other light clients. NODE_NETWORK = (1 << 0), // NODE_GETUTXO means the node is capable of responding to the getutxo protocol request. // Bitcoin Core does not support this but a patch set called Bitcoin XT does. @@ -264,6 +263,10 @@ enum ServiceFlags : uint64_t { // NODE_XTHIN means the node supports Xtreme Thinblocks // If this is turned off then the node will not service nor make xthin requests NODE_XTHIN = (1 << 4), + // NODE_NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only + // serving the last 288 (2 day) blocks + // See BIP159 for details on how this is implemented. + NODE_NETWORK_LIMITED = (1 << 10), // Bits 24-31 are reserved for temporary experiments. Just pick a bit that // isn't getting used, or one not being used much, and notify the diff --git a/src/validation.h b/src/validation.h index ec17d0d92..49bff9f30 100644 --- a/src/validation.h +++ b/src/validation.h @@ -203,6 +203,8 @@ extern bool fPruneMode; extern uint64_t nPruneTarget; /** Block files containing a block-height within MIN_BLOCKS_TO_KEEP of chainActive.Tip() will not be pruned. */ static const unsigned int MIN_BLOCKS_TO_KEEP = 288; +/** Minimum blocks required to signal NODE_NETWORK_LIMITED */ +static const unsigned int NODE_NETWORK_LIMITED_MIN_BLOCKS = 288; static const signed int DEFAULT_CHECKBLOCKS = 6; static const unsigned int DEFAULT_CHECKLEVEL = 3; diff --git a/test/functional/node_network_limited.py b/test/functional/node_network_limited.py new file mode 100755 index 000000000..6d1bf7ced --- /dev/null +++ b/test/functional/node_network_limited.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from test_framework.mininode import * + +class BaseNode(P2PInterface): + nServices = 0 + firstAddrnServices = 0 + def on_version(self, message): + self.nServices = message.nServices + +class NodeNetworkLimitedTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [['-prune=550']] + + def getSignaledServiceFlags(self): + node = self.nodes[0].add_p2p_connection(BaseNode()) + NetworkThread().start() + node.wait_for_verack() + services = node.nServices + self.nodes[0].disconnect_p2ps() + node.wait_for_disconnect() + return services + + def tryGetBlockViaGetData(self, blockhash, must_disconnect): + node = self.nodes[0].add_p2p_connection(BaseNode()) + NetworkThread().start() + node.wait_for_verack() + node.send_message(msg_verack()) + getdata_request = msg_getdata() + getdata_request.inv.append(CInv(2, int(blockhash, 16))) + node.send_message(getdata_request) + + if (must_disconnect): + #ensure we get disconnected + node.wait_for_disconnect(5) + else: + # check if the peer sends us the requested block + node.wait_for_block(int(blockhash, 16), 3) + self.nodes[0].disconnect_p2ps() + node.wait_for_disconnect() + + def run_test(self): + #NODE_BLOOM & NODE_WITNESS & NODE_NETWORK_LIMITED must now be signaled + assert_equal(self.getSignaledServiceFlags(), 1036) #1036 == 0x40C == 0100 0000 1100 +# | || +# | |^--- NODE_BLOOM +# | ^---- NODE_WITNESS +# ^-- NODE_NETWORK_LIMITED + + #now mine some blocks over the NODE_NETWORK_LIMITED + 2(racy buffer ext.) target + firstblock = self.nodes[0].generate(1)[0] + blocks = self.nodes[0].generate(292) + blockWithinLimitedRange = blocks[-1] + + #make sure we can max retrive block at tip-288 + #requesting block at height 2 (tip-289) must fail (ignored) + self.tryGetBlockViaGetData(firstblock, True) #first block must lead to disconnect + self.tryGetBlockViaGetData(blocks[1], False) #last block in valid range + self.tryGetBlockViaGetData(blocks[0], True) #first block outside of the 288+2 limit + + #NODE_NETWORK_LIMITED must still be signaled after restart + self.restart_node(0) + assert_equal(self.getSignaledServiceFlags(), 1036) + + #test the RPC service flags + assert_equal(self.nodes[0].getnetworkinfo()['localservices'], "000000000000040c") + + # getdata a block above the NODE_NETWORK_LIMITED threshold must be possible + self.tryGetBlockViaGetData(blockWithinLimitedRange, False) + + # getdata a block below the NODE_NETWORK_LIMITED threshold must be ignored + self.tryGetBlockViaGetData(firstblock, True) + +if __name__ == '__main__': + NodeNetworkLimitedTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index e38146a79..58faec521 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -128,6 +128,7 @@ BASE_SCRIPTS= [ 'uacomment.py', 'p2p-acceptblock.py', 'feature_logging.py', + 'node_network_limited.py', ] EXTENDED_SCRIPTS = [