aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xqa/pull-tester/rpc-tests.sh1
-rwxr-xr-xqa/rpc-tests/mempool_resurrect_test.py88
-rwxr-xr-xqa/rpc-tests/rest.py4
-rw-r--r--src/rest.cpp89
-rw-r--r--src/rpcserver.cpp2
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 },