aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am1
-rw-r--r--src/bitcoin-cli.cpp119
-rw-r--r--src/bitcoin-tx.cpp8
-rw-r--r--src/init.cpp1
-rw-r--r--src/netbase.cpp125
-rw-r--r--src/qt/transactionview.cpp27
-rw-r--r--src/qt/transactionview.h4
-rw-r--r--src/rpc/mining.cpp6
-rw-r--r--src/rpc/protocol.cpp21
-rw-r--r--src/rpc/protocol.h3
-rw-r--r--src/rpc/rawtransaction.cpp7
-rw-r--r--src/rpc/server.cpp7
-rw-r--r--src/rpc/server.h2
-rw-r--r--src/wallet/wallet.cpp9
-rw-r--r--src/wallet/walletdb.cpp4
-rwxr-xr-xtest/functional/bitcoin_cli.py23
-rwxr-xr-xtest/functional/deprecated_rpc.py23
-rwxr-xr-xtest/functional/smartfees.py2
-rwxr-xr-xtest/functional/test_runner.py1
-rw-r--r--test/util/data/bitcoin-util-test.json18
-rw-r--r--test/util/data/txcreatemultisig5.json26
21 files changed, 373 insertions, 64 deletions
diff --git a/Makefile.am b/Makefile.am
index 8216b7d608..3b62a10603 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -249,6 +249,7 @@ EXTRA_DIST += \
test/util/data/txcreatemultisig3.json \
test/util/data/txcreatemultisig4.hex \
test/util/data/txcreatemultisig4.json \
+ test/util/data/txcreatemultisig5.json \
test/util/data/txcreateoutpubkey1.hex \
test/util/data/txcreateoutpubkey1.json \
test/util/data/txcreateoutpubkey2.hex \
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 3c94c99b3e..e21a269221 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -37,6 +37,7 @@ std::string HelpMessageCli()
strUsage += HelpMessageOpt("-?", _("This help message"));
strUsage += HelpMessageOpt("-conf=<file>", strprintf(_("Specify configuration file (default: %s)"), BITCOIN_CONF_FILENAME));
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
+ strUsage += HelpMessageOpt("-getinfo", _("Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)"));
AppendParamsHelpMessages(strUsage);
strUsage += HelpMessageOpt("-named", strprintf(_("Pass named instead of positional arguments (default: %s)"), DEFAULT_NAMED));
strUsage += HelpMessageOpt("-rpcconnect=<ip>", strprintf(_("Send commands to node running on <ip> (default: %s)"), DEFAULT_RPCCONNECT));
@@ -191,7 +192,96 @@ static void http_error_cb(enum evhttp_request_error err, void *ctx)
}
#endif
-static UniValue CallRPC(const std::string& strMethod, const UniValue& params)
+/** Class that handles the conversion from a command-line to a JSON-RPC request,
+ * as well as converting back to a JSON object that can be shown as result.
+ */
+class BaseRequestHandler
+{
+public:
+ virtual UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) = 0;
+ virtual UniValue ProcessReply(const UniValue &batch_in) = 0;
+};
+
+/** Process getinfo requests */
+class GetinfoRequestHandler: public BaseRequestHandler
+{
+public:
+ const int ID_NETWORKINFO = 0;
+ const int ID_BLOCKCHAININFO = 1;
+ const int ID_WALLETINFO = 2;
+
+ /** Create a simulated `getinfo` request. */
+ UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
+ {
+ UniValue result(UniValue::VARR);
+ result.push_back(JSONRPCRequestObj("getnetworkinfo", NullUniValue, ID_NETWORKINFO));
+ result.push_back(JSONRPCRequestObj("getblockchaininfo", NullUniValue, ID_BLOCKCHAININFO));
+ result.push_back(JSONRPCRequestObj("getwalletinfo", NullUniValue, ID_WALLETINFO));
+ return result;
+ }
+
+ /** Collect values from the batch and form a simulated `getinfo` reply. */
+ UniValue ProcessReply(const UniValue &batch_in) override
+ {
+ UniValue result(UniValue::VOBJ);
+ std::vector<UniValue> batch = JSONRPCProcessBatchReply(batch_in, 3);
+ // Errors in getnetworkinfo() and getblockchaininfo() are fatal, pass them on
+ // getwalletinfo() is allowed to fail in case there is no wallet.
+ if (!batch[ID_NETWORKINFO]["error"].isNull()) {
+ return batch[ID_NETWORKINFO];
+ }
+ if (!batch[ID_BLOCKCHAININFO]["error"].isNull()) {
+ return batch[ID_BLOCKCHAININFO];
+ }
+ result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]);
+ result.pushKV("protocolversion", batch[ID_NETWORKINFO]["result"]["protocolversion"]);
+ if (!batch[ID_WALLETINFO].isNull()) {
+ result.pushKV("walletversion", batch[ID_WALLETINFO]["result"]["walletversion"]);
+ result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]);
+ }
+ result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]);
+ result.pushKV("timeoffset", batch[ID_NETWORKINFO]["result"]["timeoffset"]);
+ result.pushKV("connections", batch[ID_NETWORKINFO]["result"]["connections"]);
+ result.pushKV("proxy", batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]);
+ result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]);
+ result.pushKV("testnet", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"].get_str() == "test"));
+ if (!batch[ID_WALLETINFO].isNull()) {
+ result.pushKV("walletversion", batch[ID_WALLETINFO]["result"]["walletversion"]);
+ result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]);
+ result.pushKV("keypoololdest", batch[ID_WALLETINFO]["result"]["keypoololdest"]);
+ result.pushKV("keypoolsize", batch[ID_WALLETINFO]["result"]["keypoolsize"]);
+ if (!batch[ID_WALLETINFO]["result"]["unlocked_until"].isNull()) {
+ result.pushKV("unlocked_until", batch[ID_WALLETINFO]["result"]["unlocked_until"]);
+ }
+ result.pushKV("paytxfee", batch[ID_WALLETINFO]["result"]["paytxfee"]);
+ }
+ result.pushKV("relayfee", batch[ID_NETWORKINFO]["result"]["relayfee"]);
+ result.pushKV("warnings", batch[ID_NETWORKINFO]["result"]["warnings"]);
+ return JSONRPCReplyObj(result, NullUniValue, 1);
+ }
+};
+
+/** Process default single requests */
+class DefaultRequestHandler: public BaseRequestHandler {
+public:
+ UniValue PrepareRequest(const std::string& method, const std::vector<std::string>& args) override
+ {
+ UniValue params;
+ if(gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
+ params = RPCConvertNamedValues(method, args);
+ } else {
+ params = RPCConvertValues(method, args);
+ }
+ return JSONRPCRequestObj(method, params, 1);
+ }
+
+ UniValue ProcessReply(const UniValue &reply) override
+ {
+ return reply.get_obj();
+ }
+};
+
+static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, const std::vector<std::string>& args)
{
std::string host;
// In preference order, we choose the following for the port:
@@ -238,7 +328,7 @@ static UniValue CallRPC(const std::string& strMethod, const UniValue& params)
evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());
// Attach request data
- std::string strRequest = JSONRPCRequestObj(strMethod, params, 1).write() + "\n";
+ std::string strRequest = rh->PrepareRequest(strMethod, args).write() + "\n";
struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
assert(output_buffer);
evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
@@ -277,7 +367,7 @@ static UniValue CallRPC(const std::string& strMethod, const UniValue& params)
UniValue valReply(UniValue::VSTR);
if (!valReply.read(response.body))
throw std::runtime_error("couldn't parse reply from server");
- const UniValue& reply = valReply.get_obj();
+ const UniValue reply = rh->ProcessReply(valReply);
if (reply.empty())
throw std::runtime_error("expected reply to have result, error and id properties");
@@ -309,24 +399,25 @@ int CommandLineRPC(int argc, char *argv[])
args.push_back(line);
}
}
- if (args.size() < 1) {
- throw std::runtime_error("too few parameters (need at least command)");
- }
- std::string strMethod = args[0];
- args.erase(args.begin()); // Remove trailing method name from arguments vector
-
- UniValue params;
- if(gArgs.GetBoolArg("-named", DEFAULT_NAMED)) {
- params = RPCConvertNamedValues(strMethod, args);
+ std::unique_ptr<BaseRequestHandler> rh;
+ std::string method;
+ if (gArgs.GetBoolArg("-getinfo", false)) {
+ rh.reset(new GetinfoRequestHandler());
+ method = "";
} else {
- params = RPCConvertValues(strMethod, args);
+ rh.reset(new DefaultRequestHandler());
+ if (args.size() < 1) {
+ throw std::runtime_error("too few parameters (need at least command)");
+ }
+ method = args[0];
+ args.erase(args.begin()); // Remove trailing method name from arguments vector
}
// Execute and handle connection failures with -rpcwait
const bool fWait = gArgs.GetBoolArg("-rpcwait", false);
do {
try {
- const UniValue reply = CallRPC(strMethod, params);
+ const UniValue reply = CallRPC(rh.get(), method, args);
// Parse reply
const UniValue& result = find_value(reply, "result");
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index d8d7934bf6..e4f44435ba 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -310,6 +310,9 @@ static void MutateTxAddOutPubKey(CMutableTransaction& tx, const std::string& str
}
if (bSegWit) {
+ if (!pubkey.IsCompressed()) {
+ throw std::runtime_error("Uncompressed pubkeys are not useable for SegWit outputs");
+ }
// Call GetScriptForWitness() to build a P2WSH scriptPubKey
scriptPubKey = GetScriptForWitness(scriptPubKey);
}
@@ -375,6 +378,11 @@ static void MutateTxAddOutMultiSig(CMutableTransaction& tx, const std::string& s
CScript scriptPubKey = GetScriptForMultisig(required, pubkeys);
if (bSegWit) {
+ for (CPubKey& pubkey : pubkeys) {
+ if (!pubkey.IsCompressed()) {
+ throw std::runtime_error("Uncompressed pubkeys are not useable for SegWit outputs");
+ }
+ }
// Call GetScriptForWitness() to build a P2WSH scriptPubKey
scriptPubKey = GetScriptForWitness(scriptPubKey);
}
diff --git a/src/init.cpp b/src/init.cpp
index e80170b24d..55670c7dc6 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -430,6 +430,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u)", defaultChainParams->DefaultConsistencyChecks()));
strUsage += HelpMessageOpt("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED));
strUsage += HelpMessageOpt("-disablesafemode", strprintf("Disable safemode, override a real safe mode event (default: %u)", DEFAULT_DISABLE_SAFEMODE));
+ strUsage += HelpMessageOpt("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used");
strUsage += HelpMessageOpt("-testsafemode", strprintf("Force safe mode (default: %u)", DEFAULT_TESTSAFEMODE));
strUsage += HelpMessageOpt("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages");
strUsage += HelpMessageOpt("-fuzzmessagestest=<n>", "Randomly fuzz 1 of every <n> network messages");
diff --git a/src/netbase.cpp b/src/netbase.cpp
index 05f9f6961c..914124cb17 100644
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -184,6 +184,48 @@ struct timeval MillisToTimeval(int64_t nTimeout)
return timeout;
}
+/** SOCKS version */
+enum SOCKSVersion: uint8_t {
+ SOCKS4 = 0x04,
+ SOCKS5 = 0x05
+};
+
+/** Values defined for METHOD in RFC1928 */
+enum SOCKS5Method: uint8_t {
+ NOAUTH = 0x00, //! No authentication required
+ GSSAPI = 0x01, //! GSSAPI
+ USER_PASS = 0x02, //! Username/password
+ NO_ACCEPTABLE = 0xff, //! No acceptable methods
+};
+
+/** Values defined for CMD in RFC1928 */
+enum SOCKS5Command: uint8_t {
+ CONNECT = 0x01,
+ BIND = 0x02,
+ UDP_ASSOCIATE = 0x03
+};
+
+/** Values defined for REP in RFC1928 */
+enum SOCKS5Reply: uint8_t {
+ SUCCEEDED = 0x00, //! Succeeded
+ GENFAILURE = 0x01, //! General failure
+ NOTALLOWED = 0x02, //! Connection not allowed by ruleset
+ NETUNREACHABLE = 0x03, //! Network unreachable
+ HOSTUNREACHABLE = 0x04, //! Network unreachable
+ CONNREFUSED = 0x05, //! Connection refused
+ TTLEXPIRED = 0x06, //! TTL expired
+ CMDUNSUPPORTED = 0x07, //! Command not supported
+ ATYPEUNSUPPORTED = 0x08, //! Address type not supported
+};
+
+/** Values defined for ATYPE in RFC1928 */
+enum SOCKS5Atyp: uint8_t {
+ IPV4 = 0x01,
+ DOMAINNAME = 0x03,
+ IPV6 = 0x04,
+};
+
+/** Status codes that can be returned by InterruptibleRecv */
enum class IntrRecvError {
OK,
Timeout,
@@ -203,7 +245,7 @@ enum class IntrRecvError {
*
* @note This function requires that hSocket is in non-blocking mode.
*/
-static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, const SOCKET& hSocket)
+static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const SOCKET& hSocket)
{
int64_t curTime = GetTimeMillis();
int64_t endTime = curTime + timeout;
@@ -211,7 +253,7 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, cons
// to break off in case of an interruption.
const int64_t maxWait = 1000;
while (len > 0 && curTime < endTime) {
- ssize_t ret = recv(hSocket, data, len, 0); // Optimistically try the recv first
+ ssize_t ret = recv(hSocket, (char*)data, len, 0); // Optimistically try the recv first
if (ret > 0) {
len -= ret;
data += ret;
@@ -242,24 +284,35 @@ static IntrRecvError InterruptibleRecv(char* data, size_t len, int timeout, cons
return len == 0 ? IntrRecvError::OK : IntrRecvError::Timeout;
}
+/** Credentials for proxy authentication */
struct ProxyCredentials
{
std::string username;
std::string password;
};
-std::string Socks5ErrorString(int err)
+/** Convert SOCKS5 reply to a an error message */
+std::string Socks5ErrorString(uint8_t err)
{
switch(err) {
- case 0x01: return "general failure";
- case 0x02: return "connection not allowed";
- case 0x03: return "network unreachable";
- case 0x04: return "host unreachable";
- case 0x05: return "connection refused";
- case 0x06: return "TTL expired";
- case 0x07: return "protocol error";
- case 0x08: return "address type not supported";
- default: return "unknown";
+ case SOCKS5Reply::GENFAILURE:
+ return "general failure";
+ case SOCKS5Reply::NOTALLOWED:
+ return "connection not allowed";
+ case SOCKS5Reply::NETUNREACHABLE:
+ return "network unreachable";
+ case SOCKS5Reply::HOSTUNREACHABLE:
+ return "host unreachable";
+ case SOCKS5Reply::CONNREFUSED:
+ return "connection refused";
+ case SOCKS5Reply::TTLEXPIRED:
+ return "TTL expired";
+ case SOCKS5Reply::CMDUNSUPPORTED:
+ return "protocol error";
+ case SOCKS5Reply::ATYPEUNSUPPORTED:
+ return "address type not supported";
+ default:
+ return "unknown";
}
}
@@ -274,34 +327,34 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
}
// Accepted authentication methods
std::vector<uint8_t> vSocks5Init;
- vSocks5Init.push_back(0x05);
+ vSocks5Init.push_back(SOCKSVersion::SOCKS5);
if (auth) {
- vSocks5Init.push_back(0x02); // # METHODS
- vSocks5Init.push_back(0x00); // X'00' NO AUTHENTICATION REQUIRED
- vSocks5Init.push_back(0x02); // X'02' USERNAME/PASSWORD (RFC1929)
+ vSocks5Init.push_back(0x02); // Number of methods
+ vSocks5Init.push_back(SOCKS5Method::NOAUTH);
+ vSocks5Init.push_back(SOCKS5Method::USER_PASS);
} else {
- vSocks5Init.push_back(0x01); // # METHODS
- vSocks5Init.push_back(0x00); // X'00' NO AUTHENTICATION REQUIRED
+ vSocks5Init.push_back(0x01); // Number of methods
+ vSocks5Init.push_back(SOCKS5Method::NOAUTH);
}
ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL);
if (ret != (ssize_t)vSocks5Init.size()) {
CloseSocket(hSocket);
return error("Error sending to proxy");
}
- char pchRet1[2];
+ uint8_t pchRet1[2];
if ((recvr = InterruptibleRecv(pchRet1, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket);
LogPrintf("Socks5() connect to %s:%d failed: InterruptibleRecv() timeout or other failure\n", strDest, port);
return false;
}
- if (pchRet1[0] != 0x05) {
+ if (pchRet1[0] != SOCKSVersion::SOCKS5) {
CloseSocket(hSocket);
return error("Proxy failed to initialize");
}
- if (pchRet1[1] == 0x02 && auth) {
+ if (pchRet1[1] == SOCKS5Method::USER_PASS && auth) {
// Perform username/password authentication (as described in RFC1929)
std::vector<uint8_t> vAuth;
- vAuth.push_back(0x01);
+ vAuth.push_back(0x01); // Current (and only) version of user/pass subnegotiation
if (auth->username.size() > 255 || auth->password.size() > 255)
return error("Proxy username or password too long");
vAuth.push_back(auth->username.size());
@@ -314,7 +367,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
return error("Error sending authentication to proxy");
}
LogPrint(BCLog::PROXY, "SOCKS5 sending proxy authentication %s:%s\n", auth->username, auth->password);
- char pchRetA[2];
+ uint8_t pchRetA[2];
if ((recvr = InterruptibleRecv(pchRetA, 2, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket);
return error("Error reading proxy authentication response");
@@ -323,17 +376,17 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
CloseSocket(hSocket);
return error("Proxy authentication unsuccessful");
}
- } else if (pchRet1[1] == 0x00) {
+ } else if (pchRet1[1] == SOCKS5Method::NOAUTH) {
// Perform no authentication
} else {
CloseSocket(hSocket);
return error("Proxy requested wrong authentication method %02x", pchRet1[1]);
}
std::vector<uint8_t> vSocks5;
- vSocks5.push_back(0x05); // VER protocol version
- vSocks5.push_back(0x01); // CMD CONNECT
- vSocks5.push_back(0x00); // RSV Reserved
- vSocks5.push_back(0x03); // ATYP DOMAINNAME
+ vSocks5.push_back(SOCKSVersion::SOCKS5); // VER protocol version
+ vSocks5.push_back(SOCKS5Command::CONNECT); // CMD CONNECT
+ vSocks5.push_back(0x00); // RSV Reserved must be 0
+ vSocks5.push_back(SOCKS5Atyp::DOMAINNAME); // ATYP DOMAINNAME
vSocks5.push_back(strDest.size()); // Length<=255 is checked at beginning of function
vSocks5.insert(vSocks5.end(), strDest.begin(), strDest.end());
vSocks5.push_back((port >> 8) & 0xFF);
@@ -343,7 +396,7 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
CloseSocket(hSocket);
return error("Error sending to proxy");
}
- char pchRet2[4];
+ uint8_t pchRet2[4];
if ((recvr = InterruptibleRecv(pchRet2, 4, SOCKS5_RECV_TIMEOUT, hSocket)) != IntrRecvError::OK) {
CloseSocket(hSocket);
if (recvr == IntrRecvError::Timeout) {
@@ -355,26 +408,26 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
return error("Error while reading proxy response");
}
}
- if (pchRet2[0] != 0x05) {
+ if (pchRet2[0] != SOCKSVersion::SOCKS5) {
CloseSocket(hSocket);
return error("Proxy failed to accept request");
}
- if (pchRet2[1] != 0x00) {
+ if (pchRet2[1] != SOCKS5Reply::SUCCEEDED) {
// Failures to connect to a peer that are not proxy errors
CloseSocket(hSocket);
LogPrintf("Socks5() connect to %s:%d failed: %s\n", strDest, port, Socks5ErrorString(pchRet2[1]));
return false;
}
- if (pchRet2[2] != 0x00) {
+ if (pchRet2[2] != 0x00) { // Reserved field must be 0
CloseSocket(hSocket);
return error("Error: malformed proxy response");
}
- char pchRet3[256];
+ uint8_t pchRet3[256];
switch (pchRet2[3])
{
- case 0x01: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); break;
- case 0x04: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); break;
- case 0x03:
+ case SOCKS5Atyp::IPV4: recvr = InterruptibleRecv(pchRet3, 4, SOCKS5_RECV_TIMEOUT, hSocket); break;
+ case SOCKS5Atyp::IPV6: recvr = InterruptibleRecv(pchRet3, 16, SOCKS5_RECV_TIMEOUT, hSocket); break;
+ case SOCKS5Atyp::DOMAINNAME:
{
recvr = InterruptibleRecv(pchRet3, 1, SOCKS5_RECV_TIMEOUT, hSocket);
if (recvr != IntrRecvError::OK) {
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 53c38da9db..39dfdb587c 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -33,6 +33,7 @@
#include <QScrollBar>
#include <QSignalMapper>
#include <QTableView>
+#include <QTimer>
#include <QUrl>
#include <QVBoxLayout>
@@ -112,6 +113,17 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
hlayout->addWidget(amountWidget);
+ // Delay before filtering transactions in ms
+ static const int input_filter_delay = 200;
+
+ QTimer* amount_typing_delay = new QTimer(this);
+ amount_typing_delay->setSingleShot(true);
+ amount_typing_delay->setInterval(input_filter_delay);
+
+ QTimer* prefix_typing_delay = new QTimer(this);
+ prefix_typing_delay->setSingleShot(true);
+ prefix_typing_delay->setInterval(input_filter_delay);
+
QVBoxLayout *vlayout = new QVBoxLayout(this);
vlayout->setContentsMargins(0,0,0,0);
vlayout->setSpacing(0);
@@ -173,8 +185,10 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int)));
connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int)));
connect(watchOnlyWidget, SIGNAL(activated(int)), this, SLOT(chooseWatchonly(int)));
- connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString)));
- connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString)));
+ connect(amountWidget, SIGNAL(textChanged(QString)), amount_typing_delay, SLOT(start()));
+ connect(amount_typing_delay, SIGNAL(timeout()), this, SLOT(changedAmount()));
+ connect(addressWidget, SIGNAL(textChanged(QString)), prefix_typing_delay, SLOT(start()));
+ connect(prefix_typing_delay, SIGNAL(timeout()), this, SLOT(changedPrefix()));
connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
@@ -312,20 +326,19 @@ void TransactionView::chooseWatchonly(int idx)
(TransactionFilterProxy::WatchOnlyFilter)watchOnlyWidget->itemData(idx).toInt());
}
-void TransactionView::changedPrefix(const QString &prefix)
+void TransactionView::changedPrefix()
{
if(!transactionProxyModel)
return;
- transactionProxyModel->setAddressPrefix(prefix);
+ transactionProxyModel->setAddressPrefix(addressWidget->text());
}
-void TransactionView::changedAmount(const QString &amount)
+void TransactionView::changedAmount()
{
if(!transactionProxyModel)
return;
CAmount amount_parsed = 0;
- if(BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed))
- {
+ if (BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amountWidget->text(), &amount_parsed)) {
transactionProxyModel->setMinAmount(amount_parsed);
}
else
diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h
index 52e57cae4c..5b4cfd4a88 100644
--- a/src/qt/transactionview.h
+++ b/src/qt/transactionview.h
@@ -112,8 +112,8 @@ public Q_SLOTS:
void chooseDate(int idx);
void chooseType(int idx);
void chooseWatchonly(int idx);
- void changedPrefix(const QString &prefix);
- void changedAmount(const QString &amount);
+ void changedAmount();
+ void changedPrefix();
void exportClicked();
void focusTransaction(const QModelIndex&);
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index f0ffa07e12..5ac32dc974 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -789,6 +789,12 @@ UniValue estimatefee(const JSONRPCRequest& request)
+ HelpExampleCli("estimatefee", "6")
);
+ if (!IsDeprecatedRPCEnabled("estimatefee")) {
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "estimatefee is deprecated and will be fully removed in v0.17. "
+ "To use estimatefee in v0.16, restart bitcoind with -deprecatedrpc=estimatefee.\n"
+ "Projects should transition to using estimatesmartfee before upgrading to v0.17");
+ }
+
RPCTypeCheck(request.params, {UniValue::VNUM});
int nBlocks = request.params[0].get_int();
diff --git a/src/rpc/protocol.cpp b/src/rpc/protocol.cpp
index dc6bcec382..1f4ae75b18 100644
--- a/src/rpc/protocol.cpp
+++ b/src/rpc/protocol.cpp
@@ -19,7 +19,7 @@
* JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility,
* but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were
* unspecified (HTTP errors and contents of 'error').
- *
+ *
* 1.0 spec: http://json-rpc.org/wiki/specification
* 1.2 spec: http://jsonrpc.org/historical/json-rpc-over-http.html
*/
@@ -135,3 +135,22 @@ void DeleteAuthCookie()
}
}
+std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num)
+{
+ if (!in.isArray()) {
+ throw std::runtime_error("Batch must be an array");
+ }
+ std::vector<UniValue> batch(num);
+ for (size_t i=0; i<in.size(); ++i) {
+ const UniValue &rec = in[i];
+ if (!rec.isObject()) {
+ throw std::runtime_error("Batch member must be object");
+ }
+ size_t id = rec["id"].get_int();
+ if (id >= num) {
+ throw std::runtime_error("Batch member id larger than size");
+ }
+ batch[id] = rec;
+ }
+ return batch;
+}
diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h
index 5c9c64f67d..cb668f3db9 100644
--- a/src/rpc/protocol.h
+++ b/src/rpc/protocol.h
@@ -57,6 +57,7 @@ enum RPCErrorCode
RPC_VERIFY_REJECTED = -26, //!< Transaction or block was rejected by network rules
RPC_VERIFY_ALREADY_IN_CHAIN = -27, //!< Transaction already in chain
RPC_IN_WARMUP = -28, //!< Client still warming up
+ RPC_METHOD_DEPRECATED = -32, //!< RPC method is deprecated
//! Aliases for backward compatibility
RPC_TRANSACTION_ERROR = RPC_VERIFY_ERROR,
@@ -97,5 +98,7 @@ bool GenerateAuthCookie(std::string *cookie_out);
bool GetAuthCookie(std::string *cookie_out);
/** Delete RPC authentication cookie from disk */
void DeleteAuthCookie();
+/** Parse JSON-RPC batch reply into a vector */
+std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue &in, size_t num);
#endif // BITCOIN_RPCPROTOCOL_H
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index a0322f67b4..b2fc6a357a 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -873,7 +873,12 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
ScriptError serror = SCRIPT_ERR_OK;
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
- TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
+ if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
+ // Unable to sign input and verification failed (possible attempt to partially sign).
+ TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)");
+ } else {
+ TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
+ }
}
}
bool fComplete = vErrors.empty();
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 428ab3b9b0..a73b697e01 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -382,6 +382,13 @@ void JSONRPCRequest::parse(const UniValue& valRequest)
throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object");
}
+bool IsDeprecatedRPCEnabled(const std::string& method)
+{
+ const std::vector<std::string> enabled_methods = gArgs.GetArgs("-deprecatedrpc");
+
+ return find(enabled_methods.begin(), enabled_methods.end(), method) != enabled_methods.end();
+}
+
static UniValue JSONRPCExecOne(const UniValue& req)
{
UniValue rpc_result(UniValue::VOBJ);
diff --git a/src/rpc/server.h b/src/rpc/server.h
index 777acbcb94..31d6304271 100644
--- a/src/rpc/server.h
+++ b/src/rpc/server.h
@@ -171,6 +171,8 @@ public:
bool appendCommand(const std::string& name, const CRPCCommand* pcmd);
};
+bool IsDeprecatedRPCEnabled(const std::string& method);
+
extern CRPCTable tableRPC;
/**
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index fd2c1dfbe7..d83203f409 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -915,6 +915,15 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
+ // If we have a witness-stripped version of this transaction, and we
+ // see a new version with a witness, then we must be upgrading a pre-segwit
+ // wallet. Store the new version of the transaction with the witness,
+ // as the stripped-version must be invalid.
+ // TODO: Store all versions of the transaction, instead of just one.
+ if (wtxIn.tx->HasWitness() && !wtx.tx->HasWitness()) {
+ wtx.SetTx(wtxIn.tx);
+ fUpdated = true;
+ }
}
//// debug print
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 52370a8eb5..b7f873c1e4 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -41,9 +41,9 @@ bool CWalletDB::WritePurpose(const std::string& strAddress, const std::string& s
return WriteIC(std::make_pair(std::string("purpose"), strAddress), strPurpose);
}
-bool CWalletDB::ErasePurpose(const std::string& strPurpose)
+bool CWalletDB::ErasePurpose(const std::string& strAddress)
{
- return EraseIC(std::make_pair(std::string("purpose"), strPurpose));
+ return EraseIC(std::make_pair(std::string("purpose"), strAddress));
}
bool CWalletDB::WriteTx(const CWalletTx& wtx)
diff --git a/test/functional/bitcoin_cli.py b/test/functional/bitcoin_cli.py
index ffed5b0d33..996cbb8a12 100755
--- a/test/functional/bitcoin_cli.py
+++ b/test/functional/bitcoin_cli.py
@@ -35,5 +35,28 @@ class TestBitcoinCli(BitcoinTestFramework):
assert_equal(["foo", "bar"], self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input=password + "\nfoo\nbar").echo())
assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input="foo").echo)
+ self.log.info("Compare responses from `bitcoin-cli -getinfo` and the RPCs data is retrieved from.")
+ cli_get_info = self.nodes[0].cli('-getinfo').help()
+ wallet_info = self.nodes[0].getwalletinfo()
+ network_info = self.nodes[0].getnetworkinfo()
+ blockchain_info = self.nodes[0].getblockchaininfo()
+
+ assert_equal(cli_get_info['version'], network_info['version'])
+ assert_equal(cli_get_info['protocolversion'], network_info['protocolversion'])
+ assert_equal(cli_get_info['walletversion'], wallet_info['walletversion'])
+ assert_equal(cli_get_info['balance'], wallet_info['balance'])
+ assert_equal(cli_get_info['blocks'], blockchain_info['blocks'])
+ assert_equal(cli_get_info['timeoffset'], network_info['timeoffset'])
+ assert_equal(cli_get_info['connections'], network_info['connections'])
+ assert_equal(cli_get_info['proxy'], network_info['networks'][0]['proxy'])
+ assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty'])
+ assert_equal(cli_get_info['testnet'], blockchain_info['chain'] == "test")
+ assert_equal(cli_get_info['balance'], wallet_info['balance'])
+ assert_equal(cli_get_info['keypoololdest'], wallet_info['keypoololdest'])
+ assert_equal(cli_get_info['keypoolsize'], wallet_info['keypoolsize'])
+ assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee'])
+ assert_equal(cli_get_info['relayfee'], network_info['relayfee'])
+ # unlocked_until is not tested because the wallet is not encrypted
+
if __name__ == '__main__':
TestBitcoinCli().main()
diff --git a/test/functional/deprecated_rpc.py b/test/functional/deprecated_rpc.py
new file mode 100755
index 0000000000..ec500ccbf9
--- /dev/null
+++ b/test/functional/deprecated_rpc.py
@@ -0,0 +1,23 @@
+#!/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.
+"""Test deprecation of RPC calls."""
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_raises_jsonrpc
+
+class DeprecatedRpcTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+ self.setup_clean_chain = True
+ self.extra_args = [[], ["-deprecatedrpc=estimatefee"]]
+
+ def run_test(self):
+ self.log.info("estimatefee: Shows deprecated message")
+ assert_raises_jsonrpc(-32, 'estimatefee is deprecated', self.nodes[0].estimatefee, 1)
+
+ self.log.info("Using -deprecatedrpc=estimatefee bypasses the error")
+ self.nodes[1].estimatefee(1)
+
+if __name__ == '__main__':
+ DeprecatedRpcTest().main()
diff --git a/test/functional/smartfees.py b/test/functional/smartfees.py
index 76632fc578..986f4546a8 100755
--- a/test/functional/smartfees.py
+++ b/test/functional/smartfees.py
@@ -151,7 +151,7 @@ class EstimateFeeTest(BitcoinTestFramework):
which we will use to generate our transactions.
"""
self.add_nodes(3, extra_args=[["-maxorphantx=1000", "-whitelist=127.0.0.1"],
- ["-blockmaxsize=17000", "-maxorphantx=1000"],
+ ["-blockmaxsize=17000", "-maxorphantx=1000", "-deprecatedrpc=estimatefee"],
["-blockmaxsize=8000", "-maxorphantx=1000"]])
# Use node0 to mine blocks for input splitting
# Node1 mines small blocks but that are bigger than the expected transaction rate.
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 8dbe6247ee..5c8740d7cd 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -98,6 +98,7 @@ BASE_SCRIPTS= [
'disconnect_ban.py',
'decodescript.py',
'blockchain.py',
+ 'deprecated_rpc.py',
'disablewallet.py',
'net.py',
'keypool.py',
diff --git a/test/util/data/bitcoin-util-test.json b/test/util/data/bitcoin-util-test.json
index b61a4f7f8f..89b28bba6c 100644
--- a/test/util/data/bitcoin-util-test.json
+++ b/test/util/data/bitcoin-util-test.json
@@ -263,6 +263,13 @@
},
{ "exec": "./bitcoin-tx",
"args":
+ ["-json", "-create", "outpubkey=0:047d1368ba7ae01c94bc32293efd70bd7e3be7aa7912d07d0b1c659c1008d179b8642f5fb90f47580feb29f045e216ff5a4716d3a0fed36da414d332046303c44a:WS", "nversion=1"],
+ "return_code": 1,
+ "error_txt": "error: Uncompressed pubkeys are not useable for SegWit outputs",
+ "description": "Creates a new transaction with a single pay-to-pub-key output, wrapped in P2SH (output as json)"
+ },
+ { "exec": "./bitcoin-tx",
+ "args":
["-create",
"in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
"outdata=4:badhexdata"],
@@ -388,5 +395,16 @@
"args": ["-json", "-create", "outmultisig=1:2:3:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397:021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d:02df2089105c77f266fa11a9d33f05c735234075f2e8780824c6b709415f9fb485:WS", "nversion=1"],
"output_cmp": "txcreatemultisig4.json",
"description": "Creates a new transaction with a single 2-of-3 multisig in a P2WSH output, wrapped in P2SH (output in json)"
+ },
+ { "exec": "./bitcoin-tx",
+ "args": ["-json", "-create", "outmultisig=1:2:3:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397:021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d:047d1368ba7ae01c94bc32293efd70bd7e3be7aa7912d07d0b1c659c1008d179b8642f5fb90f47580feb29f045e216ff5a4716d3a0fed36da414d332046303c44a:S"],
+ "output_cmp": "txcreatemultisig5.json",
+ "description": "Uncompressed pubkeys should work just fine for non-witness outputs"
+ },
+ { "exec": "./bitcoin-tx",
+ "args": ["-json", "-create", "outmultisig=1:2:3:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397:021ac43c7ff740014c3b33737ede99c967e4764553d1b2b83db77c83b8715fa72d:047d1368ba7ae01c94bc32293efd70bd7e3be7aa7912d07d0b1c659c1008d179b8642f5fb90f47580feb29f045e216ff5a4716d3a0fed36da414d332046303c44a:WS"],
+ "return_code": 1,
+ "error_txt": "error: Uncompressed pubkeys are not useable for SegWit outputs",
+ "description": "Ensure adding witness outputs with uncompressed pubkeys fails"
}
]
diff --git a/test/util/data/txcreatemultisig5.json b/test/util/data/txcreatemultisig5.json
new file mode 100644
index 0000000000..20e9bb077b
--- /dev/null
+++ b/test/util/data/txcreatemultisig5.json
@@ -0,0 +1,26 @@
+{
+ "txid": "813cf75e1f08debd242ef7c8192b7d478fb651355209369499a0de779ba7eb2f",
+ "hash": "813cf75e1f08debd242ef7c8192b7d478fb651355209369499a0de779ba7eb2f",
+ "version": 2,
+ "size": 42,
+ "vsize": 42,
+ "locktime": 0,
+ "vin": [
+ ],
+ "vout": [
+ {
+ "value": 1.00000000,
+ "n": 0,
+ "scriptPubKey": {
+ "asm": "OP_HASH160 a4051c02398868af83f28f083208fae99a769263 OP_EQUAL",
+ "hex": "a914a4051c02398868af83f28f083208fae99a76926387",
+ "reqSigs": 1,
+ "type": "scripthash",
+ "addresses": [
+ "3GeGs1eHUxPz5YyuFe9WPpXid2UsUb5Jos"
+ ]
+ }
+ }
+ ],
+ "hex": "02000000000100e1f5050000000017a914a4051c02398868af83f28f083208fae99a7692638700000000"
+}