From e940185294ef011c0025e4e628d7191a4993666c Mon Sep 17 00:00:00 2001 From: Daniel Kraft Date: Wed, 10 Jun 2015 14:57:57 +0200 Subject: [PATCH 01/43] Fix univalue handling of \u0000 characters. Univalue's parsing of \u escape sequences did not handle NUL characters correctly. They were, effectively, dropped. The extended test-case fails with the old code, and is fixed with this patch. --- src/test/univalue_tests.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/univalue_tests.cpp b/src/test/univalue_tests.cpp index feb8afa6f..485ecaed7 100644 --- a/src/test/univalue_tests.cpp +++ b/src/test/univalue_tests.cpp @@ -286,7 +286,7 @@ BOOST_AUTO_TEST_CASE(univalue_object) } static const char *json1 = -"[1.10000000,{\"key1\":\"str\",\"key2\":800,\"key3\":{\"name\":\"martian\"}}]"; +"[1.10000000,{\"key1\":\"str\\u0000\",\"key2\":800,\"key3\":{\"name\":\"martian\"}}]"; BOOST_AUTO_TEST_CASE(univalue_readwrite) { @@ -306,7 +306,9 @@ BOOST_AUTO_TEST_CASE(univalue_readwrite) BOOST_CHECK_EQUAL(obj.size(), 3); BOOST_CHECK(obj["key1"].isStr()); - BOOST_CHECK_EQUAL(obj["key1"].getValStr(), "str"); + std::string correctValue("str"); + correctValue.push_back('\0'); + BOOST_CHECK_EQUAL(obj["key1"].getValStr(), correctValue); BOOST_CHECK(obj["key2"].isNum()); BOOST_CHECK_EQUAL(obj["key2"].getValStr(), "800"); BOOST_CHECK(obj["key3"].isObject()); From 6699b42518103da77f6005b4e2f7f3aedef17ad6 Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 11 Jun 2015 16:12:34 -0400 Subject: [PATCH 02/43] Add paytxfee to getwalletinfo, warnings to getnetworkinfo --- qa/rpc-tests/README.md | 2 +- src/rpcnet.cpp | 2 ++ src/wallet/rpcwallet.cpp | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/README.md b/qa/rpc-tests/README.md index 08c67c4ef..cfda8fe91 100644 --- a/qa/rpc-tests/README.md +++ b/qa/rpc-tests/README.md @@ -21,7 +21,7 @@ Run all possible tests with `qa/pull-tester/rpc-tests.sh -extended`. Possible options: -```` +``` -h, --help show this help message and exit --nocleanup Leave bitcoinds and test.* datadir on exit or error --noshutdown Don't stop bitcoinds after the test execution diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 522edc741..4bf3d2be7 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -409,6 +409,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp) " }\n" " ,...\n" " ]\n" + " \"warnings\": \"...\" (string) any network warnings (such as alert messages) \n" "}\n" "\nExamples:\n" + HelpExampleCli("getnetworkinfo", "") @@ -440,5 +441,6 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp) } } obj.push_back(Pair("localaddresses", localAddresses)); + obj.push_back(Pair("warnings", GetWarnings("statusbar"))); return obj; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 8f105db94..830192c10 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2260,6 +2260,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" + " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in btc/kb\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") @@ -2278,6 +2279,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize())); if (pwalletMain->IsCrypted()) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); + obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); return obj; } From 94ee48c42a91e260aecb3662a79f509428ad58a9 Mon Sep 17 00:00:00 2001 From: Alex van der Peet Date: Thu, 11 Jun 2015 20:20:54 -0700 Subject: [PATCH 03/43] New RPC command disconnectnode --- src/rpcnet.cpp | 22 ++++++++++++++++++++++ src/rpcprotocol.h | 1 + src/rpcserver.cpp | 1 + src/rpcserver.h | 1 + 4 files changed, 25 insertions(+) diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 4bf3d2be7..30b4bbcbb 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -214,6 +214,28 @@ UniValue addnode(const UniValue& params, bool fHelp) return NullUniValue; } +UniValue disconnectnode(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "disconnectnode \"node\" \n" + "\nImmediately disconnects from the specified node.\n" + "\nArguments:\n" + "1. \"node\" (string, required) The node (see getpeerinfo for nodes)\n" + "\nExamples:\n" + + HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") + + HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") + ); + + CNode* pNode = FindNode(params[0].get_str()); + if (pNode == NULL) + throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED, "Node not found in connected nodes"); + + pNode->CloseSocketDisconnect(); + + return NullUniValue; +} + UniValue getaddednodeinfo(const UniValue& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h index 852a3aef1..37b03a0f8 100644 --- a/src/rpcprotocol.h +++ b/src/rpcprotocol.h @@ -64,6 +64,7 @@ enum RPCErrorCode RPC_CLIENT_IN_INITIAL_DOWNLOAD = -10, //! Still downloading initial blocks RPC_CLIENT_NODE_ALREADY_ADDED = -23, //! Node is already added RPC_CLIENT_NODE_NOT_ADDED = -24, //! Node has not been added before + RPC_CLIENT_NODE_NOT_CONNECTED = -29, //! Node to disconnect not found in connected nodes //! Wallet errors RPC_WALLET_ERROR = -4, //! Unspecified problem with wallet (key not found etc.) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 7414ec128..cb3776465 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -280,6 +280,7 @@ static const CRPCCommand vRPCCommands[] = /* P2P networking */ { "network", "getnetworkinfo", &getnetworkinfo, true }, { "network", "addnode", &addnode, true }, + { "network", "disconnectnode", &disconnectnode, true }, { "network", "getaddednodeinfo", &getaddednodeinfo, true }, { "network", "getconnectioncount", &getconnectioncount, true }, { "network", "getnettotals", &getnettotals, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 6ee17da68..cebea25b4 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -158,6 +158,7 @@ extern UniValue getconnectioncount(const UniValue& params, bool fHelp); // in rp extern UniValue getpeerinfo(const UniValue& params, bool fHelp); extern UniValue ping(const UniValue& params, bool fHelp); extern UniValue addnode(const UniValue& params, bool fHelp); +extern UniValue disconnectnode(const UniValue& params, bool fHelp); extern UniValue getaddednodeinfo(const UniValue& params, bool fHelp); extern UniValue getnettotals(const UniValue& params, bool fHelp); From 997829713ccc7e21d3a2988b6e79ddec3dd3cf99 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Tue, 19 May 2015 10:07:23 +0200 Subject: [PATCH 04/43] [net] extend core functionallity for ban/unban/listban --- src/net.cpp | 27 +++++++++++++++++++++------ src/net.h | 5 ++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 0a96d01f2..77227c51b 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -464,16 +464,31 @@ bool CNode::IsBanned(CNetAddr ip) return fResult; } -bool CNode::Ban(const CNetAddr &addr) { +bool CNode::Ban(const CNetAddr &addr, int64_t bantimeoffset) { int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban - { - LOCK(cs_setBanned); - if (setBanned[addr] < banTime) - setBanned[addr] = banTime; - } + if (bantimeoffset > 0) + banTime = GetTime()+bantimeoffset; + + LOCK(cs_setBanned); + if (setBanned[addr] < banTime) + setBanned[addr] = banTime; + return true; } +bool CNode::Unban(const CNetAddr &addr) { + LOCK(cs_setBanned); + if (setBanned.erase(addr)) + return true; + return false; +} + +void CNode::GetBanned(std::map &banMap) +{ + LOCK(cs_setBanned); + banMap = setBanned; //create a thread safe copy +} + std::vector CNode::vWhitelistedRange; CCriticalSection CNode::cs_vWhitelistedRange; diff --git a/src/net.h b/src/net.h index 50fb90dca..db539f2ed 100644 --- a/src/net.h +++ b/src/net.h @@ -613,7 +613,10 @@ public: // new code. static void ClearBanned(); // needed for unit testing static bool IsBanned(CNetAddr ip); - static bool Ban(const CNetAddr &ip); + static bool Ban(const CNetAddr &ip, int64_t bantimeoffset = 0); + static bool Unban(const CNetAddr &ip); + static void GetBanned(std::map &banmap); + void copyStats(CNodeStats &stats); static bool IsWhitelistedRange(const CNetAddr &ip); From ed3f13a05724e9894c2db4af07e41fbfe1bc79c8 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Tue, 19 May 2015 10:07:46 +0200 Subject: [PATCH 05/43] [RPC] add setban/listbanned/clearbanned RPC commands --- src/rpcclient.cpp | 1 + src/rpcnet.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++++++ src/rpcserver.cpp | 3 ++ src/rpcserver.h | 3 ++ 4 files changed, 96 insertions(+) diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index fe442b81c..c40fbeedd 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -92,6 +92,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "estimatepriority", 0 }, { "prioritisetransaction", 1 }, { "prioritisetransaction", 2 }, + { "setban", 2 }, { "zcrawjoinsplit", 1 }, { "zcrawjoinsplit", 2 }, { "zcrawjoinsplit", 3 }, diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 30b4bbcbb..d702d59a2 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -466,3 +466,92 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("warnings", GetWarnings("statusbar"))); return obj; } + +Value setban(const Array& params, bool fHelp) +{ + string strCommand; + if (params.size() >= 2) + strCommand = params[1].get_str(); + if (fHelp || params.size() < 2 || + (strCommand != "add" && strCommand != "remove")) + throw runtime_error( + "setban \"node\" \"add|remove\" (bantime)\n" + "\nAttempts add or remove a IP from the banned list.\n" + "\nArguments:\n" + "1. \"ip\" (string, required) The IP (see getpeerinfo for nodes ip)\n" + "2. \"command\" (string, required) 'add' to add a IP to the list, 'remove' to remove a IP from the list\n" + "1. \"bantime\" (numeric, optional) time in seconds how long the ip is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n" + "\nExamples:\n" + + HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") + + HelpExampleRpc("setban", "\"192.168.0.6\", \"add\" 86400") + ); + + CNetAddr netAddr(params[0].get_str()); + if (!netAddr.IsValid()) + throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Invalid IP Address"); + + if (strCommand == "add") + { + if (CNode::IsBanned(netAddr)) + throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP already banned"); + + int64_t banTime = 0; //use standard bantime if not specified + if (params.size() == 3 && !params[2].is_null()) + banTime = params[2].get_int64(); + + CNode::Ban(netAddr, banTime); + + //disconnect possible nodes + while(CNode *bannedNode = FindNode(netAddr)) + bannedNode->CloseSocketDisconnect(); + } + else if(strCommand == "remove") + { + if (!CNode::Unban(netAddr)) + throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Unban failed"); + } + + return Value::null; +} + +Value listbanned(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "listbanned\n" + "\nList all banned IPs.\n" + "\nExamples:\n" + + HelpExampleCli("listbanned", "") + + HelpExampleRpc("listbanned", "") + ); + + std::map banMap; + CNode::GetBanned(banMap); + + Array bannedAddresses; + for (std::map::iterator it = banMap.begin(); it != banMap.end(); it++) + { + Object rec; + rec.push_back(Pair("address", (*it).first.ToString())); + rec.push_back(Pair("bannedtill", (*it).second)); + bannedAddresses.push_back(rec); + } + + return bannedAddresses; +} + +Value clearbanned(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "clearbanned\n" + "\nClear all banned IPs.\n" + "\nExamples:\n" + + HelpExampleCli("clearbanned", "") + + HelpExampleRpc("clearbanned", "") + ); + + CNode::ClearBanned(); + + return Value::null; +} diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index cb3776465..d3eb8217a 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -286,6 +286,9 @@ static const CRPCCommand vRPCCommands[] = { "network", "getnettotals", &getnettotals, true }, { "network", "getpeerinfo", &getpeerinfo, true }, { "network", "ping", &ping, true }, + { "network", "setban", &setban, true }, + { "network", "listbanned", &listbanned, true }, + { "network", "clearbanned", &clearbanned, true }, /* Block chain and UTXO */ { "blockchain", "getblockchaininfo", &getblockchaininfo, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index cebea25b4..18e5bbbaa 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -161,6 +161,9 @@ extern UniValue addnode(const UniValue& params, bool fHelp); extern UniValue disconnectnode(const UniValue& params, bool fHelp); extern UniValue getaddednodeinfo(const UniValue& params, bool fHelp); extern UniValue getnettotals(const UniValue& params, bool fHelp); +extern UniValue setban(const json_spirit::Array& params, bool fHelp); +extern UniValue listbanned(const json_spirit::Array& params, bool fHelp); +extern UniValue clearbanned(const json_spirit::Array& params, bool fHelp); extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp extern UniValue importprivkey(const UniValue& params, bool fHelp); From 962ec4b5e4b73b5a8dee9d40475cb3cf817dd001 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Tue, 19 May 2015 10:20:31 +0200 Subject: [PATCH 06/43] [QA] add setban/listbanned/clearbanned tests --- qa/rpc-tests/httpbasics.py | 39 ++++++++++++++++++++++++-------------- src/test/rpc_tests.cpp | 9 +++++++++ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/qa/rpc-tests/httpbasics.py b/qa/rpc-tests/httpbasics.py index 21c7b1f8b..089009201 100755 --- a/qa/rpc-tests/httpbasics.py +++ b/qa/rpc-tests/httpbasics.py @@ -20,19 +20,19 @@ try: except ImportError: import urlparse -class HTTPBasicsTest (BitcoinTestFramework): +class HTTPBasicsTest (BitcoinTestFramework): def setup_nodes(self): return start_nodes(4, self.options.tmpdir, extra_args=[['-rpckeepalive=1'], ['-rpckeepalive=0'], [], []]) - def run_test(self): - + def run_test(self): + ################################################# # lowlevel check for http persistent connection # ################################################# url = urlparse.urlparse(self.nodes[0].url) authpair = url.username + ':' + url.password headers = {"Authorization": "Basic " + base64.b64encode(authpair)} - + conn = httplib.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) @@ -42,10 +42,10 @@ class HTTPBasicsTest (BitcoinTestFramework): # TODO #1856: Re-enable support for persistent connections. assert_equal(conn.sock!=None, False) conn.close() - + #same should be if we add keep-alive because this should be the std. behaviour headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection": "keep-alive"} - + conn = httplib.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) @@ -55,34 +55,34 @@ class HTTPBasicsTest (BitcoinTestFramework): # TODO #1856: Re-enable support for persistent connections. assert_equal(conn.sock!=None, False) conn.close() - + #now do the same with "Connection: close" headers = {"Authorization": "Basic " + base64.b64encode(authpair), "Connection":"close"} - + conn = httplib.HTTPConnection(url.hostname, url.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) out1 = conn.getresponse().read(); assert_equal('"error":null' in out1, True) - assert_equal(conn.sock!=None, False) #now the connection must be closed after the response - + assert_equal(conn.sock!=None, False) #now the connection must be closed after the response + #node1 (2nd node) is running with disabled keep-alive option urlNode1 = urlparse.urlparse(self.nodes[1].url) authpair = urlNode1.username + ':' + urlNode1.password headers = {"Authorization": "Basic " + base64.b64encode(authpair)} - + conn = httplib.HTTPConnection(urlNode1.hostname, urlNode1.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) out1 = conn.getresponse().read(); assert_equal('"error":null' in out1, True) assert_equal(conn.sock!=None, False) #connection must be closed because keep-alive was set to false - + #node2 (third node) is running with standard keep-alive parameters which means keep-alive is off urlNode2 = urlparse.urlparse(self.nodes[2].url) authpair = urlNode2.username + ':' + urlNode2.password headers = {"Authorization": "Basic " + base64.b64encode(authpair)} - + conn = httplib.HTTPConnection(urlNode2.hostname, urlNode2.port) conn.connect() conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) @@ -92,6 +92,17 @@ class HTTPBasicsTest (BitcoinTestFramework): # TODO #1856: Re-enable support for persistent connections. assert_equal(conn.sock!=None, False) conn.close() - + + ########################### + # setban/listbanned tests # + ########################### + assert_equal(len(self.nodes[2].getpeerinfo()), 4); #we should have 4 nodes at this point + self.nodes[2].setban("127.0.0.1", "add") + time.sleep(3) #wait till the nodes are disconected + assert_equal(len(self.nodes[2].getpeerinfo()), 0); #all nodes must be disconnected at this point + assert_equal(len(self.nodes[2].listbanned()), 1); + self.nodes[2].clearbanned() + assert_equal(len(self.nodes[2].listbanned()), 0); + if __name__ == '__main__': HTTPBasicsTest ().main () diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 758c02f64..3a588b271 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -227,4 +227,13 @@ BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr) BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1"); } +BOOST_AUTO_TEST_CASE(rpc_ban) +{ + BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.1 add"))); + BOOST_CHECK_THROW(CallRPC(string("setban 127.0.0.1:8334")), runtime_error); //portnumber for setban not allowed + BOOST_CHECK_NO_THROW(CallRPC(string("listbanned"))); + BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.1 remove"))); + BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); +} + BOOST_AUTO_TEST_SUITE_END() From 445cd761c5ce0a47e28b885c959d063d9a7a6adb Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Tue, 19 May 2015 17:15:25 +0200 Subject: [PATCH 07/43] [net] remove unused return type bool from CNode::Ban() --- src/net.cpp | 4 +--- src/net.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 77227c51b..ec96beb39 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -464,7 +464,7 @@ bool CNode::IsBanned(CNetAddr ip) return fResult; } -bool CNode::Ban(const CNetAddr &addr, int64_t bantimeoffset) { +void CNode::Ban(const CNetAddr &addr, int64_t bantimeoffset) { int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban if (bantimeoffset > 0) banTime = GetTime()+bantimeoffset; @@ -472,8 +472,6 @@ bool CNode::Ban(const CNetAddr &addr, int64_t bantimeoffset) { LOCK(cs_setBanned); if (setBanned[addr] < banTime) setBanned[addr] = banTime; - - return true; } bool CNode::Unban(const CNetAddr &addr) { diff --git a/src/net.h b/src/net.h index db539f2ed..86aabb888 100644 --- a/src/net.h +++ b/src/net.h @@ -613,7 +613,7 @@ public: // new code. static void ClearBanned(); // needed for unit testing static bool IsBanned(CNetAddr ip); - static bool Ban(const CNetAddr &ip, int64_t bantimeoffset = 0); + static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0); static bool Unban(const CNetAddr &ip); static void GetBanned(std::map &banmap); From e521939942ce1a375a024439b18aac6d72d898a1 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Mon, 25 May 2015 20:03:51 +0200 Subject: [PATCH 08/43] [RPC] extend setban to allow subnets --- qa/rpc-tests/httpbasics.py | 25 +++++++++++++++---- src/net.cpp | 50 ++++++++++++++++++++++++++++++++------ src/net.h | 8 ++++-- src/netbase.cpp | 5 ++++ src/netbase.h | 1 + src/rpcnet.cpp | 44 +++++++++++++++++++++------------ src/test/rpc_tests.cpp | 40 +++++++++++++++++++++++++++--- 7 files changed, 139 insertions(+), 34 deletions(-) diff --git a/qa/rpc-tests/httpbasics.py b/qa/rpc-tests/httpbasics.py index 089009201..80d40c1d0 100755 --- a/qa/rpc-tests/httpbasics.py +++ b/qa/rpc-tests/httpbasics.py @@ -96,13 +96,28 @@ class HTTPBasicsTest (BitcoinTestFramework): ########################### # setban/listbanned tests # ########################### - assert_equal(len(self.nodes[2].getpeerinfo()), 4); #we should have 4 nodes at this point + assert_equal(len(self.nodes[2].getpeerinfo()), 4) #we should have 4 nodes at this point self.nodes[2].setban("127.0.0.1", "add") time.sleep(3) #wait till the nodes are disconected - assert_equal(len(self.nodes[2].getpeerinfo()), 0); #all nodes must be disconnected at this point - assert_equal(len(self.nodes[2].listbanned()), 1); + assert_equal(len(self.nodes[2].getpeerinfo()), 0) #all nodes must be disconnected at this point + assert_equal(len(self.nodes[2].listbanned()), 1) self.nodes[2].clearbanned() - assert_equal(len(self.nodes[2].listbanned()), 0); - + assert_equal(len(self.nodes[2].listbanned()), 0) + self.nodes[2].setban("127.0.0.0/24", "add") + assert_equal(len(self.nodes[2].listbanned()), 1) + try: + self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24 + except: + pass + assert_equal(len(self.nodes[2].listbanned()), 1) #still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24 + try: + self.nodes[2].setban("127.0.0.1", "remove") + except: + pass + assert_equal(len(self.nodes[2].listbanned()), 1) + self.nodes[2].setban("127.0.0.0/24", "remove") + assert_equal(len(self.nodes[2].listbanned()), 0) + self.nodes[2].clearbanned() + assert_equal(len(self.nodes[2].listbanned()), 0) if __name__ == '__main__': HTTPBasicsTest ().main () diff --git a/src/net.cpp b/src/net.cpp index ec96beb39..53d073c06 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -332,6 +332,15 @@ CNode* FindNode(const CNetAddr& ip) return NULL; } +CNode* FindNode(const CSubNet& subNet) +{ + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if (subNet.Match((CNetAddr)pnode->addr)) + return (pnode); + return NULL; +} + CNode* FindNode(const std::string& addrName) { LOCK(cs_vNodes); @@ -440,7 +449,7 @@ void CNode::PushVersion() -std::map CNode::setBanned; +std::map CNode::setBanned; CCriticalSection CNode::cs_setBanned; void CNode::ClearBanned() @@ -453,7 +462,24 @@ bool CNode::IsBanned(CNetAddr ip) bool fResult = false; { LOCK(cs_setBanned); - std::map::iterator i = setBanned.find(ip); + for (std::map::iterator it = setBanned.begin(); it != setBanned.end(); it++) + { + CSubNet subNet = (*it).first; + int64_t t = (*it).second; + + if(subNet.Match(ip) && GetTime() < t) + fResult = true; + } + } + return fResult; +} + +bool CNode::IsBanned(CSubNet subnet) +{ + bool fResult = false; + { + LOCK(cs_setBanned); + std::map::iterator i = setBanned.find(subnet); if (i != setBanned.end()) { int64_t t = (*i).second; @@ -464,24 +490,34 @@ bool CNode::IsBanned(CNetAddr ip) return fResult; } -void CNode::Ban(const CNetAddr &addr, int64_t bantimeoffset) { +void CNode::Ban(const CNetAddr& addr, int64_t bantimeoffset) { + CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128")); + Ban(subNet, bantimeoffset); +} + +void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset) { int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban if (bantimeoffset > 0) banTime = GetTime()+bantimeoffset; LOCK(cs_setBanned); - if (setBanned[addr] < banTime) - setBanned[addr] = banTime; + if (setBanned[subNet] < banTime) + setBanned[subNet] = banTime; } bool CNode::Unban(const CNetAddr &addr) { + CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128")); + return Unban(subNet); +} + +bool CNode::Unban(const CSubNet &subNet) { LOCK(cs_setBanned); - if (setBanned.erase(addr)) + if (setBanned.erase(subNet)) return true; return false; } -void CNode::GetBanned(std::map &banMap) +void CNode::GetBanned(std::map &banMap) { LOCK(cs_setBanned); banMap = setBanned; //create a thread safe copy diff --git a/src/net.h b/src/net.h index 86aabb888..77a0bd029 100644 --- a/src/net.h +++ b/src/net.h @@ -68,6 +68,7 @@ unsigned int SendBufferSize(); void AddOneShot(std::string strDest); void AddressCurrentlyConnected(const CService& addr); CNode* FindNode(const CNetAddr& ip); +CNode* FindNode(const CSubNet& subNet); CNode* FindNode(const std::string& addrName); CNode* FindNode(const CService& ip); CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL); @@ -288,7 +289,7 @@ protected: // Denial-of-service detection/prevention // Key is IP address, value is banned-until-time - static std::map setBanned; + static std::map setBanned; static CCriticalSection cs_setBanned; // Whitelisted ranges. Any node connecting from these is automatically @@ -613,9 +614,12 @@ public: // new code. static void ClearBanned(); // needed for unit testing static bool IsBanned(CNetAddr ip); + static bool IsBanned(CSubNet subnet); static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0); + static void Ban(const CSubNet &subNet, int64_t bantimeoffset = 0); static bool Unban(const CNetAddr &ip); - static void GetBanned(std::map &banmap); + static bool Unban(const CSubNet &ip); + static void GetBanned(std::map &banmap); void copyStats(CNodeStats &stats); diff --git a/src/netbase.cpp b/src/netbase.cpp index 54d2bef7c..998a03d66 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -1340,6 +1340,11 @@ bool operator!=(const CSubNet& a, const CSubNet& b) return !(a==b); } +bool operator<(const CSubNet& a, const CSubNet& b) +{ + return (a.network < b.network || (a.network == b.network && memcmp(a.netmask, b.netmask, 16))); +} + #ifdef WIN32 std::string NetworkErrorString(int err) { diff --git a/src/netbase.h b/src/netbase.h index 1f2957116..27f0eac2a 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -125,6 +125,7 @@ class CSubNet friend bool operator==(const CSubNet& a, const CSubNet& b); friend bool operator!=(const CSubNet& a, const CSubNet& b); + friend bool operator<(const CSubNet& a, const CSubNet& b); }; /** A combination of a network address (CNetAddr) and a (TCP) port */ diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index d702d59a2..9c2374894 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -475,39 +475,51 @@ Value setban(const Array& params, bool fHelp) if (fHelp || params.size() < 2 || (strCommand != "add" && strCommand != "remove")) throw runtime_error( - "setban \"node\" \"add|remove\" (bantime)\n" - "\nAttempts add or remove a IP from the banned list.\n" + "setban \"ip(/netmask)\" \"add|remove\" (bantime)\n" + "\nAttempts add or remove a IP/Subnet from the banned list.\n" "\nArguments:\n" - "1. \"ip\" (string, required) The IP (see getpeerinfo for nodes ip)\n" - "2. \"command\" (string, required) 'add' to add a IP to the list, 'remove' to remove a IP from the list\n" - "1. \"bantime\" (numeric, optional) time in seconds how long the ip is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n" + "1. \"ip(/netmask)\" (string, required) The IP/Subnet (see getpeerinfo for nodes ip) with a optional netmask (default is /32 = single ip)\n" + "2. \"command\" (string, required) 'add' to add a IP/Subnet to the list, 'remove' to remove a IP/Subnet from the list\n" + "1. \"bantime\" (numeric, optional) time in seconds how long the ip is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n" "\nExamples:\n" + HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") + + HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") + HelpExampleRpc("setban", "\"192.168.0.6\", \"add\" 86400") ); - CNetAddr netAddr(params[0].get_str()); - if (!netAddr.IsValid()) - throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Invalid IP Address"); + CSubNet subNet; + CNetAddr netAddr; + bool isSubnet = false; + + if (params[0].get_str().find("/") != string::npos) + isSubnet = true; + + if (!isSubnet) + netAddr = CNetAddr(params[0].get_str()); + else + subNet = CSubNet(params[0].get_str()); + + if (! (isSubnet ? subNet.IsValid() : netAddr.IsValid()) ) + throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Invalid IP/Subnet"); if (strCommand == "add") { - if (CNode::IsBanned(netAddr)) - throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP already banned"); + if (isSubnet ? CNode::IsBanned(subNet) : CNode::IsBanned(netAddr)) + throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned"); int64_t banTime = 0; //use standard bantime if not specified if (params.size() == 3 && !params[2].is_null()) banTime = params[2].get_int64(); - CNode::Ban(netAddr, banTime); + isSubnet ? CNode::Ban(subNet, banTime) : CNode::Ban(netAddr, banTime); //disconnect possible nodes - while(CNode *bannedNode = FindNode(netAddr)) + while(CNode *bannedNode = (isSubnet ? FindNode(subNet) : FindNode(netAddr))) bannedNode->CloseSocketDisconnect(); } else if(strCommand == "remove") { - if (!CNode::Unban(netAddr)) + if (!( isSubnet ? CNode::Unban(subNet) : CNode::Unban(netAddr) )) throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Unban failed"); } @@ -519,17 +531,17 @@ Value listbanned(const Array& params, bool fHelp) if (fHelp || params.size() != 0) throw runtime_error( "listbanned\n" - "\nList all banned IPs.\n" + "\nList all banned IPs/Subnets.\n" "\nExamples:\n" + HelpExampleCli("listbanned", "") + HelpExampleRpc("listbanned", "") ); - std::map banMap; + std::map banMap; CNode::GetBanned(banMap); Array bannedAddresses; - for (std::map::iterator it = banMap.begin(); it != banMap.end(); it++) + for (std::map::iterator it = banMap.begin(); it != banMap.end(); it++) { Object rec; rec.push_back(Pair("address", (*it).first.ToString())); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 3a588b271..f26ced30d 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -229,11 +229,43 @@ BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr) BOOST_AUTO_TEST_CASE(rpc_ban) { - BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.1 add"))); - BOOST_CHECK_THROW(CallRPC(string("setban 127.0.0.1:8334")), runtime_error); //portnumber for setban not allowed - BOOST_CHECK_NO_THROW(CallRPC(string("listbanned"))); - BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.1 remove"))); BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); + + Value r; + BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0 add"))); + BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.0.0:8334")), runtime_error); //portnumber for setban not allowed + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + Array ar = r.get_array(); + Object o1 = ar[0].get_obj(); + Value adr = find_value(o1, "address"); + BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.255"); + BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.0 remove")));; + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + ar = r.get_array(); + BOOST_CHECK_EQUAL(ar.size(), 0); + + BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/24 add"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + ar = r.get_array(); + o1 = ar[0].get_obj(); + adr = find_value(o1, "address"); + BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0"); + + // must throw an exception because 127.0.0.1 is in already banned suubnet range + BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.0.1 add")), runtime_error); + + BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.0/24 remove")));; + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + ar = r.get_array(); + BOOST_CHECK_EQUAL(ar.size(), 0); + + BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/255.255.0.0 add"))); + BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.1.1 add")), runtime_error); + + BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + ar = r.get_array(); + BOOST_CHECK_EQUAL(ar.size(), 0); } BOOST_AUTO_TEST_SUITE_END() From 63c06b232ba49260cb1ac2048b820901e867fc0b Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 12 Jun 2015 17:42:32 +0200 Subject: [PATCH 09/43] rename json field "bannedtill" to "banned_until" --- src/rpcnet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 9c2374894..08e016890 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -545,7 +545,7 @@ Value listbanned(const Array& params, bool fHelp) { Object rec; rec.push_back(Pair("address", (*it).first.ToString())); - rec.push_back(Pair("bannedtill", (*it).second)); + rec.push_back(Pair("banned_untill", (*it).second)); bannedAddresses.push_back(rec); } From fcc8920f36fc306c5c1ead6ab41c81e9c55495c8 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 12 Jun 2015 18:31:47 +0200 Subject: [PATCH 10/43] setban: rewrite to UniValue, allow absolute bantime --- src/net.cpp | 8 ++++---- src/net.h | 4 ++-- src/rpcclient.cpp | 1 + src/rpcnet.cpp | 27 ++++++++++++++++----------- src/rpcserver.h | 6 +++--- src/test/rpc_tests.cpp | 25 ++++++++++++++++++++----- 6 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 53d073c06..1805c8af7 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -490,15 +490,15 @@ bool CNode::IsBanned(CSubNet subnet) return fResult; } -void CNode::Ban(const CNetAddr& addr, int64_t bantimeoffset) { +void CNode::Ban(const CNetAddr& addr, int64_t bantimeoffset, bool sinceUnixEpoch) { CSubNet subNet(addr.ToString()+(addr.IsIPv4() ? "/32" : "/128")); - Ban(subNet, bantimeoffset); + Ban(subNet, bantimeoffset, sinceUnixEpoch); } -void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset) { +void CNode::Ban(const CSubNet& subNet, int64_t bantimeoffset, bool sinceUnixEpoch) { int64_t banTime = GetTime()+GetArg("-bantime", 60*60*24); // Default 24-hour ban if (bantimeoffset > 0) - banTime = GetTime()+bantimeoffset; + banTime = (sinceUnixEpoch ? 0 : GetTime() )+bantimeoffset; LOCK(cs_setBanned); if (setBanned[subNet] < banTime) diff --git a/src/net.h b/src/net.h index 77a0bd029..95bb79e1a 100644 --- a/src/net.h +++ b/src/net.h @@ -615,8 +615,8 @@ public: static void ClearBanned(); // needed for unit testing static bool IsBanned(CNetAddr ip); static bool IsBanned(CSubNet subnet); - static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0); - static void Ban(const CSubNet &subNet, int64_t bantimeoffset = 0); + static void Ban(const CNetAddr &ip, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false); + static void Ban(const CSubNet &subNet, int64_t bantimeoffset = 0, bool sinceUnixEpoch = false); static bool Unban(const CNetAddr &ip); static bool Unban(const CSubNet &ip); static void GetBanned(std::map &banmap); diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index c40fbeedd..1d0073a08 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -93,6 +93,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "prioritisetransaction", 1 }, { "prioritisetransaction", 2 }, { "setban", 2 }, + { "setban", 3 }, { "zcrawjoinsplit", 1 }, { "zcrawjoinsplit", 2 }, { "zcrawjoinsplit", 3 }, diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 08e016890..19d6c61ca 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -467,7 +467,7 @@ UniValue getnetworkinfo(const UniValue& params, bool fHelp) return obj; } -Value setban(const Array& params, bool fHelp) +UniValue setban(const UniValue& params, bool fHelp) { string strCommand; if (params.size() >= 2) @@ -475,12 +475,13 @@ Value setban(const Array& params, bool fHelp) if (fHelp || params.size() < 2 || (strCommand != "add" && strCommand != "remove")) throw runtime_error( - "setban \"ip(/netmask)\" \"add|remove\" (bantime)\n" + "setban \"ip(/netmask)\" \"add|remove\" (bantime) (absolute)\n" "\nAttempts add or remove a IP/Subnet from the banned list.\n" "\nArguments:\n" "1. \"ip(/netmask)\" (string, required) The IP/Subnet (see getpeerinfo for nodes ip) with a optional netmask (default is /32 = single ip)\n" "2. \"command\" (string, required) 'add' to add a IP/Subnet to the list, 'remove' to remove a IP/Subnet from the list\n" - "1. \"bantime\" (numeric, optional) time in seconds how long the ip is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n" + "3. \"bantime\" (numeric, optional) time in seconds how long (or until when if [absolute] is set) the ip is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)\n" + "4. \"absolute\" (boolean, optional) If set, the bantime must be a absolute timestamp in seconds since epoch (Jan 1 1970 GMT)\n" "\nExamples:\n" + HelpExampleCli("setban", "\"192.168.0.6\" \"add\" 86400") + HelpExampleCli("setban", "\"192.168.0.0/24\" \"add\"") @@ -508,10 +509,14 @@ Value setban(const Array& params, bool fHelp) throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned"); int64_t banTime = 0; //use standard bantime if not specified - if (params.size() == 3 && !params[2].is_null()) + if (params.size() >= 3 && !params[2].isNull()) banTime = params[2].get_int64(); - isSubnet ? CNode::Ban(subNet, banTime) : CNode::Ban(netAddr, banTime); + bool absolute = false; + if (params.size() == 4 && params[3].isTrue()) + absolute = true; + + isSubnet ? CNode::Ban(subNet, banTime, absolute) : CNode::Ban(netAddr, banTime, absolute); //disconnect possible nodes while(CNode *bannedNode = (isSubnet ? FindNode(subNet) : FindNode(netAddr))) @@ -523,10 +528,10 @@ Value setban(const Array& params, bool fHelp) throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Unban failed"); } - return Value::null; + return NullUniValue; } -Value listbanned(const Array& params, bool fHelp) +UniValue listbanned(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( @@ -540,10 +545,10 @@ Value listbanned(const Array& params, bool fHelp) std::map banMap; CNode::GetBanned(banMap); - Array bannedAddresses; + UniValue bannedAddresses(UniValue::VARR); for (std::map::iterator it = banMap.begin(); it != banMap.end(); it++) { - Object rec; + UniValue rec(UniValue::VOBJ); rec.push_back(Pair("address", (*it).first.ToString())); rec.push_back(Pair("banned_untill", (*it).second)); bannedAddresses.push_back(rec); @@ -552,7 +557,7 @@ Value listbanned(const Array& params, bool fHelp) return bannedAddresses; } -Value clearbanned(const Array& params, bool fHelp) +UniValue clearbanned(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 0) throw runtime_error( @@ -565,5 +570,5 @@ Value clearbanned(const Array& params, bool fHelp) CNode::ClearBanned(); - return Value::null; + return NullUniValue; } diff --git a/src/rpcserver.h b/src/rpcserver.h index 18e5bbbaa..9a090d0de 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -161,9 +161,9 @@ extern UniValue addnode(const UniValue& params, bool fHelp); extern UniValue disconnectnode(const UniValue& params, bool fHelp); extern UniValue getaddednodeinfo(const UniValue& params, bool fHelp); extern UniValue getnettotals(const UniValue& params, bool fHelp); -extern UniValue setban(const json_spirit::Array& params, bool fHelp); -extern UniValue listbanned(const json_spirit::Array& params, bool fHelp); -extern UniValue clearbanned(const json_spirit::Array& params, bool fHelp); +extern UniValue setban(const UniValue& params, bool fHelp); +extern UniValue listbanned(const UniValue& params, bool fHelp); +extern UniValue clearbanned(const UniValue& params, bool fHelp); extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp extern UniValue importprivkey(const UniValue& params, bool fHelp); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index f26ced30d..1e9fd6847 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -231,25 +231,40 @@ BOOST_AUTO_TEST_CASE(rpc_ban) { BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); - Value r; + UniValue r; BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0 add"))); BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.0.0:8334")), runtime_error); //portnumber for setban not allowed BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); - Array ar = r.get_array(); - Object o1 = ar[0].get_obj(); - Value adr = find_value(o1, "address"); + UniValue ar = r.get_array(); + UniValue o1 = ar[0].get_obj(); + UniValue adr = find_value(o1, "address"); BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.255"); BOOST_CHECK_NO_THROW(CallRPC(string("setban 127.0.0.0 remove")));; BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); ar = r.get_array(); BOOST_CHECK_EQUAL(ar.size(), 0); - BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/24 add"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/24 add 1607731200 true"))); BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); ar = r.get_array(); o1 = ar[0].get_obj(); adr = find_value(o1, "address"); + UniValue banned_until = find_value(o1, "banned_untill"); BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0"); + BOOST_CHECK_EQUAL(banned_until.get_int64(), 1607731200); // absolute time check + + BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); + + BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 127.0.0.0/24 add 200"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + ar = r.get_array(); + o1 = ar[0].get_obj(); + adr = find_value(o1, "address"); + banned_until = find_value(o1, "banned_untill"); + BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0"); + int64_t now = GetTime(); + BOOST_CHECK(banned_until.get_int64() > now); + BOOST_CHECK(banned_until.get_int64()-now <= 200); // must throw an exception because 127.0.0.1 is in already banned suubnet range BOOST_CHECK_THROW(r = CallRPC(string("setban 127.0.0.1 add")), runtime_error); From 171b4de8d91e92de8873df58d4e581796cb2fd16 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 12 Jun 2015 19:51:50 +0200 Subject: [PATCH 11/43] fix CSubNet comparison operator --- src/netbase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/netbase.cpp b/src/netbase.cpp index 998a03d66..8ff621942 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -1342,7 +1342,7 @@ bool operator!=(const CSubNet& a, const CSubNet& b) bool operator<(const CSubNet& a, const CSubNet& b) { - return (a.network < b.network || (a.network == b.network && memcmp(a.netmask, b.netmask, 16))); + return (a.network < b.network || (a.network == b.network && memcmp(a.netmask, b.netmask, 16) < 0)); } #ifdef WIN32 From 1cad8c9ff9f837aa7524b86096804e4bbdd319e0 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 12 Jun 2015 22:27:03 +0200 Subject: [PATCH 12/43] setban: add RPCErrorCode --- src/rpcnet.cpp | 2 +- src/rpcprotocol.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 19d6c61ca..36c49720e 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -525,7 +525,7 @@ UniValue setban(const UniValue& params, bool fHelp) else if(strCommand == "remove") { if (!( isSubnet ? CNode::Unban(subNet) : CNode::Unban(netAddr) )) - throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Unban failed"); + throw JSONRPCError(RPC_MISC_ERROR, "Error: Unban failed"); } return NullUniValue; diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h index 37b03a0f8..6a658ae96 100644 --- a/src/rpcprotocol.h +++ b/src/rpcprotocol.h @@ -65,6 +65,7 @@ enum RPCErrorCode RPC_CLIENT_NODE_ALREADY_ADDED = -23, //! Node is already added RPC_CLIENT_NODE_NOT_ADDED = -24, //! Node has not been added before RPC_CLIENT_NODE_NOT_CONNECTED = -29, //! Node to disconnect not found in connected nodes + RPC_CLIENT_INVALID_IP_OR_SUBNET = -30, //! Invalid IP/Subnet //! Wallet errors RPC_WALLET_ERROR = -4, //! Unspecified problem with wallet (key not found etc.) From 2ab237a088023ab66c37edd011971f540ac03574 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Tue, 16 Jun 2015 18:21:03 +0200 Subject: [PATCH 13/43] add RPC tests for setban & disconnectnode --- qa/pull-tester/rpc-tests.sh | 1 + qa/rpc-tests/httpbasics.py | 28 +-------------- qa/rpc-tests/nodehandling.py | 69 ++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 27 deletions(-) create mode 100755 qa/rpc-tests/nodehandling.py diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 6512a7dae..d4899ecdf 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -32,6 +32,7 @@ testScripts=( 'merkle_blocks.py' 'signrawtransactions.py' 'walletbackup.py' + 'nodehandling.py' 'zcjoinsplit.py' 'zcjoinsplitdoublespend.py' 'getblocktemplate.py' diff --git a/qa/rpc-tests/httpbasics.py b/qa/rpc-tests/httpbasics.py index 80d40c1d0..20f3218d7 100755 --- a/qa/rpc-tests/httpbasics.py +++ b/qa/rpc-tests/httpbasics.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. # -# Test REST interface +# Test rpc http basics # from test_framework.test_framework import BitcoinTestFramework @@ -93,31 +93,5 @@ class HTTPBasicsTest (BitcoinTestFramework): assert_equal(conn.sock!=None, False) conn.close() - ########################### - # setban/listbanned tests # - ########################### - assert_equal(len(self.nodes[2].getpeerinfo()), 4) #we should have 4 nodes at this point - self.nodes[2].setban("127.0.0.1", "add") - time.sleep(3) #wait till the nodes are disconected - assert_equal(len(self.nodes[2].getpeerinfo()), 0) #all nodes must be disconnected at this point - assert_equal(len(self.nodes[2].listbanned()), 1) - self.nodes[2].clearbanned() - assert_equal(len(self.nodes[2].listbanned()), 0) - self.nodes[2].setban("127.0.0.0/24", "add") - assert_equal(len(self.nodes[2].listbanned()), 1) - try: - self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24 - except: - pass - assert_equal(len(self.nodes[2].listbanned()), 1) #still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24 - try: - self.nodes[2].setban("127.0.0.1", "remove") - except: - pass - assert_equal(len(self.nodes[2].listbanned()), 1) - self.nodes[2].setban("127.0.0.0/24", "remove") - assert_equal(len(self.nodes[2].listbanned()), 0) - self.nodes[2].clearbanned() - assert_equal(len(self.nodes[2].listbanned()), 0) if __name__ == '__main__': HTTPBasicsTest ().main () diff --git a/qa/rpc-tests/nodehandling.py b/qa/rpc-tests/nodehandling.py new file mode 100755 index 000000000..9a77bd97e --- /dev/null +++ b/qa/rpc-tests/nodehandling.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python2 +# 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 node handling +# + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +import base64 + +try: + import http.client as httplib +except ImportError: + import httplib +try: + import urllib.parse as urlparse +except ImportError: + import urlparse + +class NodeHandlingTest (BitcoinTestFramework): + def run_test(self): + ########################### + # setban/listbanned tests # + ########################### + assert_equal(len(self.nodes[2].getpeerinfo()), 4) #we should have 4 nodes at this point + self.nodes[2].setban("127.0.0.1", "add") + time.sleep(3) #wait till the nodes are disconected + assert_equal(len(self.nodes[2].getpeerinfo()), 0) #all nodes must be disconnected at this point + assert_equal(len(self.nodes[2].listbanned()), 1) + self.nodes[2].clearbanned() + assert_equal(len(self.nodes[2].listbanned()), 0) + self.nodes[2].setban("127.0.0.0/24", "add") + assert_equal(len(self.nodes[2].listbanned()), 1) + try: + self.nodes[2].setban("127.0.0.1", "add") #throws exception because 127.0.0.1 is within range 127.0.0.0/24 + except: + pass + assert_equal(len(self.nodes[2].listbanned()), 1) #still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24 + try: + self.nodes[2].setban("127.0.0.1", "remove") + except: + pass + assert_equal(len(self.nodes[2].listbanned()), 1) + self.nodes[2].setban("127.0.0.0/24", "remove") + assert_equal(len(self.nodes[2].listbanned()), 0) + self.nodes[2].clearbanned() + assert_equal(len(self.nodes[2].listbanned()), 0) + + ########################### + # RPC disconnectnode test # + ########################### + url = urlparse.urlparse(self.nodes[1].url) + self.nodes[0].disconnectnode(url.hostname+":"+str(p2p_port(1))) + time.sleep(2) #disconnecting a node needs a little bit of time + for node in self.nodes[0].getpeerinfo(): + assert(node['addr'] != url.hostname+":"+str(p2p_port(1))) + + connect_nodes_bi(self.nodes,0,1) #reconnect the node + found = False + for node in self.nodes[0].getpeerinfo(): + if node['addr'] == url.hostname+":"+str(p2p_port(1)): + found = True + assert(found) + +if __name__ == '__main__': + NodeHandlingTest ().main () From 0382246bb1fa5a15b0507181220ccd02b31ec048 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 19 Jun 2015 13:31:33 +0200 Subject: [PATCH 14/43] fix missing lock in CNode::ClearBanned() --- src/net.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/net.cpp b/src/net.cpp index 1805c8af7..d079369b9 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -454,6 +454,7 @@ CCriticalSection CNode::cs_setBanned; void CNode::ClearBanned() { + LOCK(cs_setBanned); setBanned.clear(); } From fa80be8ff5bcb6e32783046a62165e9075f855f5 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 19 Jun 2015 13:51:42 +0200 Subject: [PATCH 15/43] setban: add IPv6 tests --- src/test/rpc_tests.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 1e9fd6847..4e063a840 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -281,6 +281,33 @@ BOOST_AUTO_TEST_CASE(rpc_ban) BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); ar = r.get_array(); BOOST_CHECK_EQUAL(ar.size(), 0); + + + BOOST_CHECK_THROW(r = CallRPC(string("setban test add")), runtime_error); //invalid IP + + //IPv6 tests + BOOST_CHECK_NO_THROW(r = CallRPC(string("setban FE80:0000:0000:0000:0202:B3FF:FE1E:8329 add"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + ar = r.get_array(); + o1 = ar[0].get_obj(); + adr = find_value(o1, "address"); + BOOST_CHECK_EQUAL(adr.get_str(), "fe80::202:b3ff:fe1e:8329/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); + + BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 2001:db8::/30 add"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + ar = r.get_array(); + o1 = ar[0].get_obj(); + adr = find_value(o1, "address"); + BOOST_CHECK_EQUAL(adr.get_str(), "2001:db8::/ffff:fffc:0:0:0:0:0:0"); + + BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("setban 2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/128 add"))); + BOOST_CHECK_NO_THROW(r = CallRPC(string("listbanned"))); + ar = r.get_array(); + o1 = ar[0].get_obj(); + adr = find_value(o1, "address"); + BOOST_CHECK_EQUAL(adr.get_str(), "2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); } BOOST_AUTO_TEST_SUITE_END() From cbf3ab51f97b9568fb9781cf9bb972ef796e9cbf Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 19 Jun 2015 16:32:22 +0200 Subject: [PATCH 16/43] fix lock issue for QT node diconnect and RPC disconnectnode Zcash: only RPC disconnectnode --- src/rpcnet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 36c49720e..8eb2ef84d 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -231,7 +231,7 @@ UniValue disconnectnode(const UniValue& params, bool fHelp) if (pNode == NULL) throw JSONRPCError(RPC_CLIENT_NODE_NOT_CONNECTED, "Node not found in connected nodes"); - pNode->CloseSocketDisconnect(); + pNode->fDisconnect = true; return NullUniValue; } @@ -520,7 +520,7 @@ UniValue setban(const UniValue& params, bool fHelp) //disconnect possible nodes while(CNode *bannedNode = (isSubnet ? FindNode(subNet) : FindNode(netAddr))) - bannedNode->CloseSocketDisconnect(); + bannedNode->fDisconnect = true; } else if(strCommand == "remove") { From a0455eca119c9725dce83bec4b300b2caba6717e Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Wed, 22 Oct 2014 09:25:08 +0200 Subject: [PATCH 17/43] rpc: make `gettxoutsettinfo` run lock-free For leveldb "An iterator operates on a snapshot of the database taken when the iterator is created". This means that it is unnecessary to lock out other threads while computing statistics, and neither to hold cs_main for the whole time. Let the thread run free. --- src/rpcblockchain.cpp | 2 -- src/txdb.cpp | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 365ae3584..64b71797f 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -367,8 +367,6 @@ UniValue gettxoutsetinfo(const UniValue& params, bool fHelp) + HelpExampleRpc("gettxoutsetinfo", "") ); - LOCK(cs_main); - UniValue ret(UniValue::VOBJ); CCoinsStats stats; diff --git a/src/txdb.cpp b/src/txdb.cpp index a7444dfa4..78edd645d 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -225,7 +225,10 @@ bool CCoinsViewDB::GetStats(CCoinsStats &stats) const { return error("%s: Deserialize or I/O error - %s", __func__, e.what()); } } - stats.nHeight = mapBlockIndex.find(GetBestBlock())->second->nHeight; + { + LOCK(cs_main); + stats.nHeight = mapBlockIndex.find(stats.hashBlock)->second->nHeight; + } stats.hashSerialized = ss.GetHash(); stats.nTotalAmount = nTotalAmount; return true; From 9c7167d1bc437e073e3011b87413412d05609bd5 Mon Sep 17 00:00:00 2001 From: dexX7 Date: Tue, 9 Jun 2015 17:11:13 +0200 Subject: [PATCH 18/43] Return all available information via validateaddress `"validateaddress"` omits some information, even in cases where is it available. The primary motivation is to be able to retrieve redeemScripts, after using `"addmultisigaddress"`, when not all keys are available in the keystore, but the redeemScript actually is. The output of `"validateaddress"` with this commit: Keys not available: ```js validateaddress "n4KWZKx349gdMQGgTnZ8W6WfgSwybkGSK3" { "isvalid": true, "address": "n4KWZKx349gdMQGgTnZ8W6WfgSwybkGSK3", "scriptPubKey": "76a914fa20d564550b105787f7ce3a9ad7fd9a45cd407088ac", "ismine": false, "iswatchonly": false, "isscript": false } ``` ```js validateaddress "2N2g2H7gjA8a11g1yKBgh5VTqndyvbnWpBK" { "isvalid": true, "address": "2N2g2H7gjA8a11g1yKBgh5VTqndyvbnWpBK", "scriptPubKey": "a9146769c19a16c9400b908756e19a4d2afb9e9760e187", "ismine": false, "iswatchonly": false, "isscript": true } ``` After adding the redeemScript: ```js addmultisigaddress 2 '["02537357B156A33306A7A014A3748631C59DF405B56F11BA4AA4A3CE81501AF095","02F1FB200390E7864EF4450C07B15988179A57C3CF3A878F668E1070CB615749FE"]' 2N2g2H7gjA8a11g1yKBgh5VTqndyvbnWpBK validateaddress "2N2g2H7gjA8a11g1yKBgh5VTqndyvbnWpBK" { "isvalid": true, "address": "2N2g2H7gjA8a11g1yKBgh5VTqndyvbnWpBK", "scriptPubKey": "a9146769c19a16c9400b908756e19a4d2afb9e9760e187", "ismine": false, "iswatchonly": false, "isscript": true, "script": "multisig", "hex": "522102537357b156a33306a7a014a3748631c59df405b56f11ba4aa4a3ce81501af0952102f1fb200390e7864ef4450c07b15988179a57c3cf3a878f668e1070cb615749fe52ae", "addresses": [ "n4KWZKx349gdMQGgTnZ8W6WfgSwybkGSK3", "mmSKNtbYYHRrhTLKiok5TuYrGEs4Y2A4k6" ], "sigsrequired": 2, "account": "" } ``` All keys available: ```js validateaddress "n4KWZKx349gdMQGgTnZ8W6WfgSwybkGSK3" { "isvalid": true, "address": "n4KWZKx349gdMQGgTnZ8W6WfgSwybkGSK3", "scriptPubKey": "76a914fa20d564550b105787f7ce3a9ad7fd9a45cd407088ac", "ismine": true, "iswatchonly": false, "isscript": false, "pubkey": "02537357b156a33306a7a014a3748631c59df405b56f11ba4aa4a3ce81501af095", "iscompressed": true, "account": "" } ``` ```js validateaddress "2N2g2H7gjA8a11g1yKBgh5VTqndyvbnWpBK" { "isvalid": true, "address": "2N2g2H7gjA8a11g1yKBgh5VTqndyvbnWpBK", "scriptPubKey": "a9146769c19a16c9400b908756e19a4d2afb9e9760e187", "ismine": true, "iswatchonly": false, "isscript": true, "script": "multisig", "hex": "522102537357b156a33306a7a014a3748631c59df405b56f11ba4aa4a3ce81501af0952102f1fb200390e7864ef4450c07b15988179a57c3cf3a878f668e1070cb615749fe52ae", "addresses": [ "n4KWZKx349gdMQGgTnZ8W6WfgSwybkGSK3", "mmSKNtbYYHRrhTLKiok5TuYrGEs4Y2A4k6" ], "sigsrequired": 2, "account": "" } ``` --- src/rpcmisc.cpp | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 38610903b..0aaf4f301 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -111,20 +111,14 @@ UniValue getinfo(const UniValue& params, bool fHelp) #ifdef ENABLE_WALLET class DescribeAddressVisitor : public boost::static_visitor { -private: - isminetype mine; - public: - DescribeAddressVisitor(isminetype mineIn) : mine(mineIn) {} - UniValue operator()(const CNoDestination &dest) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const CKeyID &keyID) const { UniValue obj(UniValue::VOBJ); CPubKey vchPubKey; obj.push_back(Pair("isscript", false)); - if (mine == ISMINE_SPENDABLE) { - pwalletMain->GetPubKey(keyID, vchPubKey); + if (pwalletMain->GetPubKey(keyID, vchPubKey)) { obj.push_back(Pair("pubkey", HexStr(vchPubKey))); obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed())); } @@ -133,10 +127,9 @@ public: UniValue operator()(const CScriptID &scriptID) const { UniValue obj(UniValue::VOBJ); + CScript subscript; obj.push_back(Pair("isscript", true)); - if (mine != ISMINE_NO) { - CScript subscript; - pwalletMain->GetCScript(scriptID, subscript); + if (pwalletMain->GetCScript(scriptID, subscript)) { std::vector addresses; txnouttype whichType; int nRequired; @@ -202,11 +195,9 @@ UniValue validateaddress(const UniValue& params, bool fHelp) #ifdef ENABLE_WALLET isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : ISMINE_NO; ret.push_back(Pair("ismine", (mine & ISMINE_SPENDABLE) ? true : false)); - if (mine != ISMINE_NO) { - ret.push_back(Pair("iswatchonly", (mine & ISMINE_WATCH_ONLY) ? true: false)); - UniValue detail = boost::apply_visitor(DescribeAddressVisitor(mine), dest); - ret.pushKVs(detail); - } + ret.push_back(Pair("iswatchonly", (mine & ISMINE_WATCH_ONLY) ? true: false)); + UniValue detail = boost::apply_visitor(DescribeAddressVisitor(), dest); + ret.pushKVs(detail); if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); #endif From 18e804a9251d64fc46af87151d3946d86f3b7b19 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 1 May 2015 15:21:06 +0200 Subject: [PATCH 19/43] Add DummySignatureCreator which just creates zeroed sigs --- src/script/sign.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/script/sign.h | 8 ++++++++ 2 files changed, 44 insertions(+) diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 3f622a056..75e6c6d9d 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -281,3 +281,39 @@ CScript CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecke return CombineSignatures(scriptPubKey, checker, txType, vSolutions, stack1, stack2); } + +namespace { +/** Dummy signature checker which accepts all signatures. */ +class DummySignatureChecker : public BaseSignatureChecker +{ +public: + DummySignatureChecker() {} + + bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode) const + { + return true; + } +}; +const DummySignatureChecker dummyChecker; +} + +const BaseSignatureChecker& DummySignatureCreator::Checker() const +{ + return dummyChecker; +} + +bool DummySignatureCreator::CreateSig(std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode) const +{ + // Create a dummy signature that is a valid DER-encoding + vchSig.assign(72, '\000'); + vchSig[0] = 0x30; + vchSig[1] = 69; + vchSig[2] = 0x02; + vchSig[3] = 33; + vchSig[4] = 0x01; + vchSig[4 + 33] = 0x02; + vchSig[5 + 33] = 32; + vchSig[6 + 33] = 0x01; + vchSig[6 + 33 + 32] = SIGHASH_ALL; + return true; +} diff --git a/src/script/sign.h b/src/script/sign.h index 0c4cf61e5..13f45007d 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -43,6 +43,14 @@ public: bool CreateSig(std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode) const; }; +/** A signature creator that just produces 72-byte empty signatyres. */ +class DummySignatureCreator : public BaseSignatureCreator { +public: + DummySignatureCreator(const CKeyStore* keystoreIn) : BaseSignatureCreator(keystoreIn) {} + const BaseSignatureChecker& Checker() const; + bool CreateSig(std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode) const; +}; + /** Produce a script signature using a generic signature creator. */ bool ProduceSignature(const BaseSignatureCreator& creator, const CScript& scriptPubKey, CScript& scriptSig); From b6616548ce65341cb4372da050ecfc8fc82b8176 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 24 Apr 2015 18:27:00 -0700 Subject: [PATCH 20/43] Small tweaks to CCoinControl for fundrawtransaction --- src/coincontrol.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/coincontrol.h b/src/coincontrol.h index 92fae9847..3e8de83c3 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -12,6 +12,8 @@ class CCoinControl { public: CTxDestination destChange; + //! If false, allows unselected inputs, but requires all selected inputs be used + bool fAllowOtherInputs; CCoinControl() { @@ -21,6 +23,7 @@ public: void SetNull() { destChange = CNoDestination(); + fAllowOtherInputs = false; setSelected.clear(); } @@ -50,7 +53,7 @@ public: setSelected.clear(); } - void ListSelected(std::vector& vOutpoints) + void ListSelected(std::vector& vOutpoints) const { vOutpoints.assign(setSelected.begin(), setSelected.end()); } From aa30f655022877012db1d6886d14eb194707639a Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 24 Apr 2015 18:29:00 -0700 Subject: [PATCH 21/43] Add FundTransaction method to wallet Some code stolen from Jonas Schnelli --- src/wallet/wallet.cpp | 125 ++++++++++++++++++++++++++++++++++++++---- src/wallet/wallet.h | 5 +- 2 files changed, 117 insertions(+), 13 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 154adca60..09072b8d6 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2219,7 +2219,7 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const isminetype mine = IsMine(pcoin->vout[i]); if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && !IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) && - (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) + (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i))) vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO)); } } @@ -2407,25 +2407,108 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set return all selected outputs (we want all selected to go into the transaction for sure) - if (coinControl && coinControl->HasSelected()) + if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) { BOOST_FOREACH(const COutput& out, vCoins) { - if(!out.fSpendable) - continue; + if (!out.fSpendable) + continue; nValueRet += out.tx->vout[out.i].nValue; setCoinsRet.insert(make_pair(out.tx, out.i)); } return (nValueRet >= nTargetValue); } - return (SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) || - SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) || - (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet))); + // calculate value from preset inputs and store them + set > setPresetCoins; + CAmount nValueFromPresetInputs = 0; + + std::vector vPresetInputs; + if (coinControl) + coinControl->ListSelected(vPresetInputs); + BOOST_FOREACH(const COutPoint& outpoint, vPresetInputs) + { + map::const_iterator it = mapWallet.find(outpoint.hash); + if (it != mapWallet.end()) + { + const CWalletTx* pcoin = &it->second; + // Clearly invalid input, fail + if (pcoin->vout.size() <= outpoint.n) + return false; + nValueFromPresetInputs += pcoin->vout[outpoint.n].nValue; + setPresetCoins.insert(make_pair(pcoin, outpoint.n)); + } else + return false; // TODO: Allow non-wallet inputs + } + + // remove preset inputs from vCoins + for (vector::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();) + { + if (setPresetCoins.count(make_pair(it->tx, it->i))) + it = vCoins.erase(it); + else + ++it; + } + + bool res = nTargetValue <= nValueFromPresetInputs || + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, vCoins, setCoinsRet, nValueRet) || + SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, vCoins, setCoinsRet, nValueRet) || + (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, vCoins, setCoinsRet, nValueRet)); + + // because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset + setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end()); + + // add preset inputs to the total value selected + nValueRet += nValueFromPresetInputs; + + return res; } -bool CWallet::CreateTransaction(const vector& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason) +{ + vector vecSend; + + // Turn the txout set into a CRecipient vector + BOOST_FOREACH(const CTxOut& txOut, tx.vout) + { + CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false}; + vecSend.push_back(recipient); + } + + CCoinControl coinControl; + coinControl.fAllowOtherInputs = true; + BOOST_FOREACH(const CTxIn& txin, tx.vin) + coinControl.Select(txin.prevout); + + CReserveKey reservekey(this); + CWalletTx wtx; + if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosRet, strFailReason, &coinControl, false)) + return false; + + if (nChangePosRet != -1) + tx.vout.insert(tx.vout.begin() + nChangePosRet, wtx.vout[nChangePosRet]); + + // Add new txins (keeping original txin scriptSig/order) + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + { + bool found = false; + BOOST_FOREACH(const CTxIn& origTxIn, tx.vin) + { + if (txin.prevout.hash == origTxIn.prevout.hash && txin.prevout.n == origTxIn.prevout.n) + { + found = true; + break; + } + } + if (!found) + tx.vin.push_back(txin); + } + + return true; +} + +bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, + int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign) { CAmount nValue = 0; unsigned int nSubtractFeeFromAmount = 0; @@ -2636,23 +2719,43 @@ bool CWallet::CreateTransaction(const vector& vecSend, // Sign int nIn = 0; + CTransaction txNewConst(txNew); BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) - if (!SignSignature(*this, *coin.first, txNew, nIn++)) + { + bool signSuccess; + const CScript& scriptPubKey = coin.first->vout[coin.second].scriptPubKey; + CScript& scriptSigRes = txNew.vin[nIn].scriptSig; + if (sign) + signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, SIGHASH_ALL), scriptPubKey, scriptSigRes); + else + signSuccess = ProduceSignature(DummySignatureCreator(this), scriptPubKey, scriptSigRes); + + if (!signSuccess) { strFailReason = _("Signing transaction failed"); return false; } + nIn++; + } + + unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION); + + // Remove scriptSigs if we used dummy signatures for fee calculation + if (!sign) { + BOOST_FOREACH (CTxIn& vin, txNew.vin) + vin.scriptSig = CScript(); + } // Embed the constructed transaction data in wtxNew. *static_cast(&wtxNew) = CTransaction(txNew); // Limit size - unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION); if (nBytes >= MAX_TX_SIZE) { strFailReason = _("Transaction too large"); return false; } + dPriority = wtxNew.ComputePriority(dPriority, nBytes); // Can we complete this as a free transaction? diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 222c8cea9..ef9319294 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -912,8 +912,9 @@ public: CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; - bool CreateTransaction(const std::vector& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); + bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason); + bool CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, + std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); static CFeeRate minTxFee; From 3d8013a01b5dd2b61431464978d218dc5cd2882d Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 24 Apr 2015 18:27:30 -0700 Subject: [PATCH 22/43] Add fundrawtransaction RPC method --- src/rpcclient.cpp | 1 + src/rpcserver.cpp | 3 ++ src/rpcserver.h | 1 + src/test/rpc_wallet_tests.cpp | 6 ++++ src/wallet/rpcwallet.cpp | 54 +++++++++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+) diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 1d0073a08..99bec7b2b 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -77,6 +77,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "signrawtransaction", 1 }, { "signrawtransaction", 2 }, { "sendrawtransaction", 1 }, + { "fundrawtransaction", 1 }, { "gettxout", 1 }, { "gettxout", 2 }, { "gettxoutproof", 0 }, diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index d3eb8217a..ed628c39d 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -332,6 +332,9 @@ static const CRPCCommand vRPCCommands[] = { "rawtransactions", "getrawtransaction", &getrawtransaction, true }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false }, { "rawtransactions", "signrawtransaction", &signrawtransaction, false }, /* uses wallet if enabled */ +#ifdef ENABLE_WALLET + { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false }, +#endif /* Utility functions */ { "util", "createmultisig", &createmultisig, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 9a090d0de..dd18ff290 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -235,6 +235,7 @@ extern UniValue listlockunspent(const UniValue& params, bool fHelp); extern UniValue createrawtransaction(const UniValue& params, bool fHelp); extern UniValue decoderawtransaction(const UniValue& params, bool fHelp); extern UniValue decodescript(const UniValue& params, bool fHelp); +extern UniValue fundrawtransaction(const UniValue& params, bool fHelp); extern UniValue signrawtransaction(const UniValue& params, bool fHelp); extern UniValue sendrawtransaction(const UniValue& params, bool fHelp); extern UniValue gettxoutproof(const UniValue& params, bool fHelp); diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index 1dd74eb0e..ea74d52f3 100644 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -248,6 +248,12 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) } BOOST_CHECK(!notFound); + /********************************* + * fundrawtransaction + *********************************/ + BOOST_CHECK_THROW(CallRPC("fundrawtransaction 28z"), runtime_error); + BOOST_CHECK_THROW(CallRPC("fundrawtransaction 01000000000180969800000000001976a91450ce0a4b0ee0ddeb633da85199728b940ac3fe9488ac00000000"), runtime_error); + /* * getblocksubsidy */ diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 830192c10..93038a8af 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2421,6 +2421,60 @@ UniValue listunspent(const UniValue& params, bool fHelp) return results; } +UniValue fundrawtransaction(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() != 1) + throw runtime_error( + "fundrawtransaction \"hexstring\"\n" + "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" + "This will not modify existing inputs, and will add one change output to the outputs.\n" + "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" + "The inputs added will not be signed, use signrawtransaction for that.\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" + "\nResult:\n" + "{\n" + " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" + " \"fee\": n, (numeric) The fee added to the transaction\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\"hex\" \n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + + "\nAdd sufficient unsigned inputs to meet the output value\n" + + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + + "\nSign the transaction\n" + + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + + "\nSend the transaction\n" + + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + ); + + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); + + // parse hex string from parameter + CTransaction origTx; + if (!DecodeHexTx(origTx, params[0].get_str())) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + + CMutableTransaction tx(origTx); + CAmount nFee; + string strFailReason; + int nChangePos = -1; + if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason)) + throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("hex", EncodeHexTx(tx))); + result.push_back(Pair("changepos", nChangePos)); + result.push_back(Pair("fee", ValueFromAmount(nFee))); + + return result; +} + UniValue zc_sample_joinsplit(const UniValue& params, bool fHelp) { if (fHelp) { From 788d8e6a6fae6c0fd865fe5b0fb6ea699712164f Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 24 Apr 2015 18:26:30 -0700 Subject: [PATCH 23/43] fundrawtransaction tests --- qa/pull-tester/rpc-tests.sh | 1 + qa/rpc-tests/fundrawtransaction.py | 556 +++++++++++++++++++++++++++++ 2 files changed, 557 insertions(+) create mode 100755 qa/rpc-tests/fundrawtransaction.py diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index d4899ecdf..58cefe915 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -30,6 +30,7 @@ testScripts=( 'zapwallettxes.py' 'proxy_test.py' 'merkle_blocks.py' + 'fundrawtransaction.py' 'signrawtransactions.py' 'walletbackup.py' 'nodehandling.py' diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py new file mode 100755 index 000000000..e859b2643 --- /dev/null +++ b/qa/rpc-tests/fundrawtransaction.py @@ -0,0 +1,556 @@ +#!/usr/bin/env python2 +# 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. + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from pprint import pprint +from time import sleep + +# Create one-input, one-output, no-fee transaction: +class RawTransactionsTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 3) + + def setup_network(self, split=False): + self.nodes = start_nodes(3, self.options.tmpdir) + + connect_nodes_bi(self.nodes,0,1) + connect_nodes_bi(self.nodes,1,2) + connect_nodes_bi(self.nodes,0,2) + + self.is_network_split=False + self.sync_all() + + def run_test(self): + print "Mining blocks..." + feeTolerance = Decimal(0.00000002) #if the fee's positive delta is higher than this value tests will fail, neg. delta always fail the tests + + self.nodes[2].generate(1) + self.nodes[0].generate(101) + self.sync_all() + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5); + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.0); + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),5.0); + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + ############### + # simple test # + ############### + inputs = [ ] + outputs = { self.nodes[0].getnewaddress() : 1.0 } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + for out in dec_tx['vout']: + totalOut += out['value'] + + assert_equal(len(dec_tx['vin']), 1) #one vin coin + assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') + assert_equal(fee + totalOut, 1.5) #the 1.5BTC coin must be taken + + ############################## + # simple test with two coins # + ############################## + inputs = [ ] + outputs = { self.nodes[0].getnewaddress() : 2.2 } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + for out in dec_tx['vout']: + totalOut += out['value'] + + assert_equal(len(dec_tx['vin']), 2) #one vin coin + assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') + assert_equal(dec_tx['vin'][1]['scriptSig']['hex'], '') + assert_equal(fee + totalOut, 2.5) #the 1.5BTC+1.0BTC coins must have be taken + + ############################## + # simple test with two coins # + ############################## + inputs = [ ] + outputs = { self.nodes[0].getnewaddress() : 2.6 } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + for out in dec_tx['vout']: + totalOut += out['value'] + + assert_equal(len(dec_tx['vin']), 1) #one vin coin + assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') + assert_equal(fee + totalOut, 5.0) #the 5.0BTC coin must have be taken + + + ################################ + # simple test with two outputs # + ################################ + inputs = [ ] + outputs = { self.nodes[0].getnewaddress() : 2.6, self.nodes[1].getnewaddress() : 2.5 } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + for out in dec_tx['vout']: + totalOut += out['value'] + + assert_equal(len(dec_tx['vin']), 2) #one vin coin + assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') + assert_equal(dec_tx['vin'][1]['scriptSig']['hex'], '') + assert_equal(fee + totalOut, 6.0) #the 5.0BTC + 1.0BTC coins must have be taken + + + + ######################################################################### + # test a fundrawtransaction with a VIN greater than the required amount # + ######################################################################### + utx = False + listunspent = self.nodes[2].listunspent() + for aUtx in listunspent: + if aUtx['amount'] == 5.0: + utx = aUtx + break; + + assert_equal(utx!=False, True) + + inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] + outputs = { self.nodes[0].getnewaddress() : 1.0 } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + for out in dec_tx['vout']: + totalOut += out['value'] + + assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee + + + + ##################################################################### + # test a fundrawtransaction with which will not get a change output # + ##################################################################### + utx = False + listunspent = self.nodes[2].listunspent() + for aUtx in listunspent: + if aUtx['amount'] == 5.0: + utx = aUtx + break; + + assert_equal(utx!=False, True) + + inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] + outputs = { self.nodes[0].getnewaddress() : Decimal(5.0) - fee - feeTolerance } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + for out in dec_tx['vout']: + totalOut += out['value'] + + assert_equal(rawtxfund['changepos'], -1) + assert_equal(fee + totalOut, utx['amount']) #compare vin total and totalout+fee + + + + ######################################################################### + # test a fundrawtransaction with a VIN smaller than the required amount # + ######################################################################### + utx = False + listunspent = self.nodes[2].listunspent() + for aUtx in listunspent: + if aUtx['amount'] == 1.0: + utx = aUtx + break; + + assert_equal(utx!=False, True) + + inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']}] + outputs = { self.nodes[0].getnewaddress() : 1.0 } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + + # 4-byte version + 1-byte vin count + 36-byte prevout then script_len + rawtx = rawtx[:82] + "0100" + rawtx[84:] + + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) + assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex']) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + matchingOuts = 0 + for i, out in enumerate(dec_tx['vout']): + totalOut += out['value'] + if outputs.has_key(out['scriptPubKey']['addresses'][0]): + matchingOuts+=1 + else: + assert_equal(i, rawtxfund['changepos']) + + assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) + assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex']) + + assert_equal(matchingOuts, 1) + assert_equal(len(dec_tx['vout']), 2) + + assert_equal(fee + totalOut, 2.5) #this tx must use the 1.0BTC and the 1.5BTC coin + + + ########################################### + # test a fundrawtransaction with two VINs # + ########################################### + utx = False + utx2 = False + listunspent = self.nodes[2].listunspent() + for aUtx in listunspent: + if aUtx['amount'] == 1.0: + utx = aUtx + if aUtx['amount'] == 5.0: + utx2 = aUtx + + + assert_equal(utx!=False, True) + + inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']},{'txid' : utx2['txid'], 'vout' : utx2['vout']} ] + outputs = { self.nodes[0].getnewaddress() : 6.0 } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + matchingOuts = 0 + for out in dec_tx['vout']: + totalOut += out['value'] + if outputs.has_key(out['scriptPubKey']['addresses'][0]): + matchingOuts+=1 + + assert_equal(matchingOuts, 1) + assert_equal(len(dec_tx['vout']), 2) + + matchingIns = 0 + for vinOut in dec_tx['vin']: + for vinIn in inputs: + if vinIn['txid'] == vinOut['txid']: + matchingIns+=1 + + assert_equal(matchingIns, 2) #we now must see two vins identical to vins given as params + assert_equal(fee + totalOut, 7.5) #this tx must use the 1.0BTC and the 1.5BTC coin + + + ######################################################### + # test a fundrawtransaction with two VINs and two vOUTs # + ######################################################### + utx = False + utx2 = False + listunspent = self.nodes[2].listunspent() + for aUtx in listunspent: + if aUtx['amount'] == 1.0: + utx = aUtx + if aUtx['amount'] == 5.0: + utx2 = aUtx + + + assert_equal(utx!=False, True) + + inputs = [ {'txid' : utx['txid'], 'vout' : utx['vout']},{'txid' : utx2['txid'], 'vout' : utx2['vout']} ] + outputs = { self.nodes[0].getnewaddress() : 6.0, self.nodes[0].getnewaddress() : 1.0 } + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + fee = rawtxfund['fee'] + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + totalOut = 0 + matchingOuts = 0 + for out in dec_tx['vout']: + totalOut += out['value'] + if outputs.has_key(out['scriptPubKey']['addresses'][0]): + matchingOuts+=1 + + assert_equal(matchingOuts, 2) + assert_equal(len(dec_tx['vout']), 3) + assert_equal(fee + totalOut, 7.5) #this tx must use the 1.0BTC and the 1.5BTC coin + + + ############################################## + # test a fundrawtransaction with invalid vin # + ############################################## + listunspent = self.nodes[2].listunspent() + inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin! + outputs = { self.nodes[0].getnewaddress() : 1.0} + rawtx = self.nodes[2].createrawtransaction(inputs, outputs) + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + + errorString = "" + try: + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + except JSONRPCException,e: + errorString = e.error['message'] + + assert_equal("Insufficient" in errorString, True); + + + + ############################################################ + #compare fee of a standard pubkeyhash transaction + inputs = [] + outputs = {self.nodes[1].getnewaddress():1.1} + rawTx = self.nodes[0].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[0].fundrawtransaction(rawTx) + + #create same transaction over sendtoaddress + txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1); + signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] + + #compare fee + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + assert(feeDelta >= 0 and feeDelta <= feeTolerance) + ############################################################ + + ############################################################ + #compare fee of a standard pubkeyhash transaction with multiple outputs + inputs = [] + outputs = {self.nodes[1].getnewaddress():1.1,self.nodes[1].getnewaddress():1.2,self.nodes[1].getnewaddress():0.1,self.nodes[1].getnewaddress():1.3,self.nodes[1].getnewaddress():0.2,self.nodes[1].getnewaddress():0.3} + rawTx = self.nodes[0].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[0].fundrawtransaction(rawTx) + #create same transaction over sendtoaddress + txId = self.nodes[0].sendmany("", outputs); + signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] + + #compare fee + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + assert(feeDelta >= 0 and feeDelta <= feeTolerance) + ############################################################ + + + ############################################################ + #compare fee of a 2of2 multisig p2sh transaction + + # create 2of2 addr + addr1 = self.nodes[1].getnewaddress() + addr2 = self.nodes[1].getnewaddress() + + addr1Obj = self.nodes[1].validateaddress(addr1) + addr2Obj = self.nodes[1].validateaddress(addr2) + + mSigObj = self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) + + inputs = [] + outputs = {mSigObj:1.1} + rawTx = self.nodes[0].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[0].fundrawtransaction(rawTx) + + #create same transaction over sendtoaddress + txId = self.nodes[0].sendtoaddress(mSigObj, 1.1); + signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] + + #compare fee + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + assert(feeDelta >= 0 and feeDelta <= feeTolerance) + ############################################################ + + + ############################################################ + #compare fee of a standard pubkeyhash transaction + + # create 4of5 addr + addr1 = self.nodes[1].getnewaddress() + addr2 = self.nodes[1].getnewaddress() + addr3 = self.nodes[1].getnewaddress() + addr4 = self.nodes[1].getnewaddress() + addr5 = self.nodes[1].getnewaddress() + + addr1Obj = self.nodes[1].validateaddress(addr1) + addr2Obj = self.nodes[1].validateaddress(addr2) + addr3Obj = self.nodes[1].validateaddress(addr3) + addr4Obj = self.nodes[1].validateaddress(addr4) + addr5Obj = self.nodes[1].validateaddress(addr5) + + mSigObj = self.nodes[1].addmultisigaddress(4, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey'], addr4Obj['pubkey'], addr5Obj['pubkey']]) + + inputs = [] + outputs = {mSigObj:1.1} + rawTx = self.nodes[0].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[0].fundrawtransaction(rawTx) + + #create same transaction over sendtoaddress + txId = self.nodes[0].sendtoaddress(mSigObj, 1.1); + signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] + + #compare fee + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + assert(feeDelta >= 0 and feeDelta <= feeTolerance) + ############################################################ + + + ############################################################ + # spend a 2of2 multisig transaction over fundraw + + # create 2of2 addr + addr1 = self.nodes[2].getnewaddress() + addr2 = self.nodes[2].getnewaddress() + + addr1Obj = self.nodes[2].validateaddress(addr1) + addr2Obj = self.nodes[2].validateaddress(addr2) + + mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) + + + # send 1.2 BTC to msig addr + txId = self.nodes[0].sendtoaddress(mSigObj, 1.2); + self.sync_all() + self.nodes[1].generate(1) + self.sync_all() + + oldBalance = self.nodes[1].getbalance() + inputs = [] + outputs = {self.nodes[1].getnewaddress():1.1} + rawTx = self.nodes[2].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[2].fundrawtransaction(rawTx) + + signedTx = self.nodes[2].signrawtransaction(fundedTx['hex']) + txId = self.nodes[2].sendrawtransaction(signedTx['hex']) + self.sync_all() + self.nodes[1].generate(1) + self.sync_all() + + # make sure funds are received at node1 + assert_equal(oldBalance+Decimal('1.10000000'), self.nodes[1].getbalance()) + + ############################################################ + # locked wallet test + self.nodes[1].encryptwallet("test") + self.nodes.pop(1) + stop_nodes(self.nodes) + wait_bitcoinds() + + self.nodes = start_nodes(3, self.options.tmpdir) + + connect_nodes_bi(self.nodes,0,1) + connect_nodes_bi(self.nodes,1,2) + connect_nodes_bi(self.nodes,0,2) + self.is_network_split=False + self.sync_all() + + error = False + try: + self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.2); + except: + error = True + assert(error) + + oldBalance = self.nodes[0].getbalance() + + inputs = [] + outputs = {self.nodes[0].getnewaddress():1.1} + rawTx = self.nodes[1].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[1].fundrawtransaction(rawTx) + + #now we need to unlock + self.nodes[1].walletpassphrase("test", 100) + signedTx = self.nodes[1].signrawtransaction(fundedTx['hex']) + txId = self.nodes[1].sendrawtransaction(signedTx['hex']) + self.sync_all() + self.nodes[1].generate(1) + self.sync_all() + + # make sure funds are received at node1 + assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance()) + + + + ############################################### + # multiple (~19) inputs tx test | Compare fee # + ############################################### + + #empty node1, send some small coins from node0 to node1 + self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True); + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + for i in range(0,20): + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01); + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + #fund a tx with ~20 small inputs + inputs = [] + outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04} + rawTx = self.nodes[1].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[1].fundrawtransaction(rawTx) + + #create same transaction over sendtoaddress + txId = self.nodes[1].sendmany("", outputs); + signedFee = self.nodes[1].getrawmempool(True)[txId]['fee'] + + #compare fee + feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee); + assert(feeDelta >= 0 and feeDelta <= feeTolerance*19) #~19 inputs + + + ############################################# + # multiple (~19) inputs tx test | sign/send # + ############################################# + + #again, empty node1, send some small coins from node0 to node1 + self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True); + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + for i in range(0,20): + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01); + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + #fund a tx with ~20 small inputs + oldBalance = self.nodes[0].getbalance() + + inputs = [] + outputs = {self.nodes[0].getnewaddress():0.15,self.nodes[0].getnewaddress():0.04} + rawTx = self.nodes[1].createrawtransaction(inputs, outputs) + fundedTx = self.nodes[1].fundrawtransaction(rawTx) + fundedAndSignedTx = self.nodes[1].signrawtransaction(fundedTx['hex']) + txId = self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex']) + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward + + +if __name__ == '__main__': + RawTransactionsTest().main() From 5548701eebf1b74827fd440ee78555ad816d25cd Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Wed, 24 Jun 2015 22:31:10 +0200 Subject: [PATCH 24/43] UniValue: don't escape solidus, keep espacing of reverse solidus Zcash: The UniValue changes are already merged; this updates the test --- src/test/univalue_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/univalue_tests.cpp b/src/test/univalue_tests.cpp index 485ecaed7..f3bbfaef1 100644 --- a/src/test/univalue_tests.cpp +++ b/src/test/univalue_tests.cpp @@ -286,7 +286,7 @@ BOOST_AUTO_TEST_CASE(univalue_object) } static const char *json1 = -"[1.10000000,{\"key1\":\"str\\u0000\",\"key2\":800,\"key3\":{\"name\":\"martian\"}}]"; +"[1.10000000,{\"key1\":\"str\\u0000\",\"key2\":800,\"key3\":{\"name\":\"martian http://test.com\"}}]"; BOOST_AUTO_TEST_CASE(univalue_readwrite) { From bf890b651816b77f52dd56983441bf67d132cb18 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Wed, 17 Jun 2015 18:06:39 +0200 Subject: [PATCH 25/43] test: Move reindex test to standard tests This test finishes very quickly, so it should be part of the default set of tests in rpc-tests. --- qa/pull-tester/rpc-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 58cefe915..7a7b8c0f2 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -34,6 +34,7 @@ testScripts=( 'signrawtransactions.py' 'walletbackup.py' 'nodehandling.py' + 'reindex.py' 'zcjoinsplit.py' 'zcjoinsplitdoublespend.py' 'getblocktemplate.py' @@ -49,7 +50,6 @@ testScriptsExt=( 'invalidateblock.py' 'keypool.py' 'receivedby.py' - 'reindex.py' 'rpcbind_test.py' # 'script_test.py' 'smartfees.py' From 81ae51c8834d2544d69d39448fdd0df221a1d4ee Mon Sep 17 00:00:00 2001 From: mruddy Date: Tue, 30 Jun 2015 09:40:44 -0400 Subject: [PATCH 26/43] add tests for the decodescript rpc. add mention of the rpc regression tests to the testing seciton of the main readme. --- qa/pull-tester/rpc-tests.sh | 1 + qa/rpc-tests/decodescript.py | 116 +++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100755 qa/rpc-tests/decodescript.py diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 7a7b8c0f2..790c6e3f7 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -35,6 +35,7 @@ testScripts=( 'walletbackup.py' 'nodehandling.py' 'reindex.py' + 'decodescript.py' 'zcjoinsplit.py' 'zcjoinsplitdoublespend.py' 'getblocktemplate.py' diff --git a/qa/rpc-tests/decodescript.py b/qa/rpc-tests/decodescript.py new file mode 100755 index 000000000..ce3bc94ef --- /dev/null +++ b/qa/rpc-tests/decodescript.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python2 +# Copyright (c) 2015 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 * + +class DecodeScriptTest(BitcoinTestFramework): + """Tests decoding scripts via RPC command "decodescript".""" + + def setup_chain(self): + print('Initializing test directory ' + self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 1) + + def setup_network(self, split=False): + self.nodes = start_nodes(1, self.options.tmpdir) + self.is_network_split = False + + def decodescript_script_sig(self): + signature = '304502207fa7a6d1e0ee81132a269ad84e68d695483745cde8b541e3bf630749894e342a022100c1f7ab20e13e22fb95281a870f3dcf38d782e53023ee313d741ad0cfbc0c509001' + push_signature = '48' + signature + public_key = '03b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb2' + push_public_key = '21' + public_key + + # below are test cases for all of the standard transaction types + + # 1) P2PK scriptSig + # the scriptSig of a public key scriptPubKey simply pushes a signature onto the stack + rpc_result = self.nodes[0].decodescript(push_signature) + assert_equal(signature, rpc_result['asm']) + + # 2) P2PKH scriptSig + rpc_result = self.nodes[0].decodescript(push_signature + push_public_key) + assert_equal(signature + ' ' + public_key, rpc_result['asm']) + + # 3) multisig scriptSig + # this also tests the leading portion of a P2SH multisig scriptSig + # OP_0 + rpc_result = self.nodes[0].decodescript('00' + push_signature + push_signature) + assert_equal('0 ' + signature + ' ' + signature, rpc_result['asm']) + + # 4) P2SH scriptSig + # an empty P2SH redeemScript is valid and makes for a very simple test case. + # thus, such a spending scriptSig would just need to pass the outer redeemScript + # hash test and leave true on the top of the stack. + rpc_result = self.nodes[0].decodescript('5100') + assert_equal('1 0', rpc_result['asm']) + + # 5) null data scriptSig - no such thing because null data scripts can not be spent. + # thus, no test case for that standard transaction type is here. + + def decodescript_script_pub_key(self): + public_key = '03b0da749730dc9b4b1f4a14d6902877a92541f5368778853d9c4a0cb7802dcfb2' + push_public_key = '21' + public_key + public_key_hash = '11695b6cd891484c2d49ec5aa738ec2b2f897777' + push_public_key_hash = '14' + public_key_hash + + # below are test cases for all of the standard transaction types + + # 1) P2PK scriptPubKey + # OP_CHECKSIG + rpc_result = self.nodes[0].decodescript(push_public_key + 'ac') + assert_equal(public_key + ' OP_CHECKSIG', rpc_result['asm']) + + # 2) P2PKH scriptPubKey + # OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG + rpc_result = self.nodes[0].decodescript('76a9' + push_public_key_hash + '88ac') + assert_equal('OP_DUP OP_HASH160 ' + public_key_hash + ' OP_EQUALVERIFY OP_CHECKSIG', rpc_result['asm']) + + # 3) multisig scriptPubKey + # OP_CHECKMULTISIG + # just imagine that the pub keys used below are different. + # for our purposes here it does not matter that they are the same even though it is unrealistic. + rpc_result = self.nodes[0].decodescript('52' + push_public_key + push_public_key + push_public_key + '53ae') + assert_equal('2 ' + public_key + ' ' + public_key + ' ' + public_key + ' 3 OP_CHECKMULTISIG', rpc_result['asm']) + + # 4) P2SH scriptPubKey + # OP_HASH160 OP_EQUAL. + # push_public_key_hash here should actually be the hash of a redeem script. + # but this works the same for purposes of this test. + rpc_result = self.nodes[0].decodescript('a9' + push_public_key_hash + '87') + assert_equal('OP_HASH160 ' + public_key_hash + ' OP_EQUAL', rpc_result['asm']) + + # 5) null data scriptPubKey + # use a signature look-alike here to make sure that we do not decode random data as a signature. + # this matters if/when signature sighash decoding comes along. + # would want to make sure that no such decoding takes place in this case. + signature_imposter = '48304502207fa7a6d1e0ee81132a269ad84e68d695483745cde8b541e3bf630749894e342a022100c1f7ab20e13e22fb95281a870f3dcf38d782e53023ee313d741ad0cfbc0c509001' + # OP_RETURN + rpc_result = self.nodes[0].decodescript('6a' + signature_imposter) + assert_equal('OP_RETURN ' + signature_imposter[2:], rpc_result['asm']) + + # 6) a CLTV redeem script. redeem scripts are in-effect scriptPubKey scripts, so adding a test here. + # OP_NOP2 is also known as OP_CHECKLOCKTIMEVERIFY. + # just imagine that the pub keys used below are different. + # for our purposes here it does not matter that they are the same even though it is unrealistic. + # + # OP_IF + # OP_CHECKSIGVERIFY + # OP_ELSE + # OP_NOP2 OP_DROP + # OP_ENDIF + # OP_CHECKSIG + # + # lock until block 500,000 + rpc_result = self.nodes[0].decodescript('63' + push_public_key + 'ad670320a107b17568' + push_public_key + 'ac') + assert_equal('OP_IF ' + public_key + ' OP_CHECKSIGVERIFY OP_ELSE 500000 OP_NOP2 OP_DROP OP_ENDIF ' + public_key + ' OP_CHECKSIG', rpc_result['asm']) + + def run_test(self): + self.decodescript_script_sig() + self.decodescript_script_pub_key() + +if __name__ == '__main__': + DecodeScriptTest().main() + From d3d5483eef5102070d7a1f1206bc21349a15ec71 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Fri, 5 Jun 2015 17:07:17 -0230 Subject: [PATCH 27/43] Add getblockheader RPC call Alternative to getblock that works even when the block itself has been pruned, returning all available information. --- src/rpcblockchain.cpp | 82 +++++++++++++++++++++++++++++++++++++++++++ src/rpcclient.cpp | 1 + src/rpcserver.cpp | 1 + src/rpcserver.h | 1 + 4 files changed, 85 insertions(+) diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 64b71797f..7b2b49784 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -72,6 +72,32 @@ double GetNetworkDifficulty(const CBlockIndex* blockindex) return GetDifficultyINTERNAL(blockindex, true); } +UniValue blockheaderToJSON(const CBlockIndex* blockindex) +{ + UniValue result(UniValue::VOBJ); + result.push_back(Pair("hash", blockindex->GetBlockHash().GetHex())); + int confirmations = -1; + // Only report confirmations if the block is on the main chain + if (chainActive.Contains(blockindex)) + confirmations = chainActive.Height() - blockindex->nHeight + 1; + result.push_back(Pair("confirmations", confirmations)); + result.push_back(Pair("height", blockindex->nHeight)); + result.push_back(Pair("version", blockindex->nVersion)); + result.push_back(Pair("merkleroot", blockindex->hashMerkleRoot.GetHex())); + result.push_back(Pair("time", (int64_t)blockindex->nTime)); + result.push_back(Pair("nonce", (uint64_t)blockindex->nNonce)); + result.push_back(Pair("bits", strprintf("%08x", blockindex->nBits))); + result.push_back(Pair("difficulty", GetDifficulty(blockindex))); + result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex())); + + if (blockindex->pprev) + result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); + CBlockIndex *pnext = chainActive.Next(blockindex); + if (pnext) + result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); + return result; +} + UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false) { @@ -277,6 +303,62 @@ UniValue getblockhash(const UniValue& params, bool fHelp) return pblockindex->GetBlockHash().GetHex(); } +UniValue getblockheader(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "getblockheader \"hash\" ( verbose )\n" + "\nIf verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'.\n" + "If verbose is true, returns an Object with information about blockheader .\n" + "\nArguments:\n" + "1. \"hash\" (string, required) The block hash\n" + "2. verbose (boolean, optional, default=true) true for a json object, false for the hex encoded data\n" + "\nResult (for verbose = true):\n" + "{\n" + " \"hash\" : \"hash\", (string) the block hash (same as provided)\n" + " \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n" + " \"height\" : n, (numeric) The block height or index\n" + " \"version\" : n, (numeric) The block version\n" + " \"merkleroot\" : \"xxxx\", (string) The merkle root\n" + " \"time\" : ttt, (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"nonce\" : n, (numeric) The nonce\n" + " \"bits\" : \"1d00ffff\", (string) The bits\n" + " \"difficulty\" : x.xxx, (numeric) The difficulty\n" + " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" + " \"nextblockhash\" : \"hash\" (string) The hash of the next block\n" + "}\n" + "\nResult (for verbose=false):\n" + "\"data\" (string) A string that is serialized, hex-encoded data for block 'hash'.\n" + "\nExamples:\n" + + HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + ); + + LOCK(cs_main); + + std::string strHash = params[0].get_str(); + uint256 hash(uint256S(strHash)); + + bool fVerbose = true; + if (params.size() > 1) + fVerbose = params[1].get_bool(); + + if (mapBlockIndex.count(hash) == 0) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + + CBlockIndex* pblockindex = mapBlockIndex[hash]; + + if (!fVerbose) + { + CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); + ssBlock << pblockindex->GetBlockHeader(); + std::string strHex = HexStr(ssBlock.begin(), ssBlock.end()); + return strHex; + } + + return blockheaderToJSON(pblockindex); +} + UniValue getblock(const UniValue& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 99bec7b2b..f173afe2c 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -70,6 +70,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "listunspent", 1 }, { "listunspent", 2 }, { "getblock", 1 }, + { "getblockheader", 1 }, { "gettransaction", 1 }, { "getrawtransaction", 1 }, { "createrawtransaction", 0 }, diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index ed628c39d..b0d5ad7c7 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -296,6 +296,7 @@ static const CRPCCommand vRPCCommands[] = { "blockchain", "getblockcount", &getblockcount, true }, { "blockchain", "getblock", &getblock, true }, { "blockchain", "getblockhash", &getblockhash, true }, + { "blockchain", "getblockheader", &getblockheader, true }, { "blockchain", "getchaintips", &getchaintips, true }, { "blockchain", "getdifficulty", &getdifficulty, true }, { "blockchain", "getmempoolinfo", &getmempoolinfo, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index dd18ff290..b1b389992 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -248,6 +248,7 @@ extern UniValue settxfee(const UniValue& params, bool fHelp); extern UniValue getmempoolinfo(const UniValue& params, bool fHelp); extern UniValue getrawmempool(const UniValue& params, bool fHelp); extern UniValue getblockhash(const UniValue& params, bool fHelp); +extern UniValue getblockheader(const UniValue& params, bool fHelp); extern UniValue getblock(const UniValue& params, bool fHelp); extern UniValue gettxoutsetinfo(const UniValue& params, bool fHelp); extern UniValue gettxout(const UniValue& params, bool fHelp); From 0352b37bffe4076887895876c32934d5d6bde8be Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 15 Feb 2017 14:54:58 +0000 Subject: [PATCH 28/43] Adjust blockheaderToJSON() for Zcash block header --- src/rpcblockchain.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 7b2b49784..26f9ff88d 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -85,7 +85,8 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex) result.push_back(Pair("version", blockindex->nVersion)); result.push_back(Pair("merkleroot", blockindex->hashMerkleRoot.GetHex())); result.push_back(Pair("time", (int64_t)blockindex->nTime)); - result.push_back(Pair("nonce", (uint64_t)blockindex->nNonce)); + result.push_back(Pair("nonce", blockindex->nNonce.GetHex())); + result.push_back(Pair("solution", HexStr(blockindex->nSolution))); result.push_back(Pair("bits", strprintf("%08x", blockindex->nBits))); result.push_back(Pair("difficulty", GetDifficulty(blockindex))); result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex())); From 483672f77207a25f362274a5d63b3ca45442241e Mon Sep 17 00:00:00 2001 From: Forrest Voight Date: Wed, 1 Jul 2015 21:34:31 -0400 Subject: [PATCH 29/43] When processing RPC commands during warmup phase, parse the request object before returning an error so that id value can be used in the response. Prior to this commit, RPC commands sent during Bitcoin's warmup/startup phase were responded to with a JSON-RPC error with an id of null, which violated the JSON-RPC 2.0 spec: id: This member is REQUIRED. It MUST be the same as the value of the id member in the Request Object. If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), it MUST be Null. --- src/rpcserver.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index b0d5ad7c7..d10e14302 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -975,13 +975,6 @@ static bool HTTPReq_JSONRPC(AcceptedConnection *conn, if (!valRequest.read(strRequest)) throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); - // Return immediately if in warmup - { - LOCK(cs_rpcWarmup); - if (fRPCInWarmup) - throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); - } - string strReply; // singleton request @@ -1059,6 +1052,13 @@ void ServiceConnection(AcceptedConnection *conn) UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms) const { + // Return immediately if in warmup + { + LOCK(cs_rpcWarmup); + if (fRPCInWarmup) + throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); + } + // Find method const CRPCCommand *pcmd = tableRPC[strMethod]; if (!pcmd) From 81eb80d841549e2a8fbc822b40d14dffe0fcf93c Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Tue, 16 Dec 2014 10:50:44 +0100 Subject: [PATCH 30/43] [REST] add JSON support for /rest/headers/ --- qa/rpc-tests/rest.py | 39 +++++++++++++++++++++++++++++++++++---- src/rest.cpp | 19 +++++++++++++++---- src/rpcblockchain.cpp | 2 -- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/qa/rpc-tests/rest.py b/qa/rpc-tests/rest.py index 42da14ee2..eecbe0a3b 100755 --- a/qa/rpc-tests/rest.py +++ b/qa/rpc-tests/rest.py @@ -244,12 +244,43 @@ class RESTTest (BitcoinTestFramework): assert_equal(response_header_str.encode("hex")[0:354], response_header_hex_str[0:354]) # check json format - json_string = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+'json') - json_obj = json.loads(json_string) - assert_equal(json_obj['hash'], bb_hash) + block_json_string = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+'json') + block_json_obj = json.loads(block_json_string) + assert_equal(block_json_obj['hash'], bb_hash) + + # compare with json block header + response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"json", "", True) + assert_equal(response_header_json.status, 200) + response_header_json_str = response_header_json.read() + json_obj = json.loads(response_header_json_str) + assert_equal(len(json_obj), 1) #ensure that there is one header in the json response + assert_equal(json_obj[0]['hash'], bb_hash) #request/response hash should be the same + + #compare with normal RPC block response + rpc_block_json = self.nodes[0].getblock(bb_hash) + assert_equal(json_obj[0]['hash'], rpc_block_json['hash']) + assert_equal(json_obj[0]['confirmations'], rpc_block_json['confirmations']) + assert_equal(json_obj[0]['height'], rpc_block_json['height']) + assert_equal(json_obj[0]['version'], rpc_block_json['version']) + assert_equal(json_obj[0]['merkleroot'], rpc_block_json['merkleroot']) + assert_equal(json_obj[0]['time'], rpc_block_json['time']) + assert_equal(json_obj[0]['nonce'], rpc_block_json['nonce']) + assert_equal(json_obj[0]['bits'], rpc_block_json['bits']) + assert_equal(json_obj[0]['difficulty'], rpc_block_json['difficulty']) + assert_equal(json_obj[0]['chainwork'], rpc_block_json['chainwork']) + assert_equal(json_obj[0]['previousblockhash'], rpc_block_json['previousblockhash']) + + #see if we can get 5 headers in one response + self.nodes[1].generate(5) + self.sync_all() + response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/5/'+bb_hash+self.FORMAT_SEPARATOR+"json", "", True) + assert_equal(response_header_json.status, 200) + response_header_json_str = response_header_json.read() + json_obj = json.loads(response_header_json_str) + assert_equal(len(json_obj), 5) #now we should have 5 header objects # do tx test - tx_hash = json_obj['tx'][0]['txid']; + tx_hash = block_json_obj['tx'][0]['txid']; json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"json") json_obj = json.loads(json_string) assert_equal(json_obj['txid'], tx_hash) diff --git a/src/rest.cpp b/src/rest.cpp index 82155bab1..fe76a36b6 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -65,6 +65,7 @@ public: extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); extern UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false); extern void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); +extern UniValue blockheaderToJSON(const CBlockIndex* blockindex); static RestErr RESTERR(enum HTTPStatusCode status, string message) { @@ -134,14 +135,14 @@ static bool rest_headers(AcceptedConnection* conn, if (!ParseHashStr(hashStr, hash)) throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); - std::vector headers; + std::vector headers; headers.reserve(count); { LOCK(cs_main); BlockMap::const_iterator it = mapBlockIndex.find(hash); const CBlockIndex *pindex = (it != mapBlockIndex.end()) ? it->second : NULL; while (pindex != NULL && chainActive.Contains(pindex)) { - headers.push_back(pindex->GetBlockHeader()); + headers.push_back(pindex); if (headers.size() == (unsigned long)count) break; pindex = chainActive.Next(pindex); @@ -149,8 +150,8 @@ static bool rest_headers(AcceptedConnection* conn, } CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION); - BOOST_FOREACH(const CBlockHeader &header, headers) { - ssHeader << header; + BOOST_FOREACH(const CBlockIndex *pindex, headers) { + ssHeader << pindex->GetBlockHeader(); } switch (rf) { @@ -166,6 +167,16 @@ static bool rest_headers(AcceptedConnection* conn, return true; } + case RF_JSON: { + UniValue jsonHeaders(UniValue::VARR); + BOOST_FOREACH(const CBlockIndex *pindex, headers) { + jsonHeaders.push_back(blockheaderToJSON(pindex)); + } + string strJSON = jsonHeaders.write() + "\n"; + conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + return true; + } + default: { throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)"); } diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 26f9ff88d..24bff3f48 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -99,7 +99,6 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex) return result; } - UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false) { UniValue result(UniValue::VOBJ); @@ -141,7 +140,6 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx return result; } - UniValue getblockcount(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 0) From 897a370c393439c4876b11b4571901ca18c7608b Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 10 Jul 2015 20:11:44 +0200 Subject: [PATCH 31/43] [QA] fix possible reorg issue in rawtransaction.py/fundrawtransaction.py RPC test - added missing mempool sync between block generations --- qa/rpc-tests/fundrawtransaction.py | 37 ++++-------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index e859b2643..3431771bb 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -30,6 +30,7 @@ class RawTransactionsTest(BitcoinTestFramework): feeTolerance = Decimal(0.00000002) #if the fee's positive delta is higher than this value tests will fail, neg. delta always fail the tests self.nodes[2].generate(1) + self.sync_all() self.nodes[0].generate(101) self.sync_all() self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(),1.5); @@ -46,17 +47,10 @@ class RawTransactionsTest(BitcoinTestFramework): outputs = { self.nodes[0].getnewaddress() : 1.0 } rawtx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawtx) - rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - totalOut = 0 - for out in dec_tx['vout']: - totalOut += out['value'] - - assert_equal(len(dec_tx['vin']), 1) #one vin coin - assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') - assert_equal(fee + totalOut, 1.5) #the 1.5BTC coin must be taken + assert_equal(len(dec_tx['vin']) > 0, True) #test if we have enought inputs ############################## # simple test with two coins # @@ -69,14 +63,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - totalOut = 0 - for out in dec_tx['vout']: - totalOut += out['value'] - - assert_equal(len(dec_tx['vin']), 2) #one vin coin - assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') - assert_equal(dec_tx['vin'][1]['scriptSig']['hex'], '') - assert_equal(fee + totalOut, 2.5) #the 1.5BTC+1.0BTC coins must have be taken + assert_equal(len(dec_tx['vin']) > 0, True) #test if we have enought inputs ############################## # simple test with two coins # @@ -89,13 +76,8 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - totalOut = 0 - for out in dec_tx['vout']: - totalOut += out['value'] - - assert_equal(len(dec_tx['vin']), 1) #one vin coin + assert_equal(len(dec_tx['vin']) > 0, True) assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') - assert_equal(fee + totalOut, 5.0) #the 5.0BTC coin must have be taken ################################ @@ -113,11 +95,8 @@ class RawTransactionsTest(BitcoinTestFramework): for out in dec_tx['vout']: totalOut += out['value'] - assert_equal(len(dec_tx['vin']), 2) #one vin coin + assert_equal(len(dec_tx['vin']) > 0, True) assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') - assert_equal(dec_tx['vin'][1]['scriptSig']['hex'], '') - assert_equal(fee + totalOut, 6.0) #the 5.0BTC + 1.0BTC coins must have be taken - ######################################################################### @@ -220,8 +199,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingOuts, 1) assert_equal(len(dec_tx['vout']), 2) - assert_equal(fee + totalOut, 2.5) #this tx must use the 1.0BTC and the 1.5BTC coin - ########################################### # test a fundrawtransaction with two VINs # @@ -264,8 +241,6 @@ class RawTransactionsTest(BitcoinTestFramework): matchingIns+=1 assert_equal(matchingIns, 2) #we now must see two vins identical to vins given as params - assert_equal(fee + totalOut, 7.5) #this tx must use the 1.0BTC and the 1.5BTC coin - ######################################################### # test a fundrawtransaction with two VINs and two vOUTs # @@ -300,8 +275,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert_equal(matchingOuts, 2) assert_equal(len(dec_tx['vout']), 3) - assert_equal(fee + totalOut, 7.5) #this tx must use the 1.0BTC and the 1.5BTC coin - ############################################## # test a fundrawtransaction with invalid vin # From 5bfd954fc66ace4a9591a5153c0110f04b182735 Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Fri, 10 Jul 2015 21:14:33 +0200 Subject: [PATCH 32/43] [QA] remove rawtransactions.py from the extended test list rawtransactions.py is already in the standard test list --- qa/pull-tester/rpc-tests.sh | 1 - qa/rpc-tests/fundrawtransaction.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index 790c6e3f7..e2cba1796 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -56,7 +56,6 @@ testScriptsExt=( 'smartfees.py' 'maxblocksinflight.py' 'invalidblockrequest.py' - 'rawtransactions.py' # 'forknotify.py' 'p2p-acceptblock.py' ); diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index 3431771bb..80f1d1e12 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -63,7 +63,7 @@ class RawTransactionsTest(BitcoinTestFramework): rawtxfund = self.nodes[2].fundrawtransaction(rawtx) fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) - assert_equal(len(dec_tx['vin']) > 0, True) #test if we have enought inputs + assert_equal(len(dec_tx['vin']) > 0, True) #test if we have enough inputs ############################## # simple test with two coins # From ce83df8fc4c3d1ff4289f72f8ad885fb59b45500 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Wed, 8 Jul 2015 14:47:39 +0200 Subject: [PATCH 33/43] rpc: Remove chain-specific RequireRPCPassword I've never liked the chain-specific exception to having to set a password. It gives issues with #6388 which makes it valid to set no password in every case (as it enables random cookie authentication). This pull removes the flag, so that all chains are regarded the same. It also removes the username==password test, which doesn't provide any substantial extra security. --- src/chainparams.cpp | 3 --- src/chainparams.h | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 697f6f88e..ce71a3aaa 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -117,7 +117,6 @@ public: vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); - fRequireRPCPassword = true; fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; fRequireStandard = true; @@ -251,7 +250,6 @@ public: vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); - fRequireRPCPassword = true; fMiningRequiresPeers = true; fDefaultConsistencyChecks = false; fRequireStandard = true; @@ -328,7 +326,6 @@ public: vFixedSeeds.clear(); //! Regtest mode doesn't have any fixed seeds. vSeeds.clear(); //! Regtest mode doesn't have any DNS seeds. - fRequireRPCPassword = false; fMiningRequiresPeers = false; fDefaultConsistencyChecks = true; fRequireStandard = false; diff --git a/src/chainparams.h b/src/chainparams.h index 0af9920de..dc7cbc383 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -56,7 +56,6 @@ public: /** Used if GenerateBitcoins is called with a negative number of threads */ int DefaultMinerThreads() const { return nMinerThreads; } const CBlock& GenesisBlock() const { return genesis; } - bool RequireRPCPassword() const { return fRequireRPCPassword; } /** Make miner wait to have peers to avoid wasting work */ bool MiningRequiresPeers() const { return fMiningRequiresPeers; } /** Default value for -checkmempool and -checkblockindex argument */ @@ -103,7 +102,6 @@ protected: std::string strCurrencyUnits; CBlock genesis; std::vector vFixedSeeds; - bool fRequireRPCPassword = false; bool fMiningRequiresPeers = false; bool fDefaultConsistencyChecks = false; bool fRequireStandard = false; From a11c4bbd261bbc609ff95b4ad7bc7e7ec1dddcd9 Mon Sep 17 00:00:00 2001 From: zathras-crypto Date: Wed, 25 Mar 2015 02:04:02 -0700 Subject: [PATCH 34/43] Exempt unspendable transaction outputs from dust checks Since unspendable outputs can't be spent, there is no threshold at which it would be uneconomic to spend them. This primarily targets transaction outputs with `OP_RETURN`. --- Initially based on: commit 9cf0ae26350033d43d5dd3c95054c0d1b1641eda Author: zathras-crypto Date: Wed Mar 25 02:04:02 2015 -0700 Changes: - cherry-picked on top of bitcoin:master - added RPC test for fundrawtransaction --- qa/rpc-tests/fundrawtransaction.py | 16 ++++++++++++++++ src/primitives/transaction.h | 7 +++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index 80f1d1e12..ce52247b2 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -524,6 +524,22 @@ class RawTransactionsTest(BitcoinTestFramework): self.sync_all() assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward + ##################################################### + # test fundrawtransaction with OP_RETURN and no vin # + ##################################################### + + rawtx = "0100000000010000000000000000066a047465737400000000" + dec_tx = self.nodes[2].decoderawtransaction(rawtx) + + assert_equal(len(dec_tx['vin']), 0) + assert_equal(len(dec_tx['vout']), 1) + + rawtxfund = self.nodes[2].fundrawtransaction(rawtx) + dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) + + assert_greater_than(len(dec_tx['vin']), 0) # at least one vin + assert_equal(len(dec_tx['vout']), 2) # one change output added + if __name__ == '__main__': RawTransactionsTest().main() diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 06a5e80ca..111237cb7 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -272,10 +272,13 @@ public: // which has units satoshis-per-kilobyte. // If you'd pay more than 1/3 in fees // to spend something, then we consider it dust. - // A typical txout is 34 bytes big, and will + // A typical spendable txout is 34 bytes big, and will // need a CTxIn of at least 148 bytes to spend: - // so dust is a txout less than 54 satoshis + // so dust is a spendable txout less than 54 satoshis // with default minRelayTxFee. + if (scriptPubKey.IsUnspendable()) + return 0; + size_t nSize = GetSerializeSize(SER_DISK,0)+148u; return 3*minRelayTxFee.GetFee(nSize); } From d101d7b8e68d7949b1dbc6286074e1fb0dee82de Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Sat, 18 Jul 2015 07:44:19 +0200 Subject: [PATCH 35/43] univalue: Avoid unnecessary roundtrip through double for numbers JSON makes no distinction between numbers and reals, and our code doesn't need to do so either. This removes VREAL, as well as its specific post-processing in `UniValue::write`. Non-monetary amounts do not need to be forcibly formatted with 8 decimals, so the extra roundtrip was unnecessary (and potentially loses precision). Zcash: cherry-picked from commit 7650449a6777710cf818d41862626164da0cd412 Left over from 2aee4619304adb9cecf4a9a4b04d83e0fc4d9d8d --- qa/rpc-tests/rest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qa/rpc-tests/rest.py b/qa/rpc-tests/rest.py index eecbe0a3b..0c67ceacc 100755 --- a/qa/rpc-tests/rest.py +++ b/qa/rpc-tests/rest.py @@ -14,6 +14,7 @@ from struct import * import binascii import json import StringIO +import decimal try: import http.client as httplib @@ -252,7 +253,7 @@ class RESTTest (BitcoinTestFramework): response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"json", "", True) assert_equal(response_header_json.status, 200) response_header_json_str = response_header_json.read() - json_obj = json.loads(response_header_json_str) + json_obj = json.loads(response_header_json_str, parse_float=decimal.Decimal) assert_equal(len(json_obj), 1) #ensure that there is one header in the json response assert_equal(json_obj[0]['hash'], bb_hash) #request/response hash should be the same From 84d1d5fd6f8f76fe52421ce97e581cac9f66765a Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Mon, 6 Jul 2015 11:43:56 +0200 Subject: [PATCH 36/43] rpc: Accept strings in AmountFromValue Accept strings containing decimal values, in addition to bare values. Useful from JSON-RPC implementations where it's not possible to have direct control over the text of numbers (e.g. where numbers are always doubles), and it's still desired to send an exact value. This would allow users to post JSON content with numbers encoded like `{"value": "0.00000001"}` instead of `{"value": 0.00000001}` which some php/python encoders wrap into 1e-8, or worse. --- src/rpcserver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index d10e14302..137a431fc 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -123,8 +123,8 @@ void RPCTypeCheckObj(const UniValue& o, CAmount AmountFromValue(const UniValue& value) { - if (!value.isNum()) - throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number"); + if (!value.isNum() && !value.isStr()) + throw JSONRPCError(RPC_TYPE_ERROR, "Amount is not a number or string"); CAmount amount; if (!ParseFixedPoint(value.getValStr(), 8, &amount)) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount"); From c2616abb3ccc0137b665f047e685b9e7d48bcffd Mon Sep 17 00:00:00 2001 From: Jonas Schnelli Date: Tue, 14 Jul 2015 21:13:16 +0200 Subject: [PATCH 37/43] [QA] add testcases for parsing strings as values --- qa/rpc-tests/wallet.py | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py index 7c440951e..ff827892a 100755 --- a/qa/rpc-tests/wallet.py +++ b/qa/rpc-tests/wallet.py @@ -67,14 +67,14 @@ class WalletTest (BitcoinTestFramework): assert_equal(self.nodes[2].getbalance("*"), 21) # Node0 should have three unspent outputs. - # Create a couple of transactions to send them to node2, submit them through - # node1, and make sure both node0 and node2 pick them up properly: + # Create a couple of transactions to send them to node2, submit them through + # node1, and make sure both node0 and node2 pick them up properly: node0utxos = self.nodes[0].listunspent(1) assert_equal(len(node0utxos), 3) # create both transactions txns_to_send = [] - for utxo in node0utxos: + for utxo in node0utxos: inputs = [] outputs = {} inputs.append({ "txid" : utxo["txid"], "vout" : utxo["vout"]}) @@ -159,7 +159,7 @@ class WalletTest (BitcoinTestFramework): #check if we can list zero value tx as available coins #1. create rawtx - #2. hex-changed one output to 0.0 + #2. hex-changed one output to 0.0 #3. sign and send #4. check if recipient (node0) can list the zero value tx usp = self.nodes[1].listunspent() @@ -380,5 +380,37 @@ class WalletTest (BitcoinTestFramework): assert_equal(Decimal(self.nodes[2].getbalance()), node2balance) assert_equal(Decimal(self.nodes[2].getbalance("*")), node2balance) + #send a tx with value in a string (PR#6380 +) + txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "2") + txObj = self.nodes[0].gettransaction(txId) + assert_equal(txObj['amount'], Decimal('-2.00000000')) + + txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "0.0001") + txObj = self.nodes[0].gettransaction(txId) + assert_equal(txObj['amount'], Decimal('-0.00010000')) + + #check if JSON parser can handle scientific notation in strings + txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1e-4") + txObj = self.nodes[0].gettransaction(txId) + assert_equal(txObj['amount'], Decimal('-0.00010000')) + + #this should fail + errorString = "" + try: + txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4") + except JSONRPCException,e: + errorString = e.error['message'] + + assert_equal("Invalid amount" in errorString, True); + + errorString = "" + try: + self.nodes[0].generate("2") #use a string to as block amount parameter must fail because it's not interpreted as amount + except JSONRPCException,e: + errorString = e.error['message'] + + assert_equal("not an integer" in errorString, True); + + if __name__ == '__main__': WalletTest ().main () From 56215c77d8cb9a43c81179ea5fe5e2fe24c0b10b Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Mon, 9 Nov 2015 08:40:46 +0100 Subject: [PATCH 38/43] Fix crash in validateaddress with -disablewallet Fix a null pointer dereference in validateaddress with -disablewallet. Also add a regression testcase. --- qa/pull-tester/rpc-tests.sh | 1 + qa/rpc-tests/disablewallet.py | 32 ++++++++++++++++++++++++++++++++ src/rpcmisc.cpp | 4 ++-- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100755 qa/rpc-tests/disablewallet.py diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index e2cba1796..08ff3fe7a 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -36,6 +36,7 @@ testScripts=( 'nodehandling.py' 'reindex.py' 'decodescript.py' + 'disablewallet.py' 'zcjoinsplit.py' 'zcjoinsplitdoublespend.py' 'getblocktemplate.py' diff --git a/qa/rpc-tests/disablewallet.py b/qa/rpc-tests/disablewallet.py new file mode 100755 index 000000000..4cb01575e --- /dev/null +++ b/qa/rpc-tests/disablewallet.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python2 +# 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. + +# +# Exercise API with -disablewallet. +# + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +class DisableWalletTest (BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 1) + + def setup_network(self, split=False): + self.nodes = start_nodes(1, self.options.tmpdir, [['-disablewallet']]) + self.is_network_split = False + self.sync_all() + + def run_test (self): + # Check regression: https://github.com/bitcoin/bitcoin/issues/6963#issuecomment-154548880 + x = self.nodes[0].validateaddress('3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy') + assert(x['isvalid'] == False) + x = self.nodes[0].validateaddress('mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ') + assert(x['isvalid'] == True) + +if __name__ == '__main__': + DisableWalletTest ().main () diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 0aaf4f301..33f4601f2 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -118,7 +118,7 @@ public: UniValue obj(UniValue::VOBJ); CPubKey vchPubKey; obj.push_back(Pair("isscript", false)); - if (pwalletMain->GetPubKey(keyID, vchPubKey)) { + if (pwalletMain && pwalletMain->GetPubKey(keyID, vchPubKey)) { obj.push_back(Pair("pubkey", HexStr(vchPubKey))); obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed())); } @@ -129,7 +129,7 @@ public: UniValue obj(UniValue::VOBJ); CScript subscript; obj.push_back(Pair("isscript", true)); - if (pwalletMain->GetCScript(scriptID, subscript)) { + if (pwalletMain && pwalletMain->GetCScript(scriptID, subscript)) { std::vector addresses; txnouttype whichType; int nRequired; From 5329495fe47e64735934cc7d5ff019d9afe5d8aa Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 15 Feb 2017 16:12:06 +0000 Subject: [PATCH 39/43] Adjust fundrawtransaction RPC test for Zcash - Enable wallet encryption - Correct block reward --- qa/rpc-tests/fundrawtransaction.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qa/rpc-tests/fundrawtransaction.py b/qa/rpc-tests/fundrawtransaction.py index ce52247b2..1d15fd132 100755 --- a/qa/rpc-tests/fundrawtransaction.py +++ b/qa/rpc-tests/fundrawtransaction.py @@ -16,7 +16,8 @@ class RawTransactionsTest(BitcoinTestFramework): initialize_chain_clean(self.options.tmpdir, 3) def setup_network(self, split=False): - self.nodes = start_nodes(3, self.options.tmpdir) + self.nodes = start_nodes(3, self.options.tmpdir, + extra_args=[['-experimentalfeatures', '-developerencryptwallet']] * 4) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) @@ -459,7 +460,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.sync_all() # make sure funds are received at node1 - assert_equal(oldBalance+Decimal('51.10000000'), self.nodes[0].getbalance()) + assert_equal(oldBalance+Decimal('11.10000000'), self.nodes[0].getbalance()) @@ -522,7 +523,7 @@ class RawTransactionsTest(BitcoinTestFramework): self.sync_all() self.nodes[0].generate(1) self.sync_all() - assert_equal(oldBalance+Decimal('50.19000000'), self.nodes[0].getbalance()) #0.19+block reward + assert_equal(oldBalance+Decimal('10.19000000'), self.nodes[0].getbalance()) #0.19+block reward ##################################################### # test fundrawtransaction with OP_RETURN and no vin # From 25b8f796ff81ed090ad38130931be4ac6844232d Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 28 Feb 2017 12:49:22 -0800 Subject: [PATCH 40/43] Re-encode t-addrs in disablewallet.py with Zcash prefixes --- qa/rpc-tests/disablewallet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qa/rpc-tests/disablewallet.py b/qa/rpc-tests/disablewallet.py index 4cb01575e..67acdcea1 100755 --- a/qa/rpc-tests/disablewallet.py +++ b/qa/rpc-tests/disablewallet.py @@ -23,9 +23,9 @@ class DisableWalletTest (BitcoinTestFramework): def run_test (self): # Check regression: https://github.com/bitcoin/bitcoin/issues/6963#issuecomment-154548880 - x = self.nodes[0].validateaddress('3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy') + x = self.nodes[0].validateaddress('t3b1jtLvxCstdo1pJs9Tjzc5dmWyvGQSZj8') assert(x['isvalid'] == False) - x = self.nodes[0].validateaddress('mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ') + x = self.nodes[0].validateaddress('tmGqwWtL7RsbxikDSN26gsbicxVr2xJNe86') assert(x['isvalid'] == True) if __name__ == '__main__': From 3c014397a95d0646228e5d262e798974beffcd32 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 28 Feb 2017 11:50:36 -0800 Subject: [PATCH 41/43] BTC -> ZEC in paytxfee RPC docs Revert before merging bitcoin/bitcoin#6504. --- src/wallet/rpcwallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 93038a8af..efa1a18a3 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2260,7 +2260,7 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp) " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" - " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in btc/kb\n" + " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in ZEC/KB\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") From 4acd140eeb660e7196372ac1a4d9742a39dea068 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 28 Feb 2017 11:51:46 -0800 Subject: [PATCH 42/43] Update default RPC port in help strings --- src/rpcnet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 8eb2ef84d..5eeaeacfe 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -223,8 +223,8 @@ UniValue disconnectnode(const UniValue& params, bool fHelp) "\nArguments:\n" "1. \"node\" (string, required) The node (see getpeerinfo for nodes)\n" "\nExamples:\n" - + HelpExampleCli("disconnectnode", "\"192.168.0.6:8333\"") - + HelpExampleRpc("disconnectnode", "\"192.168.0.6:8333\"") + + HelpExampleCli("disconnectnode", "\"192.168.0.6:8233\"") + + HelpExampleRpc("disconnectnode", "\"192.168.0.6:8233\"") ); CNode* pNode = FindNode(params[0].get_str()); From 14454a3a82904ed2beabc8fb2cdb1ab09ca46fb4 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 28 Feb 2017 11:52:34 -0800 Subject: [PATCH 43/43] Fix typo in listbanned RPC keys --- src/rpcnet.cpp | 2 +- src/test/rpc_tests.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 5eeaeacfe..6fdef9b9e 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -550,7 +550,7 @@ UniValue listbanned(const UniValue& params, bool fHelp) { UniValue rec(UniValue::VOBJ); rec.push_back(Pair("address", (*it).first.ToString())); - rec.push_back(Pair("banned_untill", (*it).second)); + rec.push_back(Pair("banned_until", (*it).second)); bannedAddresses.push_back(rec); } diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 4e063a840..30401c253 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -249,7 +249,7 @@ BOOST_AUTO_TEST_CASE(rpc_ban) ar = r.get_array(); o1 = ar[0].get_obj(); adr = find_value(o1, "address"); - UniValue banned_until = find_value(o1, "banned_untill"); + UniValue banned_until = find_value(o1, "banned_until"); BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0"); BOOST_CHECK_EQUAL(banned_until.get_int64(), 1607731200); // absolute time check @@ -260,7 +260,7 @@ BOOST_AUTO_TEST_CASE(rpc_ban) ar = r.get_array(); o1 = ar[0].get_obj(); adr = find_value(o1, "address"); - banned_until = find_value(o1, "banned_untill"); + banned_until = find_value(o1, "banned_until"); BOOST_CHECK_EQUAL(adr.get_str(), "127.0.0.0/255.255.255.0"); int64_t now = GetTime(); BOOST_CHECK(banned_until.get_int64() > now);