diff options
-rwxr-xr-x | qa/pull-tester/rpc-tests.sh | 1 | ||||
-rwxr-xr-x | qa/rpc-tests/mempool_resurrect_test.py | 88 | ||||
-rwxr-xr-x | qa/rpc-tests/rest.py | 4 | ||||
-rw-r--r-- | src/rest.cpp | 89 | ||||
-rw-r--r-- | src/rpcserver.cpp | 2 |
5 files changed, 150 insertions, 34 deletions
diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index a064fb81e9..b888135492 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -18,6 +18,7 @@ fi if [ "x${ENABLE_BITCOIND}${ENABLE_UTILS}${ENABLE_WALLET}" = "x111" ]; then ${BUILDDIR}/qa/rpc-tests/wallet.py --srcdir "${BUILDDIR}/src" ${BUILDDIR}/qa/rpc-tests/listtransactions.py --srcdir "${BUILDDIR}/src" + ${BUILDDIR}/qa/rpc-tests/mempool_resurrect_test.py --srcdir "${BUILDDIR}/src" ${BUILDDIR}/qa/rpc-tests/txn_doublespend.py --srcdir "${BUILDDIR}/src" ${BUILDDIR}/qa/rpc-tests/txn_doublespend.py --mineblock --srcdir "${BUILDDIR}/src" ${BUILDDIR}/qa/rpc-tests/getchaintips.py --srcdir "${BUILDDIR}/src" diff --git a/qa/rpc-tests/mempool_resurrect_test.py b/qa/rpc-tests/mempool_resurrect_test.py new file mode 100755 index 0000000000..907cbf98f9 --- /dev/null +++ b/qa/rpc-tests/mempool_resurrect_test.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# Copyright (c) 2014 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 resurrection of mined transactions when +# the blockchain is re-organized. +# + +from test_framework import BitcoinTestFramework +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException +from util import * +import os +import shutil + +# Create one-input, one-output, no-fee transaction: +class MempoolCoinbaseTest(BitcoinTestFramework): + + def setup_network(self): + # Just need one node for this test + args = ["-checkmempool", "-debug=mempool"] + self.nodes = [] + self.nodes.append(start_node(0, self.options.tmpdir, args)) + self.is_network_split = False + + def create_tx(self, from_txid, to_address, amount): + inputs = [{ "txid" : from_txid, "vout" : 0}] + outputs = { to_address : amount } + rawtx = self.nodes[0].createrawtransaction(inputs, outputs) + signresult = self.nodes[0].signrawtransaction(rawtx) + assert_equal(signresult["complete"], True) + return signresult["hex"] + + def run_test(self): + node0_address = self.nodes[0].getnewaddress() + + # Spend block 1/2/3's coinbase transactions + # Mine a block. + # Create three more transactions, spending the spends + # Mine another block. + # ... make sure all the transactions are confirmed + # Invalidate both blocks + # ... make sure all the transactions are put back in the mempool + # Mine a new block + # ... make sure all the transactions are confirmed again. + + b = [ self.nodes[0].getblockhash(n) for n in range(1, 4) ] + coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ] + spends1_raw = [ self.create_tx(txid, node0_address, 50) for txid in coinbase_txids ] + spends1_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends1_raw ] + + blocks = [] + blocks.extend(self.nodes[0].setgenerate(True, 1)) + + spends2_raw = [ self.create_tx(txid, node0_address, 49.99) for txid in spends1_id ] + spends2_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends2_raw ] + + blocks.extend(self.nodes[0].setgenerate(True, 1)) + + # mempool should be empty, all txns confirmed + assert_equal(set(self.nodes[0].getrawmempool()), set()) + for txid in spends1_id+spends2_id: + tx = self.nodes[0].gettransaction(txid) + assert(tx["confirmations"] > 0) + + # Use invalidateblock to re-org back; all transactions should + # end up unconfirmed and back in the mempool + for node in self.nodes: + node.invalidateblock(blocks[0]) + + # mempool should be empty, all txns confirmed + assert_equal(set(self.nodes[0].getrawmempool()), set(spends1_id+spends2_id)) + for txid in spends1_id+spends2_id: + tx = self.nodes[0].gettransaction(txid) + assert(tx["confirmations"] == 0) + + # Generate another block, they should all get mined + self.nodes[0].setgenerate(True, 1) + # mempool should be empty, all txns confirmed + assert_equal(set(self.nodes[0].getrawmempool()), set()) + for txid in spends1_id+spends2_id: + tx = self.nodes[0].gettransaction(txid) + assert(tx["confirmations"] > 0) + + +if __name__ == '__main__': + MempoolCoinbaseTest().main() diff --git a/qa/rpc-tests/rest.py b/qa/rpc-tests/rest.py index ca09a5c6d8..2d301bf4f8 100755 --- a/qa/rpc-tests/rest.py +++ b/qa/rpc-tests/rest.py @@ -31,7 +31,7 @@ def http_get_call(host, port, path, response_object = 0): class RESTTest (BitcoinTestFramework): - FORMAT_SEPARATOR = "/" + FORMAT_SEPARATOR = "." def run_test(self): url = urlparse.urlparse(self.nodes[0].url) @@ -59,4 +59,4 @@ class RESTTest (BitcoinTestFramework): assert_greater_than(int(response.getheader('content-length')), 10) if __name__ == '__main__': - RESTTest ().main ()
\ No newline at end of file + RESTTest ().main () diff --git a/src/rest.cpp b/src/rest.cpp index 4953d7e717..27551c9295 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -18,6 +18,7 @@ using namespace std; using namespace json_spirit; enum RetFormat { + RF_UNDEF, RF_BINARY, RF_HEX, RF_JSON, @@ -25,14 +26,16 @@ enum RetFormat { static const struct { enum RetFormat rf; - const char *name; + const char* name; } rf_names[] = { - { RF_BINARY, "binary" }, // default, if match not found - { RF_HEX, "hex" }, - { RF_JSON, "json" }, + {RF_UNDEF, ""}, + {RF_BINARY, "bin"}, + {RF_HEX, "hex"}, + {RF_JSON, "json"}, }; -class RestErr { +class RestErr +{ public: enum HTTPStatusCode status; string message; @@ -49,15 +52,34 @@ static RestErr RESTERR(enum HTTPStatusCode status, string message) return re; } -static enum RetFormat ParseDataFormat(const string& format) +static enum RetFormat ParseDataFormat(vector<string>& params, const string strReq) { - for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++) - if (format == rf_names[i].name) - return rf_names[i].rf; + boost::split(params, strReq, boost::is_any_of(".")); + if (params.size() > 1) { + for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++) + if (params[1] == rf_names[i].name) + return rf_names[i].rf; + } return rf_names[0].rf; } +static string AvailableDataFormatsString() +{ + string formats = ""; + for (unsigned int i = 0; i < ARRAYLEN(rf_names); i++) + if (strlen(rf_names[i].name) > 0) { + formats.append("."); + formats.append(rf_names[i].name); + formats.append(", "); + } + + if (formats.length() > 0) + return formats.substr(0, formats.length() - 2); + + return formats; +} + static bool ParseHashStr(const string& strReq, uint256& v) { if (!IsHex(strReq) || (strReq.size() != 64)) @@ -67,15 +89,13 @@ static bool ParseHashStr(const string& strReq, uint256& v) return true; } -static bool rest_block(AcceptedConnection *conn, +static bool rest_block(AcceptedConnection* conn, string& strReq, map<string, string>& mapHeaders, bool fRun) { vector<string> params; - boost::split(params, strReq, boost::is_any_of("/")); - - enum RetFormat rf = ParseDataFormat(params.size() > 1 ? params[1] : string("")); + enum RetFormat rf = ParseDataFormat(params, strReq); string hashStr = params[0]; uint256 hash; @@ -105,7 +125,7 @@ static bool rest_block(AcceptedConnection *conn, } case RF_HEX: { - string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n";; + string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n"; conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; return true; } @@ -115,22 +135,24 @@ static bool rest_block(AcceptedConnection *conn, string strJSON = write_string(Value(objBlock), false) + "\n"; conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; return true; - } + } + + default: { + throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + } } // not reached - return true; // continue to process further HTTP reqs on this cxn + return true; // continue to process further HTTP reqs on this cxn } -static bool rest_tx(AcceptedConnection *conn, +static bool rest_tx(AcceptedConnection* conn, string& strReq, map<string, string>& mapHeaders, bool fRun) { vector<string> params; - boost::split(params, strReq, boost::is_any_of("/")); - - enum RetFormat rf = ParseDataFormat(params.size() > 1 ? params[1] : string("")); + enum RetFormat rf = ParseDataFormat(params, strReq); string hashStr = params[0]; uint256 hash; @@ -153,7 +175,7 @@ static bool rest_tx(AcceptedConnection *conn, } case RF_HEX: { - string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n";; + string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n"; conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; return true; } @@ -165,33 +187,37 @@ static bool rest_tx(AcceptedConnection *conn, conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; return true; } + + default: { + throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + } } // not reached - return true; // continue to process further HTTP reqs on this cxn + return true; // continue to process further HTTP reqs on this cxn } static const struct { - const char *prefix; - bool (*handler)(AcceptedConnection *conn, + const char* prefix; + bool (*handler)(AcceptedConnection* conn, string& strURI, map<string, string>& mapHeaders, bool fRun); } uri_prefixes[] = { - { "/rest/tx/", rest_tx }, - { "/rest/block/", rest_block }, + {"/rest/tx/", rest_tx}, + {"/rest/block/", rest_block}, }; -bool HTTPReq_REST(AcceptedConnection *conn, +bool HTTPReq_REST(AcceptedConnection* conn, string& strURI, map<string, string>& mapHeaders, bool fRun) { try { std::string statusmessage; - if(RPCIsInWarmup(&statusmessage)) - throw RESTERR(HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: "+statusmessage); - + if (RPCIsInWarmup(&statusmessage)) + throw RESTERR(HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage); + for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) { unsigned int plen = strlen(uri_prefixes[i].prefix); if (strURI.substr(0, plen) == uri_prefixes[i].prefix) { @@ -199,8 +225,7 @@ bool HTTPReq_REST(AcceptedConnection *conn, return uri_prefixes[i].handler(conn, strReq, mapHeaders, fRun); } } - } - catch (RestErr& re) { + } catch (RestErr& re) { conn->stream() << HTTPReply(re.status, re.message + "\r\n", false, false, "text/plain") << std::flush; return false; } diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 8512212185..90695611f2 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -269,6 +269,8 @@ static const CRPCCommand vRPCCommands[] = { "blockchain", "gettxout", &gettxout, true, false, false }, { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true, false, false }, { "blockchain", "verifychain", &verifychain, true, false, false }, + { "blockchain", "invalidateblock", &invalidateblock, true, true, false }, + { "blockchain", "reconsiderblock", &reconsiderblock, true, true, false }, /* Mining */ { "mining", "getblocktemplate", &getblocktemplate, true, false, false }, |