diff options
-rw-r--r-- | Makefile.am | 13 | ||||
-rw-r--r-- | contrib/gitian-descriptors/gitian-linux.yml | 4 | ||||
-rw-r--r-- | contrib/gitian-descriptors/gitian-osx-signer.yml | 2 | ||||
-rw-r--r-- | contrib/gitian-descriptors/gitian-osx.yml | 4 | ||||
-rw-r--r-- | contrib/gitian-descriptors/gitian-win.yml | 4 | ||||
-rw-r--r-- | contrib/macdeploy/Base.lproj/InfoPlist.strings | 1 | ||||
-rw-r--r-- | contrib/macdeploy/DS_Store | bin | 10244 -> 10244 bytes | |||
-rwxr-xr-x | contrib/macdeploy/detached-sig-create.sh | 2 | ||||
-rw-r--r-- | contrib/macdeploy/fancy.plist | 2 | ||||
-rwxr-xr-x | contrib/macdeploy/macdeployqtplus | 6 | ||||
-rw-r--r-- | doc/build-osx.md | 4 | ||||
-rwxr-xr-x | qa/pull-tester/rpc-tests.sh | 1 | ||||
-rwxr-xr-x | qa/rpc-tests/p2p-acceptblock.py | 226 | ||||
-rw-r--r-- | share/certs/PrivateKeyNotes.md | 4 | ||||
-rw-r--r-- | share/qt/Info.plist.in | 8 | ||||
-rw-r--r-- | src/json/json_spirit_reader_template.h | 6 | ||||
-rw-r--r-- | src/main.cpp | 40 | ||||
-rw-r--r-- | src/main.h | 7 | ||||
-rw-r--r-- | src/miner.cpp | 2 | ||||
-rw-r--r-- | src/rpcmining.cpp | 4 | ||||
-rw-r--r-- | src/test/miner_tests.cpp | 2 | ||||
-rw-r--r-- | src/test/rpc_tests.cpp | 18 |
22 files changed, 314 insertions, 46 deletions
diff --git a/Makefile.am b/Makefile.am index 56c8c09223..2f478e4b41 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,11 +14,12 @@ BITCOIN_QT_BIN=$(top_builddir)/src/qt/bitcoin-qt$(EXEEXT) BITCOIN_CLI_BIN=$(top_builddir)/src/bitcoin-cli$(EXEEXT) BITCOIN_WIN_INSTALLER=$(PACKAGE)-$(PACKAGE_VERSION)-win$(WINDOWS_BITS)-setup$(EXEEXT) -OSX_APP=Bitcoin-Core.app +OSX_APP=Bitcoin-Qt.app OSX_DMG=Bitcoin-Core.dmg OSX_BACKGROUND_IMAGE=background.tiff OSX_DEPLOY_SCRIPT=$(top_srcdir)/contrib/macdeploy/macdeployqtplus OSX_FANCY_PLIST=$(top_srcdir)/contrib/macdeploy/fancy.plist +OSX_BASE_LPROJ_DIR=$(top_srcdir)/contrib/macdeploy/Base.lproj/InfoPlist.strings OSX_INSTALLER_ICONS=$(top_srcdir)/src/qt/res/icons/bitcoin.icns OSX_PLIST=$(top_srcdir)/share/qt/Info.plist #not installed OSX_QT_TRANSLATIONS = da,de,es,hu,ru,uk,zh_CN,zh_TW @@ -30,7 +31,7 @@ WINDOWS_PACKAGING = $(top_srcdir)/share/pixmaps/bitcoin.ico \ $(top_srcdir)/share/pixmaps/nsis-wizard.bmp \ $(top_srcdir)/doc/README_windows.txt -OSX_PACKAGING = $(OSX_DEPLOY_SCRIPT) $(OSX_FANCY_PLIST) $(OSX_INSTALLER_ICONS) \ +OSX_PACKAGING = $(OSX_DEPLOY_SCRIPT) $(OSX_FANCY_PLIST) $(OSX_INSTALLER_ICONS) $(OSX_BASE_LPROJ_DIR) \ $(top_srcdir)/contrib/macdeploy/$(OSX_BACKGROUND_IMAGE) \ $(top_srcdir)/contrib/macdeploy/DS_Store \ $(top_srcdir)/contrib/macdeploy/detached-sig-apply.sh \ @@ -72,7 +73,7 @@ $(OSX_APP)/Contents/PkgInfo: $(OSX_APP)/Contents/Resources/empty.lproj: $(MKDIR_P) $(@D) - @touch $@ + @touch $@ $(OSX_APP)/Contents/Info.plist: $(OSX_PLIST) $(MKDIR_P) $(@D) @@ -86,9 +87,13 @@ $(OSX_APP)/Contents/MacOS/Bitcoin-Qt: $(BITCOIN_QT_BIN) $(MKDIR_P) $(@D) STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $< $@ +$(OSX_APP)/Contents/Resources/Base.lproj/InfoPlist.strings: $(OSX_BASE_LPROJ_DIR) + $(MKDIR_P) $(@D) + $(INSTALL_DATA) $< $@ + OSX_APP_BUILT=$(OSX_APP)/Contents/PkgInfo $(OSX_APP)/Contents/Resources/empty.lproj \ $(OSX_APP)/Contents/Resources/bitcoin.icns $(OSX_APP)/Contents/Info.plist \ - $(OSX_APP)/Contents/MacOS/Bitcoin-Qt + $(OSX_APP)/Contents/MacOS/Bitcoin-Qt $(OSX_APP)/Contents/Resources/Base.lproj/InfoPlist.strings if BUILD_DARWIN $(OSX_DMG): $(OSX_APP_BUILT) $(OSX_PACKAGING) diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index dde4af3491..440d95a355 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -1,5 +1,5 @@ --- -name: "bitcoin-linux-0.10" +name: "bitcoin-linux-0.12" enable_cache: true suites: - "precise" @@ -16,7 +16,7 @@ packages: - "bsdmainutils" - "binutils-gold" - "libstdc++6-4.6-pic" -reference_datetime: "2013-06-01 00:00:00" +reference_datetime: "2015-06-01 00:00:00" remotes: - "url": "https://github.com/bitcoin/bitcoin.git" "dir": "bitcoin" diff --git a/contrib/gitian-descriptors/gitian-osx-signer.yml b/contrib/gitian-descriptors/gitian-osx-signer.yml index afe03c7a22..c0c0b0c98b 100644 --- a/contrib/gitian-descriptors/gitian-osx-signer.yml +++ b/contrib/gitian-descriptors/gitian-osx-signer.yml @@ -7,7 +7,7 @@ architectures: packages: - "libc6:i386" - "faketime" -reference_datetime: "2013-06-01 00:00:00" +reference_datetime: "2015-06-01 00:00:00" remotes: [] files: - "bitcoin-osx-unsigned.tar.gz" diff --git a/contrib/gitian-descriptors/gitian-osx.yml b/contrib/gitian-descriptors/gitian-osx.yml index 61eb5b1001..bad9a0e768 100644 --- a/contrib/gitian-descriptors/gitian-osx.yml +++ b/contrib/gitian-descriptors/gitian-osx.yml @@ -1,5 +1,5 @@ --- -name: "bitcoin-osx-0.10" +name: "bitcoin-osx-0.12" enable_cache: true suites: - "precise" @@ -18,7 +18,7 @@ packages: - "libcap-dev" - "libz-dev" - "libbz2-dev" -reference_datetime: "2013-06-01 00:00:00" +reference_datetime: "2015-06-01 00:00:00" remotes: - "url": "https://github.com/bitcoin/bitcoin.git" "dir": "bitcoin" diff --git a/contrib/gitian-descriptors/gitian-win.yml b/contrib/gitian-descriptors/gitian-win.yml index 2d72f7b6e5..20cfb29859 100644 --- a/contrib/gitian-descriptors/gitian-win.yml +++ b/contrib/gitian-descriptors/gitian-win.yml @@ -1,5 +1,5 @@ --- -name: "bitcoin-win-0.10" +name: "bitcoin-win-0.12" enable_cache: true suites: - "precise" @@ -18,7 +18,7 @@ packages: - "g++-mingw-w64" - "nsis" - "zip" -reference_datetime: "2013-06-01 00:00:00" +reference_datetime: "2015-06-01 00:00:00" remotes: - "url": "https://github.com/bitcoin/bitcoin.git" "dir": "bitcoin" diff --git a/contrib/macdeploy/Base.lproj/InfoPlist.strings b/contrib/macdeploy/Base.lproj/InfoPlist.strings new file mode 100644 index 0000000000..b259ea141c --- /dev/null +++ b/contrib/macdeploy/Base.lproj/InfoPlist.strings @@ -0,0 +1 @@ +{ CFBundleDisplayName = "Bitcoin Core"; CFBundleName = "Bitcoin Core"; } diff --git a/contrib/macdeploy/DS_Store b/contrib/macdeploy/DS_Store Binary files differindex ca19b207c0..db9d16f1d7 100644 --- a/contrib/macdeploy/DS_Store +++ b/contrib/macdeploy/DS_Store diff --git a/contrib/macdeploy/detached-sig-create.sh b/contrib/macdeploy/detached-sig-create.sh index fd7314bd7e..18f8661cea 100755 --- a/contrib/macdeploy/detached-sig-create.sh +++ b/contrib/macdeploy/detached-sig-create.sh @@ -2,7 +2,7 @@ set -e ROOTDIR=dist -BUNDLE="${ROOTDIR}/Bitcoin Core.app" +BUNDLE="${ROOTDIR}/Bitcoin-Qt.app" CODESIGN=codesign TEMPDIR=sign.temp TEMPLIST=${TEMPDIR}/signatures.txt diff --git a/contrib/macdeploy/fancy.plist b/contrib/macdeploy/fancy.plist index a333f5dccd..ef277a7f14 100644 --- a/contrib/macdeploy/fancy.plist +++ b/contrib/macdeploy/fancy.plist @@ -22,7 +22,7 @@ <integer>370</integer> <integer>156</integer> </array> - <key>Bitcoin Core.app</key> + <key>Bitcoin-Qt.app</key> <array> <integer>128</integer> <integer>156</integer> diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus index a625987ca7..2253c40af1 100755 --- a/contrib/macdeploy/macdeployqtplus +++ b/contrib/macdeploy/macdeployqtplus @@ -596,7 +596,7 @@ if os.path.exists("dist"): # ------------------------------------------------ -target = os.path.join("dist", "Bitcoin Core.app") +target = os.path.join("dist", "Bitcoin-Qt.app") if verbose >= 2: print "+ Copying source bundle +" @@ -757,7 +757,7 @@ if config.dmg is not None: if fancy is None: try: - runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname=app_bundle_name, ov=True) + runHDIUtil("create", dmg_name, srcfolder="dist", format="UDBZ", volname="Bitcoin-Core", ov=True) except subprocess.CalledProcessError as e: sys.exit(e.returncode) else: @@ -772,7 +772,7 @@ if config.dmg is not None: if verbose >= 3: print "Creating temp image for modification..." try: - runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname=app_bundle_name, ov=True) + runHDIUtil("create", dmg_name + ".temp", srcfolder="dist", format="UDRW", size=size, volname="Bitcoin-Core", ov=True) except subprocess.CalledProcessError as e: sys.exit(e.returncode) diff --git a/doc/build-osx.md b/doc/build-osx.md index 02adff0615..913e72519f 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -100,7 +100,7 @@ Creating a release build ------------------------ You can ignore this section if you are building `bitcoind` for your own use. -bitcoind/bitcoin-cli binaries are not included in the Bitcoin-Core.app bundle. +bitcoind/bitcoin-cli binaries are not included in the Bitcoin-Qt.app bundle. If you are building `bitcoind` or `Bitcoin-Qt` for others, your build machine should be set up as follows for maximum compatibility: @@ -111,7 +111,7 @@ All dependencies should be compiled with these flags: -arch x86_64 -isysroot $(xcode-select --print-path)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk -Once dependencies are compiled, see [doc/release-process.md](release-process.md) for how the Bitcoin-Core.app +Once dependencies are compiled, see [doc/release-process.md](release-process.md) for how the Bitcoin-Qt.app bundle is packaged and signed to create the .dmg disk image that is distributed. Running diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 88772a9c32..9b318650e9 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -51,6 +51,7 @@ testScriptsExt=( 'invalidblockrequest.py' 'rawtransactions.py' # 'forknotify.py' + 'p2p-acceptblock.py' ); extArg="-extended" diff --git a/qa/rpc-tests/p2p-acceptblock.py b/qa/rpc-tests/p2p-acceptblock.py new file mode 100755 index 0000000000..fcdd1e1b99 --- /dev/null +++ b/qa/rpc-tests/p2p-acceptblock.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python2 +# +# Distributed under the MIT/X11 software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# + +from test_framework.mininode import * +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +import time +from test_framework.blocktools import create_block, create_coinbase + +''' +AcceptBlockTest -- test processing of unrequested blocks. + +Since behavior differs when receiving unrequested blocks from whitelisted peers +versus non-whitelisted peers, this tests the behavior of both (effectively two +separate tests running in parallel). + +Setup: two nodes, node0 and node1, not connected to each other. Node0 does not +whitelist localhost, but node1 does. They will each be on their own chain for +this test. + +We have one NodeConn connection to each, test_node and white_node respectively. + +The test: +1. Generate one block on each node, to leave IBD. + +2. Mine a new block on each tip, and deliver to each node from node's peer. + The tip should advance. + +3. Mine a block that forks the previous block, and deliver to each node from + corresponding peer. + Node0 should not process this block (just accept the header), because it is + unrequested and doesn't have more work than the tip. + Node1 should process because this is coming from a whitelisted peer. + +4. Send another block that builds on the forking block. + Node0 should process this block but be stuck on the shorter chain, because + it's missing an intermediate block. + Node1 should reorg to this longer chain. + +5. Send a duplicate of the block in #3 to Node0. + Node0 should not process the block because it is unrequested, and stay on + the shorter chain. + +6. Send Node0 an inv for the height 3 block produced in #4 above. + Node0 should figure out that Node0 has the missing height 2 block and send a + getdata. + +7. Send Node0 the missing block again. + Node0 should process and the tip should advance. +''' + +# TestNode: bare-bones "peer". Used mostly as a conduit for a test to sending +# p2p messages to a node, generating the messages in the main testing logic. +class TestNode(NodeConnCB): + def __init__(self): + NodeConnCB.__init__(self) + self.create_callback_map() + self.connection = None + + def add_connection(self, conn): + self.connection = conn + + # Track the last getdata message we receive (used in the test) + def on_getdata(self, conn, message): + self.last_getdata = message + + # Spin until verack message is received from the node. + # We use this to signal that our test can begin. This + # is called from the testing thread, so it needs to acquire + # the global lock. + def wait_for_verack(self): + while True: + with mininode_lock: + if self.verack_received: + return + time.sleep(0.05) + + # Wrapper for the NodeConn's send_message function + def send_message(self, message): + self.connection.send_message(message) + +class AcceptBlockTest(BitcoinTestFramework): + def add_options(self, parser): + parser.add_option("--testbinary", dest="testbinary", + default=os.getenv("BITCOIND", "bitcoind"), + help="bitcoind binary to test") + + def setup_chain(self): + initialize_chain_clean(self.options.tmpdir, 2) + + def setup_network(self): + # Node0 will be used to test behavior of processing unrequested blocks + # from peers which are not whitelisted, while Node1 will be used for + # the whitelisted case. + self.nodes = [] + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"], + binary=self.options.testbinary)) + self.nodes.append(start_node(1, self.options.tmpdir, + ["-debug", "-whitelist=127.0.0.1"], + binary=self.options.testbinary)) + + def run_test(self): + # Setup the p2p connections and start up the network thread. + test_node = TestNode() # connects to node0 (not whitelisted) + white_node = TestNode() # connects to node1 (whitelisted) + + connections = [] + connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)) + connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], white_node)) + test_node.add_connection(connections[0]) + white_node.add_connection(connections[1]) + + NetworkThread().start() # Start up network handling in another thread + + # Test logic begins here + test_node.wait_for_verack() + white_node.wait_for_verack() + + # 1. Have both nodes mine a block (leave IBD) + [ n.generate(1) for n in self.nodes ] + tips = [ int ("0x" + n.getbestblockhash() + "L", 0) for n in self.nodes ] + + # 2. Send one block that builds on each tip. + # This should be accepted. + blocks_h2 = [] # the height 2 blocks on each node's chain + for i in xrange(2): + blocks_h2.append(create_block(tips[i], create_coinbase(), time.time()+1)) + blocks_h2[i].solve() + test_node.send_message(msg_block(blocks_h2[0])) + white_node.send_message(msg_block(blocks_h2[1])) + + time.sleep(1) + assert_equal(self.nodes[0].getblockcount(), 2) + assert_equal(self.nodes[1].getblockcount(), 2) + print "First height 2 block accepted by both nodes" + + # 3. Send another block that builds on the original tip. + blocks_h2f = [] # Blocks at height 2 that fork off the main chain + for i in xrange(2): + blocks_h2f.append(create_block(tips[i], create_coinbase(), blocks_h2[i].nTime+1)) + blocks_h2f[i].solve() + test_node.send_message(msg_block(blocks_h2f[0])) + white_node.send_message(msg_block(blocks_h2f[1])) + + time.sleep(1) # Give time to process the block + for x in self.nodes[0].getchaintips(): + if x['hash'] == blocks_h2f[0].hash: + assert_equal(x['status'], "headers-only") + + for x in self.nodes[1].getchaintips(): + if x['hash'] == blocks_h2f[1].hash: + assert_equal(x['status'], "valid-headers") + + print "Second height 2 block accepted only from whitelisted peer" + + # 4. Now send another block that builds on the forking chain. + blocks_h3 = [] + for i in xrange(2): + blocks_h3.append(create_block(blocks_h2f[i].sha256, create_coinbase(), blocks_h2f[i].nTime+1)) + blocks_h3[i].solve() + test_node.send_message(msg_block(blocks_h3[0])) + white_node.send_message(msg_block(blocks_h3[1])) + + time.sleep(1) + # Since the earlier block was not processed by node0, the new block + # can't be fully validated. + for x in self.nodes[0].getchaintips(): + if x['hash'] == blocks_h3[0].hash: + assert_equal(x['status'], "headers-only") + + # But this block should be accepted by node0 since it has more work. + try: + self.nodes[0].getblock(blocks_h3[0].hash) + print "Unrequested more-work block accepted from non-whitelisted peer" + except: + raise AssertionError("Unrequested more work block was not processed") + + # Node1 should have accepted and reorged. + assert_equal(self.nodes[1].getblockcount(), 3) + print "Successfully reorged to length 3 chain from whitelisted peer" + + # 5. Test handling of unrequested block on the node that didn't process + # Should still not be processed (even though it has a child that has more + # work). + test_node.send_message(msg_block(blocks_h2f[0])) + + # Here, if the sleep is too short, the test could falsely succeed (if the + # node hasn't processed the block by the time the sleep returns, and then + # the node processes it and incorrectly advances the tip). + # But this would be caught later on, when we verify that an inv triggers + # a getdata request for this block. + time.sleep(1) + assert_equal(self.nodes[0].getblockcount(), 2) + print "Unrequested block that would complete more-work chain was ignored" + + # 6. Try to get node to request the missing block. + # Poke the node with an inv for block at height 3 and see if that + # triggers a getdata on block 2 (it should if block 2 is missing). + with mininode_lock: + # Clear state so we can check the getdata request + test_node.last_getdata = None + test_node.send_message(msg_inv([CInv(2, blocks_h3[0].sha256)])) + + time.sleep(1) + with mininode_lock: + getdata = test_node.last_getdata + + # Check that the getdata is for the right block + assert_equal(len(getdata.inv), 1) + assert_equal(getdata.inv[0].hash, blocks_h2f[0].sha256) + print "Inv at tip triggered getdata for unprocessed block" + + # 7. Send the missing block for the third time (now it is requested) + test_node.send_message(msg_block(blocks_h2f[0])) + + time.sleep(1) + assert_equal(self.nodes[0].getblockcount(), 3) + print "Successfully reorged to length 3 chain from non-whitelisted peer" + + [ c.disconnect_node() for c in connections ] + +if __name__ == '__main__': + AcceptBlockTest().main() diff --git a/share/certs/PrivateKeyNotes.md b/share/certs/PrivateKeyNotes.md index cbd060c268..da299d168f 100644 --- a/share/certs/PrivateKeyNotes.md +++ b/share/certs/PrivateKeyNotes.md @@ -7,7 +7,7 @@ signing requests. For OSX, the private key was generated by Keychain.app on Gavin's main work machine. The key and certificate is in a separate, passphrase-protected keychain file that is -unlocked to sign the Bitcoin-Core.app bundle. +unlocked to sign the Bitcoin-Qt.app bundle. For Windows, the private key was generated by Firefox running on Gavin's main work machine. The key and certificate were exported into a separate, passphrase-protected PKCS#12 file, and @@ -17,7 +17,7 @@ Threat analysis -- Gavin is a single point of failure. He could be coerced to divulge the secret signing keys, -allowing somebody to distribute a Bitcoin-Core.app or bitcoin-qt-setup.exe with a valid +allowing somebody to distribute a Bitcoin-Qt.app or bitcoin-qt-setup.exe with a valid signature but containing a malicious binary. Or the machine Gavin uses to sign the binaries could be compromised, either remotely or diff --git a/share/qt/Info.plist.in b/share/qt/Info.plist.in index dd6edde8d2..a389332a52 100644 --- a/share/qt/Info.plist.in +++ b/share/qt/Info.plist.in @@ -3,7 +3,7 @@ <plist version="0.9"> <dict> <key>LSMinimumSystemVersion</key> - <string>10.6.0</string> + <string>10.7.0</string> <key>LSArchitecturePriority</key> <array> @@ -30,6 +30,12 @@ <key>CFBundleExecutable</key> <string>Bitcoin-Qt</string> + + <key>CFBundleName</key> + <string>Bitcoin-Qt</string> + + <key>LSHasLocalizedDisplayName</key> + <true/> <key>CFBundleIdentifier</key> <string>org.bitcoinfoundation.Bitcoin-Qt</string> diff --git a/src/json/json_spirit_reader_template.h b/src/json/json_spirit_reader_template.h index 46f5892f62..47e3c1ca84 100644 --- a/src/json/json_spirit_reader_template.h +++ b/src/json/json_spirit_reader_template.h @@ -521,12 +521,11 @@ namespace json_spirit const spirit_namespace::parse_info< Iter_type > info = spirit_namespace::parse( begin, end, - Json_grammer< Value_type, Iter_type >( semantic_actions ), + Json_grammer< Value_type, Iter_type >( semantic_actions ) >> spirit_namespace::end_p, spirit_namespace::space_p ); if( !info.hit ) { - assert( false ); // in theory exception should already have been thrown throw_error( info.stop, "error" ); } @@ -570,7 +569,8 @@ namespace json_spirit { typename String_type::const_iterator begin = s.begin(); - return read_range( begin, s.end(), value ); + bool success = read_range( begin, s.end(), value ); + return success && begin == s.end(); } template< class Istream_type > diff --git a/src/main.cpp b/src/main.cpp index efb7fb3f87..760cda5dee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -296,7 +296,8 @@ void FinalizeNode(NodeId nodeid) { } // Requires cs_main. -void MarkBlockAsReceived(const uint256& hash) { +// Returns a bool indicating whether we requested this block. +bool MarkBlockAsReceived(const uint256& hash) { map<uint256, pair<NodeId, list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash); if (itInFlight != mapBlocksInFlight.end()) { CNodeState *state = State(itInFlight->second.first); @@ -306,7 +307,9 @@ void MarkBlockAsReceived(const uint256& hash) { state->nBlocksInFlight--; state->nStallingSince = 0; mapBlocksInFlight.erase(itInFlight); + return true; } + return false; } // Requires cs_main. @@ -2826,7 +2829,7 @@ bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBloc return true; } -bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, CDiskBlockPos* dbp) +bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, bool fRequested, CDiskBlockPos* dbp) { const CChainParams& chainparams = Params(); AssertLockHeld(cs_main); @@ -2836,13 +2839,18 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, if (!AcceptBlockHeader(block, state, &pindex)) return false; - // If we're pruning, ensure that we don't allow a peer to dump a copy - // of old blocks. But we might need blocks that are not on the main chain - // to handle a reorg, even if we've processed once. - if (pindex->nStatus & BLOCK_HAVE_DATA || chainActive.Contains(pindex)) { - // TODO: deal better with duplicate blocks. - // return state.DoS(20, error("AcceptBlock(): already have block %d %s", pindex->nHeight, pindex->GetBlockHash().ToString()), REJECT_DUPLICATE, "duplicate"); - return true; + // Try to process all requested blocks that we don't have, but only + // process an unrequested block if it's new and has enough work to + // advance our tip. + bool fAlreadyHave = pindex->nStatus & BLOCK_HAVE_DATA; + bool fHasMoreWork = (chainActive.Tip() ? pindex->nChainWork > chainActive.Tip()->nChainWork : true); + + // TODO: deal better with return value and error conditions for duplicate + // and unrequested blocks. + if (fAlreadyHave) return true; + if (!fRequested) { // If we didn't ask for it: + if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned + if (!fHasMoreWork) return true; // Don't process less-work chains } if ((!CheckBlock(block, state)) || !ContextualCheckBlock(block, state, pindex->pprev)) { @@ -2891,21 +2899,22 @@ static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned } -bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBlockPos *dbp) +bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp) { // Preliminary checks bool checked = CheckBlock(*pblock, state); { LOCK(cs_main); - MarkBlockAsReceived(pblock->GetHash()); + bool fRequested = MarkBlockAsReceived(pblock->GetHash()); + fRequested |= fForceProcessing; if (!checked) { return error("%s: CheckBlock FAILED", __func__); } // Store to disk CBlockIndex *pindex = NULL; - bool ret = AcceptBlock(*pblock, state, &pindex, dbp); + bool ret = AcceptBlock(*pblock, state, &pindex, fRequested, dbp); if (pindex && pfrom) { mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId(); } @@ -3453,7 +3462,7 @@ bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp) // process in case the block isn't known yet if (mapBlockIndex.count(hash) == 0 || (mapBlockIndex[hash]->nStatus & BLOCK_HAVE_DATA) == 0) { CValidationState state; - if (ProcessNewBlock(state, NULL, &block, dbp)) + if (ProcessNewBlock(state, NULL, &block, true, dbp)) nLoaded++; if (state.IsError()) break; @@ -3475,7 +3484,7 @@ bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp) LogPrintf("%s: Processing out of order child %s of %s\n", __func__, block.GetHash().ToString(), head.ToString()); CValidationState dummy; - if (ProcessNewBlock(dummy, NULL, &block, &it->second)) + if (ProcessNewBlock(dummy, NULL, &block, true, &it->second)) { nLoaded++; queue.push_back(block.GetHash()); @@ -4462,7 +4471,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, pfrom->AddInventoryKnown(inv); CValidationState state; - ProcessNewBlock(state, pfrom, &block); + // Process all blocks from whitelisted peers, even if not requested. + ProcessNewBlock(state, pfrom, &block, pfrom->fWhitelisted, NULL); int nDoS; if (state.IsInvalid(nDoS)) { pfrom->PushMessage("reject", strCommand, state.GetRejectCode(), diff --git a/src/main.h b/src/main.h index 3b25217869..669ea7128a 100644 --- a/src/main.h +++ b/src/main.h @@ -153,10 +153,11 @@ void UnregisterNodeSignals(CNodeSignals& nodeSignals); * @param[out] state This may be set to an Error state if any error occurred processing it, including during validation/connection/etc of otherwise unrelated blocks during reorganisation; or it may be set to an Invalid state if pblock is itself invalid (but this is not guaranteed even when the block is checked). If you want to *possibly* get feedback on whether pblock is valid, you must also install a CValidationInterface (see validationinterface.h) - this will have its BlockChecked method called whenever *any* block completes validation. * @param[in] pfrom The node which we are receiving the block from; it is added to mapBlockSource and may be penalised if the block is invalid. * @param[in] pblock The block we want to process. + * @param[in] fForceProcessing Process this block even if unrequested; used for non-network block sources and whitelisted peers. * @param[out] dbp If pblock is stored to disk (or already there), this will be set to its location. * @return True if state.IsValid() */ -bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBlockPos *dbp = NULL); +bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, bool fForceProcessing, CDiskBlockPos *dbp); /** Check whether enough disk space is available for an incoming block */ bool CheckDiskSpace(uint64_t nAdditionalBytes = 0); /** Open a block file (blk?????.dat) */ @@ -400,8 +401,8 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIn /** Check a block is completely valid from start to finish (only works on top of our current best block, with cs_main held) */ bool TestBlockValidity(CValidationState &state, const CBlock& block, CBlockIndex *pindexPrev, bool fCheckPOW = true, bool fCheckMerkleRoot = true); -/** Store block on disk. If dbp is provided, the file is known to already reside on disk */ -bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex **pindex, CDiskBlockPos* dbp = NULL); +/** Store block on disk. If dbp is non-NULL, the file is known to already reside on disk */ +bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex **pindex, bool fRequested, CDiskBlockPos* dbp); bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBlockIndex **ppindex= NULL); diff --git a/src/miner.cpp b/src/miner.cpp index dfabfd48d0..f5919ca3af 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -434,7 +434,7 @@ static bool ProcessBlockFound(CBlock* pblock, CWallet& wallet, CReserveKey& rese // Process this block the same as if we had received it from another node CValidationState state; - if (!ProcessNewBlock(state, NULL, pblock)) + if (!ProcessNewBlock(state, NULL, pblock, true, NULL)) return error("BitcoinMiner: ProcessNewBlock, block not accepted"); return true; diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index d58d438573..528d5406f4 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -164,7 +164,7 @@ Value generate(const Array& params, bool fHelp) ++pblock->nNonce; } CValidationState state; - if (!ProcessNewBlock(state, NULL, pblock)) + if (!ProcessNewBlock(state, NULL, pblock, true, NULL)) throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted"); ++nHeight; blockHashes.push_back(pblock->GetHash().GetHex()); @@ -650,7 +650,7 @@ Value submitblock(const Array& params, bool fHelp) CValidationState state; submitblock_StateCatcher sc(block.GetHash()); RegisterValidationInterface(&sc); - bool fAccepted = ProcessNewBlock(state, NULL, &block); + bool fAccepted = ProcessNewBlock(state, NULL, &block, true, NULL); UnregisterValidationInterface(&sc); if (fBlockPresent) { diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index e3e9061cce..9e4fd8513e 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -84,7 +84,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) pblock->hashMerkleRoot = pblock->BuildMerkleTree(); pblock->nNonce = blockinfo[i].nonce; CValidationState state; - BOOST_CHECK(ProcessNewBlock(state, NULL, pblock)); + BOOST_CHECK(ProcessNewBlock(state, NULL, pblock, true, NULL)); BOOST_CHECK(state.IsValid()); pblock->hashPrevBlock = pblock->GetHash(); } diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 45cb551d04..5899671d2f 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -140,6 +140,24 @@ BOOST_AUTO_TEST_CASE(rpc_parse_monetary_values) BOOST_CHECK_EQUAL(AmountFromValue(ValueFromString("20999999.99999999")), 2099999999999999LL); } +BOOST_AUTO_TEST_CASE(json_parse_errors) +{ + Value value; + // Valid + BOOST_CHECK_EQUAL(read_string(std::string("1.0"), value), true); + // Valid, with trailing whitespace + BOOST_CHECK_EQUAL(read_string(std::string("1.0 "), value), true); + // Invalid, initial garbage + BOOST_CHECK_EQUAL(read_string(std::string("[1.0"), value), false); + BOOST_CHECK_EQUAL(read_string(std::string("a1.0"), value), false); + // Invalid, trailing garbage + BOOST_CHECK_EQUAL(read_string(std::string("1.0sds"), value), false); + BOOST_CHECK_EQUAL(read_string(std::string("1.0]"), value), false); + // BTC addresses should fail parsing + BOOST_CHECK_EQUAL(read_string(std::string("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"), value), false); + BOOST_CHECK_EQUAL(read_string(std::string("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNL"), value), false); +} + BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr) { // Check IPv4 addresses |