diff --git a/doc/bips.md b/doc/bips.md --- a/doc/bips.md +++ b/doc/bips.md @@ -33,3 +33,4 @@ * [`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 145 has been removed in Bitcoin ABC. * [`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 147 has been removed in Bitcoin ABC. * [`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.18.7**. diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1292,7 +1292,7 @@ int nMaxConnections; int nUserMaxConnections; int nFD; -ServiceFlags nLocalServices = NODE_NETWORK; +ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED); } // namespace [[noreturn]] static void new_handler_terminate() { diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1306,6 +1306,26 @@ pfrom->fDisconnect = true; send = false; } + // Avoid leaking prune-height by never sending blocks below the + // NODE_NETWORK_LIMITED threshold. + // Add two blocks buffer extension for possible races + 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)))) { + 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.hasData())) { diff --git a/src/protocol.h b/src/protocol.h --- a/src/protocol.h +++ b/src/protocol.h @@ -280,9 +280,9 @@ 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 ABC 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 ABC 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 ABC does not support this but a patch set @@ -304,6 +304,10 @@ // TODO: remove (free up) the NODE_BITCOIN_CASH service bit once no longer // needed. NODE_BITCOIN_CASH = (1 << 5), + // 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 --- a/src/validation.h +++ b/src/validation.h @@ -262,6 +262,8 @@ /** 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 --- /dev/null +++ b/test/functional/node_network_limited.py @@ -0,0 +1,90 @@ +#!/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_BITCOIN_CASH & NODE_NETWORK_LIMITED must now be signaled + # 1060 == 0x424 == 0100 0010 0100 + # | | | + # | | ^--- NODE_BLOOM + # | ^---- NODE_BITCOIN_CASH + # ^-- NODE_NETWORK_LIMITED + assert_equal(self.getSignaledServiceFlags(), 1060) + + # 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) + # first block must lead to disconnect + self.tryGetBlockViaGetData(firstblock, True) + # last block in valid range + self.tryGetBlockViaGetData(blocks[1], False) + # first block outside of the 288+2 limit + self.tryGetBlockViaGetData(blocks[0], True) + + # NODE_NETWORK_LIMITED must still be signaled after restart + self.restart_node(0) + assert_equal(self.getSignaledServiceFlags(), 1060) + + # test the RPC service flags + assert_equal(self.nodes[0].getnetworkinfo()[ + 'localservices'], "0000000000000424") + + # 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()