diff options
33 files changed, 1187 insertions, 656 deletions
diff --git a/contrib/README.md b/contrib/README.md index d1cdd7eb38..167b5df4e1 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -52,3 +52,7 @@ tests each pull and when master is tested using jenkins. ### [Verify SF Binaries](/contrib/verifysfbinaries) ### This script attempts to download and verify the signature file SHA256SUMS.asc from SourceForge. +### [Developer tools](/control/devtools) ### +Specific tools for developers working on this repository. +Contains the script `github-merge.sh` for merging github pull requests securely and signing them using GPG. + diff --git a/contrib/devtools/README.md b/contrib/devtools/README.md new file mode 100644 index 0000000000..55d5d24cca --- /dev/null +++ b/contrib/devtools/README.md @@ -0,0 +1,38 @@ +Contents +=========== +This directory contains tools for developers working on this repository. + +github-merge.sh +---------------- + +A small script to automate merging pull-requests securely and sign them with GPG. + +For example: + + ./github-merge.sh bitcoin/bitcoin 3077 + +(in any git repository) will help you merge pull request #3077 for the +bitcoin/bitcoin repository. + +What it does: +* Fetch master and the pull request. +* Locally construct a merge commit. +* Show the diff that merge results in. +* Ask you to verify the resulting source tree (so you can do a make +check or whatever). +* Ask you whether to GPG sign the merge commit. +* Ask you whether to push the result upstream. + +This means that there are no potential race conditions (where a +pullreq gets updated while you're reviewing it, but before you click +merge), and when using GPG signatures, that even a compromised github +couldn't mess with the sources. + +Setup +--------- +Configuring the github-merge tool for the bitcoin repository is done in the following way: + + git config githubmerge.repository bitcoin/bitcoin + git config githubmerge.testcmd "make -j4 check" (adapt to whatever you want to use for testing) + git config --global user.signingkey mykeyid (if you want to GPG sign) + diff --git a/contrib/devtools/github-merge.sh b/contrib/devtools/github-merge.sh new file mode 100755 index 0000000000..e42b71a54a --- /dev/null +++ b/contrib/devtools/github-merge.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# This script will locally construct a merge commit for a pull request on a +# github repository, inspect it, sign it and optionally push it. + +# The following temporary branches are created/overwritten and deleted: +# * pull/$PULL/base (the current master we're merging onto) +# * pull/$PULL/head (the current state of the remote pull request) +# * pull/$PULL/merge (github's merge) +# * pull/$PULL/local-merge (our merge) + +# In case of a clean merge that is accepted by the user, the local branch with +# name $BRANCH is overwritten with the merged result, and optionally pushed. + +REPO="$(git config --get githubmerge.repository)" +if [[ "d$REPO" == "d" ]]; then + echo "ERROR: No repository configured. Use this command to set:" >&2 + echo "git config githubmerge.repository <owner>/<repo>" >&2 + echo "In addition, you can set the following variables:" >&2 + echo "- githubmerge.host (default git@github.com)" >&2 + echo "- githubmerge.branch (default master)" >&2 + echo "- githubmerge.testcmd (default none)" >&2 + exit 1 +fi + +HOST="$(git config --get githubmerge.host)" +if [[ "d$HOST" == "d" ]]; then + HOST="git@github.com" +fi + +BRANCH="$(git config --get githubmerge.branch)" +if [[ "d$BRANCH" == "d" ]]; then + BRANCH="master" +fi + +TESTCMD="$(git config --get githubmerge.testcmd)" + +PULL="$1" + +if [[ "d$PULL" == "d" ]]; then + echo "Usage: $0 pullnumber [branch]" >&2 + exit 2 +fi + +if [[ "d$2" != "d" ]]; then + BRANCH="$2" +fi + +# Initialize source branches. +git checkout -q "$BRANCH" +if git fetch -q "$HOST":"$REPO" "+refs/pull/$PULL/*:refs/heads/pull/$PULL/*"; then + if ! git log -1q "refs/heads/pull/$PULL/head" >/dev/null 2>&1; then + echo "ERROR: Cannot find head of pull request #$PULL on $HOST:$REPO." >&2 + exit 3 + fi + if ! git log -1q "refs/heads/pull/$PULL/merge" >/dev/null 2>&1; then + echo "ERROR: Cannot find merge of pull request #$PULL on $HOST:$REPO." >&2 + exit 3 + fi +else + echo "ERROR: Cannot find pull request #$PULL on $HOST:$REPO." >&2 + exit 3 +fi +if git fetch -q "$HOST":"$REPO" +refs/heads/"$BRANCH":refs/heads/pull/"$PULL"/base; then + true +else + echo "ERROR: Cannot find branch $BRANCH on $HOST:$REPO." >&2 + exit 3 +fi +git checkout -q pull/"$PULL"/base +git branch -q -D pull/"$PULL"/local-merge 2>/dev/null +git checkout -q -b pull/"$PULL"/local-merge +TMPDIR="$(mktemp -d -t ghmXXXXX)" + +function cleanup() { + git checkout -q "$BRANCH" + git branch -q -D pull/"$PULL"/head 2>/dev/null + git branch -q -D pull/"$PULL"/base 2>/dev/null + git branch -q -D pull/"$PULL"/merge 2>/dev/null + git branch -q -D pull/"$PULL"/local-merge 2>/dev/null + rm -rf "$TMPDIR" +} + +# Create unsigned merge commit. +( + echo "Merge pull request #$PULL" + echo "" + git log --no-merges --topo-order --pretty='format:%h %s (%an)' pull/"$PULL"/base..pull/"$PULL"/head +)>"$TMPDIR/message" +if git merge -q --commit --no-edit --no-ff -m "$(<"$TMPDIR/message")" pull/"$PULL"/head; then + if [ "d$(git log --pretty='format:%s' -n 1)" != "dMerge pull request #$PULL" ]; then + echo "ERROR: Creating merge failed (already merged?)." >&2 + cleanup + exit 4 + fi +else + echo "ERROR: Cannot be merged cleanly." >&2 + git merge --abort + cleanup + exit 4 +fi + +# Run test command if configured. +if [[ "d$TESTCMD" != "d" ]]; then + # Go up to the repository's root. + while [ ! -d .git ]; do cd ..; done + if ! $TESTCMD; then + echo "ERROR: Running $TESTCMD failed." >&2 + cleanup + exit 5 + fi + # Show the created merge. + git diff pull/"$PULL"/merge..pull/"$PULL"/local-merge >"$TMPDIR"/diff + git diff pull/"$PULL"/base..pull/"$PULL"/local-merge + if [[ "$(<"$TMPDIR"/diff)" != "" ]]; then + echo "WARNING: merge differs from github!" >&2 + read -p "Type 'ignore' to continue. " -r >&2 + if [[ "d$REPLY" =~ ^d[iI][gG][nN][oO][rR][eE]$ ]]; then + echo "Difference with github ignored." >&2 + else + cleanup + exit 6 + fi + fi + read -p "Press 'd' to accept the diff. " -n 1 -r >&2 + echo + if [[ "d$REPLY" =~ ^d[dD]$ ]]; then + echo "Diff accepted." >&2 + else + echo "ERROR: Diff rejected." >&2 + cleanup + exit 6 + fi +else + # Verify the result. + echo "Dropping you on a shell so you can try building/testing the merged source." >&2 + echo "Run 'git diff HEAD~' to show the changes being merged." >&2 + echo "Type 'exit' when done." >&2 + bash -i + read -p "Press 'm' to accept the merge. " -n 1 -r >&2 + echo + if [[ "d$REPLY" =~ ^d[Mm]$ ]]; then + echo "Merge accepted." >&2 + else + echo "ERROR: Merge rejected." >&2 + cleanup + exit 7 + fi +fi + +# Sign the merge commit. +read -p "Press 's' to sign off on the merge. " -n 1 -r >&2 +echo +if [[ "d$REPLY" =~ ^d[Ss]$ ]]; then + if [[ "$(git config --get user.signingkey)" == "" ]]; then + echo "WARNING: No GPG signing key set, not signing. Set one using:" >&2 + echo "git config --global user.signingkey <key>" >&2 + git commit -q --signoff --amend --no-edit + else + git commit -q --gpg-sign --amend --no-edit + fi +fi + +# Clean up temporary branches, and put the result in $BRANCH. +git checkout -q "$BRANCH" +git reset -q --hard pull/"$PULL"/local-merge +cleanup + +# Push the result. +read -p "Type 'push' to push the result to $HOST:$REPO, branch $BRANCH. " -r >&2 +if [[ "d$REPLY" =~ ^d[Pp][Uu][Ss][Hh]$ ]]; then + git push "$HOST":"$REPO" refs/heads/"$BRANCH" +fi diff --git a/qa/pull-tester/build-tests.sh.in b/qa/pull-tester/build-tests.sh.in index 82ae60fdf1..86d4d9d0e9 100755 --- a/qa/pull-tester/build-tests.sh.in +++ b/qa/pull-tester/build-tests.sh.in @@ -68,6 +68,9 @@ fi cd @abs_top_srcdir@/linux-build make check +# Run RPC integration test on Linux: +@abs_top_srcdir@/qa/rpc-tests/wallet.sh @abs_top_srcdir@/linux-build/src + if [ $RUN_EXPENSIVE_TESTS = 1 ]; then # Run unit tests and blockchain-tester on Windows: cd @abs_top_srcdir@/win32-build diff --git a/qa/rpc-tests/README.md b/qa/rpc-tests/README.md new file mode 100644 index 0000000000..c8537247d9 --- /dev/null +++ b/qa/rpc-tests/README.md @@ -0,0 +1,6 @@ +Regression tests of RPC interface +================================= + +wallet.sh : Test wallet send/receive code (see comments for details) + +util.sh : useful re-usable functions diff --git a/qa/rpc-tests/util.sh b/qa/rpc-tests/util.sh new file mode 100644 index 0000000000..dc2a319970 --- /dev/null +++ b/qa/rpc-tests/util.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +# Functions used by more than one test + +function echoerr { + echo "$@" 1>&2; +} + +# Usage: ExtractKey <key> "<json_object_string>" +# Warning: this will only work for the very-well-behaved +# JSON produced by bitcoind, do NOT use it to try to +# parse arbitrary/nested/etc JSON. +function ExtractKey { + echo $2 | tr -d ' "{}\n' | awk -v RS=',' -F: "\$1 ~ /$1/ { print \$2}" +} + +function CreateDataDir { + DIR=$1 + mkdir -p $DIR + CONF=$DIR/bitcoin.conf + echo "regtest=1" >> $CONF + echo "keypool=2" >> $CONF + echo "rpcuser=rt" >> $CONF + echo "rpcpassword=rt" >> $CONF + echo "rpcwait=1" >> $CONF + shift + while (( "$#" )); do + echo $1 >> $CONF + shift + done +} + +function AssertEqual { + if (( $( echo "$1 == $2" | bc ) == 0 )) + then + echoerr "AssertEqual: $1 != $2" + exit 1 + fi +} + +# CheckBalance -datadir=... amount account minconf +function CheckBalance { + B=$( $CLI $1 getbalance $3 $4 ) + if (( $( echo "$B == $2" | bc ) == 0 )) + then + echoerr "bad balance: $B (expected $2)" + exit 1 + fi +} + +# Use: Address <datadir> [account] +function Address { + $CLI $1 getnewaddress $2 +} + +# Send from to amount +function Send { + from=$1 + to=$2 + amount=$3 + address=$(Address $to) + txid=$( $CLI $from sendtoaddress $address $amount ) +} + +# Use: Unspent <datadir> <n'th-last-unspent> <var> +function Unspent { + local r=$( $CLI $1 listunspent | awk -F'[ |:,"]+' "\$2 ~ /$3/ { print \$3 }" | tail -n $2 | head -n 1) + echo $r +} + +# Use: CreateTxn1 <datadir> <n'th-last-unspent> <destaddress> +# produces hex from signrawtransaction +function CreateTxn1 { + TXID=$(Unspent $1 $2 txid) + AMOUNT=$(Unspent $1 $2 amount) + VOUT=$(Unspent $1 $2 vout) + RAWTXN=$( $CLI $1 createrawtransaction "[{\"txid\":\"$TXID\",\"vout\":$VOUT}]" "{\"$3\":$AMOUNT}") + ExtractKey hex "$( $CLI $1 signrawtransaction $RAWTXN )" +} + +# Use: SendRawTxn <datadir> <hex_txn_data> +function SendRawTxn { + $CLI $1 sendrawtransaction $2 +} diff --git a/qa/rpc-tests/wallet.sh b/qa/rpc-tests/wallet.sh new file mode 100755 index 0000000000..dd511782d0 --- /dev/null +++ b/qa/rpc-tests/wallet.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +# Test block generation and basic wallet sending + +if [ $# -lt 1 ]; then + echo "Usage: $0 path_to_binaries" + echo "e.g. $0 ../../src" + exit 1 +fi + +BITCOIND=${1}/bitcoind +CLI=${1}/bitcoin-cli + +DIR="${BASH_SOURCE%/*}" +if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi +. "$DIR/util.sh" + +D=$(mktemp -d test.XXXXX) + +D1=${D}/node1 +CreateDataDir $D1 port=11000 rpcport=11001 +B1ARGS="-datadir=$D1" +$BITCOIND $B1ARGS & +B1PID=$! + +D2=${D}/node2 +CreateDataDir $D2 port=11010 rpcport=11011 connect=127.0.0.1:11000 +B2ARGS="-datadir=$D2" +$BITCOIND $B2ARGS & +B2PID=$! + +D3=${D}/node3 +CreateDataDir $D3 port=11020 rpcport=11021 connect=127.0.0.1:11000 +B3ARGS="-datadir=$D3" +$BITCOIND $BITCOINDARGS $B3ARGS & +B3PID=$! + +trap "kill -9 $B1PID $B2PID $B3PID; rm -rf $D" EXIT + +# 1 block, 50 XBT each == 50 XBT +$CLI $B1ARGS setgenerate true 1 +sleep 1 # sleep is necessary to let block broadcast +# 101 blocks, 1 mature == 50 XBT +$CLI $B2ARGS setgenerate true 101 +sleep 1 + +CheckBalance $B1ARGS 50 +CheckBalance $B2ARGS 50 + +# Send 21 XBT from 1 to 3. Second +# transaction will be child of first, and +# will require a fee +Send $B1ARGS $B3ARGS 11 +Send $B1ARGS $B3ARGS 10 + +# Have B1 mine a new block, and mature it +# to recover transaction fees +$CLI $B1ARGS setgenerate true 1 +sleep 1 + +# Have B2 mine 100 blocks so B1's block is mature: +$CLI $B2ARGS setgenerate true 100 +sleep 1 + +# B1 should end up with 100 XBT in block rewards plus fees, +# minus the 21 XBT sent to B3: +CheckBalance $B1ARGS "100-21" +CheckBalance $B3ARGS "21" + +# B1 should have two unspent outputs; create a couple +# of raw transactions to send them to B3, submit them through +# B2, and make sure both B1 and B3 pick them up properly: +RAW1=$(CreateTxn1 $B1ARGS 1 $(Address $B3ARGS "from1" ) ) +RAW2=$(CreateTxn1 $B1ARGS 2 $(Address $B3ARGS "from1" ) ) +RAWTXID1=$(SendRawTxn $B2ARGS $RAW1) +RAWTXID2=$(SendRawTxn $B2ARGS $RAW2) + +# Have B2 mine a block to confirm transactions: +$CLI $B2ARGS setgenerate true 1 +sleep 1 # allow time for block to be relayed + +# Check balances after confirmation +CheckBalance $B1ARGS 0 +CheckBalance $B3ARGS 100 +CheckBalance $B3ARGS "100-21" "from1" + +$CLI $B3ARGS stop > /dev/null 2>&1 +wait $B3PID +$CLI $B2ARGS stop > /dev/null 2>&1 +wait $B2PID +$CLI $B1ARGS stop > /dev/null 2>&1 +wait $B1PID + +echo "Tests successful, cleaning up" +trap "" EXIT +rm -rf $D +exit 0 diff --git a/src/Makefile.am b/src/Makefile.am index 7450507b34..561d2ca7ae 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,7 +12,10 @@ DIST_SUBDIRS = . qt test .PHONY: FORCE # bitcoin core # BITCOIN_CORE_H = addrman.h alert.h allocators.h base58.h bignum.h \ - bitcoinrpc.h bloom.h chainparams.h checkpoints.h checkqueue.h \ + rpcclient.h \ + rpcprotocol.h \ + rpcserver.h \ + bloom.h chainparams.h checkpoints.h checkqueue.h \ clientversion.h coincontrol.h compat.h core.h coins.h crypter.h db.h hash.h init.h \ key.h keystore.h leveldbwrapper.h limitedmap.h main.h miner.h mruset.h \ netbase.h net.h noui.h protocol.h script.h serialize.h sync.h threadsafety.h \ @@ -30,7 +33,11 @@ obj/build.h: FORCE $(abs_top_srcdir) version.o: obj/build.h -libbitcoin_a_SOURCES = addrman.cpp alert.cpp allocators.cpp bitcoinrpc.cpp bloom.cpp \ +libbitcoin_a_SOURCES = addrman.cpp alert.cpp allocators.cpp \ + rpcclient.cpp \ + rpcprotocol.cpp \ + rpcserver.cpp \ + bloom.cpp \ chainparams.cpp checkpoints.cpp core.cpp coins.cpp crypter.cpp db.cpp hash.cpp \ init.cpp key.cpp keystore.cpp leveldbwrapper.cpp main.cpp miner.cpp \ netbase.cpp net.cpp noui.cpp protocol.cpp rpcblockchain.cpp rpcdump.cpp \ diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index aa6ce27c52..4e65947464 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -5,8 +5,9 @@ #include "util.h" #include "init.h" -#include "bitcoinrpc.h" +#include "rpcclient.h" #include "ui_interface.h" /* for _(...) */ +#include "chainparams.h" #include <boost/filesystem/operations.hpp> @@ -26,6 +27,11 @@ static bool AppInitRPC(int argc, char* argv[]) return false; } ReadConfigFile(mapArgs, mapMultiArgs); + // Check for -testnet or -regtest parameter (TestNet() calls are only valid after this clause) + if (!SelectParamsFromCommandLine()) { + fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); + return false; + } if (argc<2 || mapArgs.count("-?") || mapArgs.count("--help")) { diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 6dbab240bf..fdc66b8d65 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -3,7 +3,8 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "bitcoinrpc.h" +#include "rpcserver.h" +#include "rpcclient.h" #include "init.h" #include "main.h" #include "noui.h" diff --git a/src/compat.h b/src/compat.h index 7e001d81e1..3924445b1a 100644 --- a/src/compat.h +++ b/src/compat.h @@ -11,7 +11,9 @@ #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0501 +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN 1 +#endif #ifndef NOMINMAX #define NOMINMAX #endif diff --git a/src/init.cpp b/src/init.cpp index b2e7ddf335..da70ef9dba 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -10,7 +10,7 @@ #include "init.h" #include "addrman.h" -#include "bitcoinrpc.h" +#include "rpcserver.h" #include "checkpoints.h" #include "miner.h" #include "net.h" @@ -112,7 +112,7 @@ void Shutdown() ShutdownRPCMining(); if (pwalletMain) bitdb.Flush(false); - GenerateBitcoins(false, NULL); + GenerateBitcoins(false, NULL, 0); StopNode(); { LOCK(cs_main); @@ -1050,7 +1050,7 @@ bool AppInit2(boost::thread_group& threadGroup, bool fForceServer) // Generate coins in the background if (pwalletMain) - GenerateBitcoins(GetBoolArg("-gen", false), pwalletMain); + GenerateBitcoins(GetBoolArg("-gen", false), pwalletMain, GetArg("-genproclimit", -1)); // ********************************************************* Step 12: finished diff --git a/src/main.cpp b/src/main.cpp index c4d58f970d..b6888f9750 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -552,7 +552,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) REJECT_INVALID, "vout empty"); // Size limits if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) - return state.DoS(100, error("CTransaction::CheckTransaction() : size limits failed"), + return state.DoS(100, error("CheckTransaction() : size limits failed"), REJECT_INVALID, "oversize"); // Check for negative or overflow output values @@ -567,7 +567,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) REJECT_INVALID, "vout too large"); nValueOut += txout.nValue; if (!MoneyRange(nValueOut)) - return state.DoS(100, error("CTransaction::CheckTransaction() : txout total out of range"), + return state.DoS(100, error("CheckTransaction() : txout total out of range"), REJECT_INVALID, "txout total too large"); } @@ -576,7 +576,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) BOOST_FOREACH(const CTxIn& txin, tx.vin) { if (vInOutPoints.count(txin.prevout)) - return state.DoS(100, error("CTransaction::CheckTransaction() : duplicate inputs"), + return state.DoS(100, error("CheckTransaction() : duplicate inputs"), REJECT_INVALID, "duplicate inputs"); vInOutPoints.insert(txin.prevout); } @@ -797,9 +797,6 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa g_signals.EraseTransaction(ptxOld->GetHash()); g_signals.SyncTransaction(hash, tx, NULL); - LogPrint("mempool", "AcceptToMemoryPool: : accepted %s (poolsz %"PRIszu")\n", - hash.ToString().c_str(), - pool.mapTx.size()); return true; } @@ -3100,8 +3097,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->nVersion = 300; if (!vRecv.empty()) vRecv >> addrFrom >> nNonce; - if (!vRecv.empty()) + if (!vRecv.empty()) { vRecv >> pfrom->strSubVer; + pfrom->cleanSubVer = SanitizeString(pfrom->strSubVer); + } if (!vRecv.empty()) vRecv >> pfrom->nStartingHeight; if (!vRecv.empty()) @@ -3168,7 +3167,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) pfrom->fSuccessfullyConnected = true; - LogPrintf("receive version message: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString().c_str(), addrFrom.ToString().c_str(), pfrom->addr.ToString().c_str()); + LogPrintf("receive version message: %s: version %d, blocks=%d, us=%s, them=%s, peer=%s\n", pfrom->cleanSubVer.c_str(), pfrom->nVersion, pfrom->nStartingHeight, addrMe.ToString().c_str(), addrFrom.ToString().c_str(), pfrom->addr.ToString().c_str()); AddTimeData(pfrom->addr, nTime); @@ -3427,6 +3426,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vWorkQueue.push_back(inv.hash); vEraseQueue.push_back(inv.hash); + + LogPrint("mempool", "AcceptToMemoryPool: %s %s : accepted %s (poolsz %"PRIszu")\n", + pfrom->addr.ToString().c_str(), pfrom->cleanSubVer.c_str(), + tx.GetHash().ToString().c_str(), + mempool.mapTx.size()); + // Recursively process any orphan transactions that depended on this one for (unsigned int i = 0; i < vWorkQueue.size(); i++) { @@ -3475,7 +3480,10 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } int nDoS = 0; if (state.IsInvalid(nDoS)) - { + { + LogPrint("mempool", "%s from %s %s was not accepted into the memory pool: %s\n", tx.GetHash().ToString().c_str(), + pfrom->addr.ToString().c_str(), pfrom->cleanSubVer.c_str(), + state.GetRejectReason().c_str()); pfrom->PushMessage("reject", strCommand, state.GetRejectCode(), state.GetRejectReason(), inv.hash); if (nDoS > 0) @@ -3612,7 +3620,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!(sProblem.empty())) { LogPrint("net", "pong %s %s: %s, %"PRIx64" expected, %"PRIx64" received, %"PRIszu" bytes\n", pfrom->addr.ToString().c_str(), - pfrom->strSubVer.c_str(), + pfrom->cleanSubVer.c_str(), sProblem.c_str(), pfrom->nPingNonceSent, nonce, diff --git a/src/main.h b/src/main.h index cf803ae25e..1d3ac1cdbb 100644 --- a/src/main.h +++ b/src/main.h @@ -937,7 +937,7 @@ private: unsigned char chRejectCode; bool corruptionPossible; public: - CValidationState() : mode(MODE_VALID), nDoS(0) {} + CValidationState() : mode(MODE_VALID), nDoS(0), corruptionPossible(false) {} bool DoS(int level, bool ret = false, unsigned char chRejectCodeIn=0, std::string strRejectReasonIn="", bool corruptionIn=false) { diff --git a/src/miner.cpp b/src/miner.cpp index 97f434851d..b01b60cc34 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -650,11 +650,10 @@ void static BitcoinMiner(CWallet *pwallet) } } -void GenerateBitcoins(bool fGenerate, CWallet* pwallet) +void GenerateBitcoins(bool fGenerate, CWallet* pwallet, int nThreads) { static boost::thread_group* minerThreads = NULL; - int nThreads = GetArg("-genproclimit", -1); if (nThreads < 0) { if (Params().NetworkID() == CChainParams::REGTEST) nThreads = 1; diff --git a/src/miner.h b/src/miner.h index 4879f55d51..26151f6cd5 100644 --- a/src/miner.h +++ b/src/miner.h @@ -16,7 +16,7 @@ class CScript; class CWallet; /** Run the miner threads */ -void GenerateBitcoins(bool fGenerate, CWallet* pwallet); +void GenerateBitcoins(bool fGenerate, CWallet* pwallet, int nThreads); /** Generate a new block, without valid proof-of-work */ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn); CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey); diff --git a/src/net.cpp b/src/net.cpp index c547cf3337..fcef9feea0 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -616,7 +616,7 @@ void CNode::copyStats(CNodeStats &stats) X(nTimeConnected); X(addrName); X(nVersion); - X(strSubVer); + X(cleanSubVer); X(fInbound); X(nStartingHeight); X(nMisbehavior); @@ -121,7 +121,7 @@ public: int64_t nTimeConnected; std::string addrName; int nVersion; - std::string strSubVer; + std::string cleanSubVer; bool fInbound; int nStartingHeight; int nMisbehavior; @@ -203,7 +203,11 @@ public: std::string addrName; CService addrLocal; int nVersion; - std::string strSubVer; + // strSubVer is whatever byte array we read from the wire. However, this field is intended + // to be printed out, displayed to humans in various forms and so on. So we sanitize it and + // store the sanitized version in cleanSubVer. The original should be used when dealing with + // the network or wire types and the cleaned string used when displayed or logged. + std::string strSubVer, cleanSubVer; bool fOneShot; bool fClient; bool fInbound; diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm index 8f826941b2..86b8c834d4 100644 --- a/src/qt/macdockiconhandler.mm +++ b/src/qt/macdockiconhandler.mm @@ -8,6 +8,10 @@ #undef slots #include <Cocoa/Cocoa.h> +#if QT_VERSION < 0x050000 +extern void qt_mac_set_dock_menu(QMenu *); +#endif + @interface DockIconClickEventHandler : NSObject { MacDockIconHandler* dockIconHandler; @@ -52,7 +56,9 @@ MacDockIconHandler::MacDockIconHandler() : QObject() this->m_dummyWidget = new QWidget(); this->m_dockMenu = new QMenu(this->m_dummyWidget); this->setMainWindow(NULL); - +#if QT_VERSION < 0x050000 + qt_mac_set_dock_menu(this->m_dockMenu); +#endif [pool release]; } diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 64a3a68ba7..d43cdc7e5f 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -8,7 +8,8 @@ #include "clientmodel.h" #include "guiutil.h" -#include "bitcoinrpc.h" +#include "rpcserver.h" +#include "rpcclient.h" #include "json/json_spirit_value.h" #include <openssl/crypto.h> diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 6f0b353e34..71663bbb34 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -3,9 +3,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - - -#include "bitcoinrpc.h" +#include "rpcserver.h" #include "main.h" #include "sync.h" diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp new file mode 100644 index 0000000000..2667a5d5a5 --- /dev/null +++ b/src/rpcclient.cpp @@ -0,0 +1,246 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "rpcclient.h" + +#include "rpcprotocol.h" +#include "util.h" +#include "ui_interface.h" +#include "chainparams.h" // for Params().RPCPort() + +#include <stdint.h> + +#include <boost/algorithm/string.hpp> +#include <boost/asio.hpp> +#include <boost/asio/ssl.hpp> +#include <boost/bind.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> +#include <boost/iostreams/concepts.hpp> +#include <boost/iostreams/stream.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> +#include "json/json_spirit_writer_template.h" + +using namespace std; +using namespace boost; +using namespace boost::asio; +using namespace json_spirit; + +Object CallRPC(const string& strMethod, const Array& params) +{ + if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "") + throw runtime_error(strprintf( + _("You must set rpcpassword=<password> in the configuration file:\n%s\n" + "If the file does not exist, create it with owner-readable-only file permissions."), + GetConfigFile().string().c_str())); + + // Connect to localhost + bool fUseSSL = GetBoolArg("-rpcssl", false); + asio::io_service io_service; + ssl::context context(io_service, ssl::context::sslv23); + context.set_options(ssl::context::no_sslv2); + asio::ssl::stream<asio::ip::tcp::socket> sslStream(io_service, context); + SSLIOStreamDevice<asio::ip::tcp> d(sslStream, fUseSSL); + iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > stream(d); + + bool fWait = GetBoolArg("-rpcwait", false); // -rpcwait means try until server has started + do { + bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(Params().RPCPort()))); + if (fConnected) break; + if (fWait) + MilliSleep(1000); + else + throw runtime_error("couldn't connect to server"); + } while (fWait); + + // HTTP basic authentication + string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]); + map<string, string> mapRequestHeaders; + mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64; + + // Send request + string strRequest = JSONRPCRequest(strMethod, params, 1); + string strPost = HTTPPost(strRequest, mapRequestHeaders); + stream << strPost << std::flush; + + // Receive HTTP reply status + int nProto = 0; + int nStatus = ReadHTTPStatus(stream, nProto); + + // Receive HTTP reply message headers and body + map<string, string> mapHeaders; + string strReply; + ReadHTTPMessage(stream, mapHeaders, strReply, nProto); + + if (nStatus == HTTP_UNAUTHORIZED) + throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); + else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR) + throw runtime_error(strprintf("server returned HTTP error %d", nStatus)); + else if (strReply.empty()) + throw runtime_error("no response from server"); + + // Parse reply + Value valReply; + if (!read_string(strReply, valReply)) + throw runtime_error("couldn't parse reply from server"); + const Object& reply = valReply.get_obj(); + if (reply.empty()) + throw runtime_error("expected reply to have result, error and id properties"); + + return reply; +} + +template<typename T> +void ConvertTo(Value& value, bool fAllowNull=false) +{ + if (fAllowNull && value.type() == null_type) + return; + if (value.type() == str_type) + { + // reinterpret string as unquoted json value + Value value2; + string strJSON = value.get_str(); + if (!read_string(strJSON, value2)) + throw runtime_error(string("Error parsing JSON:")+strJSON); + ConvertTo<T>(value2, fAllowNull); + value = value2; + } + else + { + value = value.get_value<T>(); + } +} + +// Convert strings to command-specific RPC representation +Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams) +{ + Array params; + BOOST_FOREACH(const std::string ¶m, strParams) + params.push_back(param); + + int n = params.size(); + + // + // Special case non-string parameter types + // + if (strMethod == "stop" && n > 0) ConvertTo<bool>(params[0]); + if (strMethod == "getaddednodeinfo" && n > 0) ConvertTo<bool>(params[0]); + if (strMethod == "setgenerate" && n > 0) ConvertTo<bool>(params[0]); + if (strMethod == "setgenerate" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getnetworkhashps" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "getnetworkhashps" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); + if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); + if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo<bool>(params[1]); + if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo<bool>(params[1]); + if (strMethod == "getbalance" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getblockhash" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "move" && n > 2) ConvertTo<double>(params[2]); + if (strMethod == "move" && n > 3) ConvertTo<boost::int64_t>(params[3]); + if (strMethod == "sendfrom" && n > 2) ConvertTo<double>(params[2]); + if (strMethod == "sendfrom" && n > 3) ConvertTo<boost::int64_t>(params[3]); + if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); + if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getblocktemplate" && n > 0) ConvertTo<Object>(params[0]); + if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "sendmany" && n > 1) ConvertTo<Object>(params[1]); + if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]); + if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "addmultisigaddress" && n > 1) ConvertTo<Array>(params[1]); + if (strMethod == "createmultisig" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "createmultisig" && n > 1) ConvertTo<Array>(params[1]); + if (strMethod == "listunspent" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "listunspent" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listunspent" && n > 2) ConvertTo<Array>(params[2]); + if (strMethod == "getblock" && n > 1) ConvertTo<bool>(params[1]); + if (strMethod == "getrawtransaction" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "createrawtransaction" && n > 0) ConvertTo<Array>(params[0]); + if (strMethod == "createrawtransaction" && n > 1) ConvertTo<Object>(params[1]); + if (strMethod == "signrawtransaction" && n > 1) ConvertTo<Array>(params[1], true); + if (strMethod == "signrawtransaction" && n > 2) ConvertTo<Array>(params[2], true); + if (strMethod == "sendrawtransaction" && n > 1) ConvertTo<bool>(params[1], true); + if (strMethod == "gettxout" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "gettxout" && n > 2) ConvertTo<bool>(params[2]); + if (strMethod == "lockunspent" && n > 0) ConvertTo<bool>(params[0]); + if (strMethod == "lockunspent" && n > 1) ConvertTo<Array>(params[1]); + if (strMethod == "importprivkey" && n > 2) ConvertTo<bool>(params[2]); + if (strMethod == "verifychain" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "verifychain" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "keypoolrefill" && n > 0) ConvertTo<boost::int64_t>(params[0]); + + return params; +} + +int CommandLineRPC(int argc, char *argv[]) +{ + string strPrint; + int nRet = 0; + try + { + // Skip switches + while (argc > 1 && IsSwitchChar(argv[1][0])) + { + argc--; + argv++; + } + + // Method + if (argc < 2) + throw runtime_error("too few parameters"); + string strMethod = argv[1]; + + // Parameters default to strings + std::vector<std::string> strParams(&argv[2], &argv[argc]); + Array params = RPCConvertValues(strMethod, strParams); + + // Execute + Object reply = CallRPC(strMethod, params); + + // Parse reply + const Value& result = find_value(reply, "result"); + const Value& error = find_value(reply, "error"); + + if (error.type() != null_type) + { + // Error + strPrint = "error: " + write_string(error, false); + int code = find_value(error.get_obj(), "code").get_int(); + nRet = abs(code); + } + else + { + // Result + if (result.type() == null_type) + strPrint = ""; + else if (result.type() == str_type) + strPrint = result.get_str(); + else + strPrint = write_string(result, true); + } + } + catch (boost::thread_interrupted) { + throw; + } + catch (std::exception& e) { + strPrint = string("error: ") + e.what(); + nRet = 87; + } + catch (...) { + PrintException(NULL, "CommandLineRPC()"); + } + + if (strPrint != "") + { + fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); + } + return nRet; +} diff --git a/src/rpcclient.h b/src/rpcclient.h new file mode 100644 index 0000000000..f3ea56c25b --- /dev/null +++ b/src/rpcclient.h @@ -0,0 +1,17 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef _BITCOINRPC_CLIENT_H_ +#define _BITCOINRPC_CLIENT_H_ 1 + +#include "json/json_spirit_reader_template.h" +#include "json/json_spirit_utils.h" +#include "json/json_spirit_writer_template.h" + +int CommandLineRPC(int argc, char *argv[]); + +json_spirit::Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams); + +#endif diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index 53cec9a6fa..92f4c2c6dd 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -2,10 +2,8 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - - #include "base58.h" -#include "bitcoinrpc.h" +#include "rpcserver.h" #include "init.h" #include "main.h" #include "sync.h" diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index e4fa7ed853..131a258c84 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "bitcoinrpc.h" +#include "rpcserver.h" #include "chainparams.h" #include "db.h" #include "init.h" @@ -133,6 +133,7 @@ Value setgenerate(const Array& params, bool fHelp) "\nArguments:\n" "1. generate (boolean, required) Set to true to turn on generation, off to turn off.\n" "2. genproclimit (numeric, optional) Set the processor limit for when generation is on. Can be -1 for unlimited.\n" + " Note: in -regtest mode, genproclimit controls how many blocks are generated immediately.\n" "\nExamples:\n" "\nSet the generation on with a limit of one processor\n" + HelpExampleCli("setgenerate", "true 1") + @@ -144,21 +145,55 @@ Value setgenerate(const Array& params, bool fHelp) + HelpExampleRpc("setgenerate", "true, 1") ); + if (pwalletMain == NULL) + throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (disabled)"); + bool fGenerate = true; if (params.size() > 0) fGenerate = params[0].get_bool(); + int nGenProcLimit = -1; if (params.size() > 1) { - int nGenProcLimit = params[1].get_int(); - mapArgs["-genproclimit"] = itostr(nGenProcLimit); + nGenProcLimit = params[1].get_int(); if (nGenProcLimit == 0) fGenerate = false; } - mapArgs["-gen"] = (fGenerate ? "1" : "0"); - assert(pwalletMain != NULL); - GenerateBitcoins(fGenerate, pwalletMain); + // -regtest mode: don't return until nGenProcLimit blocks are generated + if (fGenerate && Params().NetworkID() == CChainParams::REGTEST) + { + int nHeightStart = 0; + int nHeightEnd = 0; + int nHeight = 0; + int nGenerate = (nGenProcLimit > 0 ? nGenProcLimit : 1); + { // Don't keep cs_main locked + LOCK(cs_main); + nHeightStart = chainActive.Height(); + nHeight = nHeightStart; + nHeightEnd = nHeightStart+nGenerate; + } + int nHeightLast = -1; + while (nHeight < nHeightEnd) + { + if (nHeightLast != nHeight) + { + nHeightLast = nHeight; + GenerateBitcoins(fGenerate, pwalletMain, 1); + } + MilliSleep(1); + { // Don't keep cs_main locked + LOCK(cs_main); + nHeight = chainActive.Height(); + } + } + } + else // Not -regtest: start generate thread, return immediately + { + mapArgs["-gen"] = (fGenerate ? "1" : "0"); + GenerateBitcoins(fGenerate, pwalletMain, nGenProcLimit); + } + return Value::null; } diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index 9f8dea80b0..99602bd1cf 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -2,9 +2,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - - -#include "bitcoinrpc.h" +#include "rpcserver.h" #include "net.h" #include "netbase.h" #include "protocol.h" @@ -126,7 +124,10 @@ Value getpeerinfo(const Array& params, bool fHelp) if (stats.dPingWait > 0.0) obj.push_back(Pair("pingwait", stats.dPingWait)); obj.push_back(Pair("version", stats.nVersion)); - obj.push_back(Pair("subver", stats.strSubVer)); + // Use the sanitized form of subver here, to avoid tricksy remote peers from + // corrupting or modifiying the JSON output by putting special characters in + // their ver message. + obj.push_back(Pair("subver", stats.cleanSubVer)); obj.push_back(Pair("inbound", stats.fInbound)); obj.push_back(Pair("startingheight", stats.nStartingHeight)); obj.push_back(Pair("banscore", stats.nMisbehavior)); diff --git a/src/rpcprotocol.cpp b/src/rpcprotocol.cpp new file mode 100644 index 0000000000..4a2241edaa --- /dev/null +++ b/src/rpcprotocol.cpp @@ -0,0 +1,262 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "rpcprotocol.h" + +#include "util.h" + +#include <stdint.h> + +#include <boost/algorithm/string.hpp> +#include <boost/asio.hpp> +#include <boost/asio/ssl.hpp> +#include <boost/bind.hpp> +#include <boost/filesystem.hpp> +#include <boost/foreach.hpp> +#include <boost/iostreams/concepts.hpp> +#include <boost/iostreams/stream.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> +#include "json/json_spirit_writer_template.h" + +using namespace std; +using namespace boost; +using namespace boost::asio; +using namespace json_spirit; + +// +// HTTP protocol +// +// This ain't Apache. We're just using HTTP header for the length field +// and to be compatible with other JSON-RPC implementations. +// + +string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders) +{ + ostringstream s; + s << "POST / HTTP/1.1\r\n" + << "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n" + << "Host: 127.0.0.1\r\n" + << "Content-Type: application/json\r\n" + << "Content-Length: " << strMsg.size() << "\r\n" + << "Connection: close\r\n" + << "Accept: application/json\r\n"; + BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders) + s << item.first << ": " << item.second << "\r\n"; + s << "\r\n" << strMsg; + + return s.str(); +} + +static string rfc1123Time() +{ + char buffer[64]; + time_t now; + time(&now); + struct tm* now_gmt = gmtime(&now); + string locale(setlocale(LC_TIME, NULL)); + setlocale(LC_TIME, "C"); // we want POSIX (aka "C") weekday/month strings + strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S +0000", now_gmt); + setlocale(LC_TIME, locale.c_str()); + return string(buffer); +} + +string HTTPReply(int nStatus, const string& strMsg, bool keepalive) +{ + if (nStatus == HTTP_UNAUTHORIZED) + return strprintf("HTTP/1.0 401 Authorization Required\r\n" + "Date: %s\r\n" + "Server: bitcoin-json-rpc/%s\r\n" + "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 296\r\n" + "\r\n" + "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n" + "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n" + "<HTML>\r\n" + "<HEAD>\r\n" + "<TITLE>Error</TITLE>\r\n" + "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n" + "</HEAD>\r\n" + "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n" + "</HTML>\r\n", rfc1123Time().c_str(), FormatFullVersion().c_str()); + const char *cStatus; + if (nStatus == HTTP_OK) cStatus = "OK"; + else if (nStatus == HTTP_BAD_REQUEST) cStatus = "Bad Request"; + else if (nStatus == HTTP_FORBIDDEN) cStatus = "Forbidden"; + else if (nStatus == HTTP_NOT_FOUND) cStatus = "Not Found"; + else if (nStatus == HTTP_INTERNAL_SERVER_ERROR) cStatus = "Internal Server Error"; + else cStatus = ""; + return strprintf( + "HTTP/1.1 %d %s\r\n" + "Date: %s\r\n" + "Connection: %s\r\n" + "Content-Length: %"PRIszu"\r\n" + "Content-Type: application/json\r\n" + "Server: bitcoin-json-rpc/%s\r\n" + "\r\n" + "%s", + nStatus, + cStatus, + rfc1123Time().c_str(), + keepalive ? "keep-alive" : "close", + strMsg.size(), + FormatFullVersion().c_str(), + strMsg.c_str()); +} + +bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, + string& http_method, string& http_uri) +{ + string str; + getline(stream, str); + + // HTTP request line is space-delimited + vector<string> vWords; + boost::split(vWords, str, boost::is_any_of(" ")); + if (vWords.size() < 2) + return false; + + // HTTP methods permitted: GET, POST + http_method = vWords[0]; + if (http_method != "GET" && http_method != "POST") + return false; + + // HTTP URI must be an absolute path, relative to current host + http_uri = vWords[1]; + if (http_uri.size() == 0 || http_uri[0] != '/') + return false; + + // parse proto, if present + string strProto = ""; + if (vWords.size() > 2) + strProto = vWords[2]; + + proto = 0; + const char *ver = strstr(strProto.c_str(), "HTTP/1."); + if (ver != NULL) + proto = atoi(ver+7); + + return true; +} + +int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto) +{ + string str; + getline(stream, str); + vector<string> vWords; + boost::split(vWords, str, boost::is_any_of(" ")); + if (vWords.size() < 2) + return HTTP_INTERNAL_SERVER_ERROR; + proto = 0; + const char *ver = strstr(str.c_str(), "HTTP/1."); + if (ver != NULL) + proto = atoi(ver+7); + return atoi(vWords[1].c_str()); +} + +int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet) +{ + int nLen = 0; + while (true) + { + string str; + std::getline(stream, str); + if (str.empty() || str == "\r") + break; + string::size_type nColon = str.find(":"); + if (nColon != string::npos) + { + string strHeader = str.substr(0, nColon); + boost::trim(strHeader); + boost::to_lower(strHeader); + string strValue = str.substr(nColon+1); + boost::trim(strValue); + mapHeadersRet[strHeader] = strValue; + if (strHeader == "content-length") + nLen = atoi(strValue.c_str()); + } + } + return nLen; +} + + +int ReadHTTPMessage(std::basic_istream<char>& stream, map<string, + string>& mapHeadersRet, string& strMessageRet, + int nProto) +{ + mapHeadersRet.clear(); + strMessageRet = ""; + + // Read header + int nLen = ReadHTTPHeaders(stream, mapHeadersRet); + if (nLen < 0 || nLen > (int)MAX_SIZE) + return HTTP_INTERNAL_SERVER_ERROR; + + // Read message + if (nLen > 0) + { + vector<char> vch(nLen); + stream.read(&vch[0], nLen); + strMessageRet = string(vch.begin(), vch.end()); + } + + string sConHdr = mapHeadersRet["connection"]; + + if ((sConHdr != "close") && (sConHdr != "keep-alive")) + { + if (nProto >= 1) + mapHeadersRet["connection"] = "keep-alive"; + else + mapHeadersRet["connection"] = "close"; + } + + return HTTP_OK; +} + +// +// JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, +// but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were +// unspecified (HTTP errors and contents of 'error'). +// +// 1.0 spec: http://json-rpc.org/wiki/specification +// 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http +// http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx +// + +string JSONRPCRequest(const string& strMethod, const Array& params, const Value& id) +{ + Object request; + request.push_back(Pair("method", strMethod)); + request.push_back(Pair("params", params)); + request.push_back(Pair("id", id)); + return write_string(Value(request), false) + "\n"; +} + +Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id) +{ + Object reply; + if (error.type() != null_type) + reply.push_back(Pair("result", Value::null)); + else + reply.push_back(Pair("result", result)); + reply.push_back(Pair("error", error)); + reply.push_back(Pair("id", id)); + return reply; +} + +string JSONRPCReply(const Value& result, const Value& error, const Value& id) +{ + Object reply = JSONRPCReplyObj(result, error, id); + return write_string(Value(reply), false) + "\n"; +} + +Object JSONRPCError(int code, const string& message) +{ + Object error; + error.push_back(Pair("code", code)); + error.push_back(Pair("message", message)); + return error; +} diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h new file mode 100644 index 0000000000..6bf371e759 --- /dev/null +++ b/src/rpcprotocol.h @@ -0,0 +1,138 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef _BITCOINRPC_PROTOCOL_H_ +#define _BITCOINRPC_PROTOCOL_H_ 1 + +#include <list> +#include <map> +#include <stdint.h> +#include <string> +#include <boost/iostreams/concepts.hpp> +#include <boost/iostreams/stream.hpp> +#include <boost/asio.hpp> +#include <boost/asio/ssl.hpp> + +#include "json/json_spirit_reader_template.h" +#include "json/json_spirit_utils.h" +#include "json/json_spirit_writer_template.h" + +// HTTP status codes +enum HTTPStatusCode +{ + HTTP_OK = 200, + HTTP_BAD_REQUEST = 400, + HTTP_UNAUTHORIZED = 401, + HTTP_FORBIDDEN = 403, + HTTP_NOT_FOUND = 404, + HTTP_INTERNAL_SERVER_ERROR = 500, +}; + +// Bitcoin RPC error codes +enum RPCErrorCode +{ + // Standard JSON-RPC 2.0 errors + RPC_INVALID_REQUEST = -32600, + RPC_METHOD_NOT_FOUND = -32601, + RPC_INVALID_PARAMS = -32602, + RPC_INTERNAL_ERROR = -32603, + RPC_PARSE_ERROR = -32700, + + // General application defined errors + RPC_MISC_ERROR = -1, // std::exception thrown in command handling + RPC_FORBIDDEN_BY_SAFE_MODE = -2, // Server is in safe mode, and command is not allowed in safe mode + RPC_TYPE_ERROR = -3, // Unexpected type was passed as parameter + RPC_INVALID_ADDRESS_OR_KEY = -5, // Invalid address or key + RPC_OUT_OF_MEMORY = -7, // Ran out of memory during operation + RPC_INVALID_PARAMETER = -8, // Invalid, missing or duplicate parameter + RPC_DATABASE_ERROR = -20, // Database error + RPC_DESERIALIZATION_ERROR = -22, // Error parsing or validating structure in raw format + RPC_SERVER_NOT_STARTED = -18, // RPC server was not started (StartRPCThreads() not called) + + // P2P client errors + RPC_CLIENT_NOT_CONNECTED = -9, // Bitcoin is not connected + 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 + + // Wallet errors + RPC_WALLET_ERROR = -4, // Unspecified problem with wallet (key not found etc.) + RPC_WALLET_INSUFFICIENT_FUNDS = -6, // Not enough funds in wallet or account + RPC_WALLET_INVALID_ACCOUNT_NAME = -11, // Invalid account name + RPC_WALLET_KEYPOOL_RAN_OUT = -12, // Keypool ran out, call keypoolrefill first + RPC_WALLET_UNLOCK_NEEDED = -13, // Enter the wallet passphrase with walletpassphrase first + RPC_WALLET_PASSPHRASE_INCORRECT = -14, // The wallet passphrase entered was incorrect + RPC_WALLET_WRONG_ENC_STATE = -15, // Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) + RPC_WALLET_ENCRYPTION_FAILED = -16, // Failed to encrypt the wallet + RPC_WALLET_ALREADY_UNLOCKED = -17, // Wallet is already unlocked +}; + +// +// IOStream device that speaks SSL but can also speak non-SSL +// +template <typename Protocol> +class SSLIOStreamDevice : public boost::iostreams::device<boost::iostreams::bidirectional> { +public: + SSLIOStreamDevice(boost::asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn) + { + fUseSSL = fUseSSLIn; + fNeedHandshake = fUseSSLIn; + } + + void handshake(boost::asio::ssl::stream_base::handshake_type role) + { + if (!fNeedHandshake) return; + fNeedHandshake = false; + stream.handshake(role); + } + std::streamsize read(char* s, std::streamsize n) + { + handshake(boost::asio::ssl::stream_base::server); // HTTPS servers read first + if (fUseSSL) return stream.read_some(boost::asio::buffer(s, n)); + return stream.next_layer().read_some(boost::asio::buffer(s, n)); + } + std::streamsize write(const char* s, std::streamsize n) + { + handshake(boost::asio::ssl::stream_base::client); // HTTPS clients write first + if (fUseSSL) return boost::asio::write(stream, boost::asio::buffer(s, n)); + return boost::asio::write(stream.next_layer(), boost::asio::buffer(s, n)); + } + bool connect(const std::string& server, const std::string& port) + { + boost::asio::ip::tcp::resolver resolver(stream.get_io_service()); + boost::asio::ip::tcp::resolver::query query(server.c_str(), port.c_str()); + boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); + boost::asio::ip::tcp::resolver::iterator end; + boost::system::error_code error = boost::asio::error::host_not_found; + while (error && endpoint_iterator != end) + { + stream.lowest_layer().close(); + stream.lowest_layer().connect(*endpoint_iterator++, error); + } + if (error) + return false; + return true; + } + +private: + bool fNeedHandshake; + bool fUseSSL; + boost::asio::ssl::stream<typename Protocol::socket>& stream; +}; + +std::string HTTPPost(const std::string& strMsg, const std::map<std::string,std::string>& mapRequestHeaders); +std::string HTTPReply(int nStatus, const std::string& strMsg, bool keepalive); +bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, + std::string& http_method, std::string& http_uri); +int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto); +int ReadHTTPHeaders(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet); +int ReadHTTPMessage(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet, + std::string& strMessageRet, int nProto); +std::string JSONRPCRequest(const std::string& strMethod, const json_spirit::Array& params, const json_spirit::Value& id); +json_spirit::Object JSONRPCReplyObj(const json_spirit::Value& result, const json_spirit::Value& error, const json_spirit::Value& id); +std::string JSONRPCReply(const json_spirit::Value& result, const json_spirit::Value& error, const json_spirit::Value& id); +json_spirit::Object JSONRPCError(int code, const std::string& message); + +#endif diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 2225e216d3..939ca96f76 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -3,10 +3,8 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - - #include "base58.h" -#include "bitcoinrpc.h" +#include "rpcserver.h" #include "init.h" #include "net.h" #include "uint256.h" diff --git a/src/bitcoinrpc.cpp b/src/rpcserver.cpp index a1e7d14dcc..c746d8c8fb 100644 --- a/src/bitcoinrpc.cpp +++ b/src/rpcserver.cpp @@ -3,7 +3,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "bitcoinrpc.h" +#include "rpcserver.h" #include "base58.h" #include "init.h" @@ -36,14 +36,6 @@ static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers; static ssl::context* rpc_ssl_context = NULL; static boost::thread_group* rpc_worker_group = NULL; -Object JSONRPCError(int code, const string& message) -{ - Object error; - error.push_back(Pair("code", code)); - error.push_back(Pair("message", message)); - return error; -} - void RPCTypeCheck(const Array& params, const list<Value_type>& typesExpected, bool fAllowNull) @@ -238,7 +230,7 @@ static const CRPCCommand vRPCCommands[] = { "getdifficulty", &getdifficulty, true, false, false }, { "getnetworkhashps", &getnetworkhashps, true, false, false }, { "getgenerate", &getgenerate, true, false, false }, - { "setgenerate", &setgenerate, true, false, true }, + { "setgenerate", &setgenerate, true, true, false }, { "gethashespersec", &gethashespersec, true, false, false }, { "getinfo", &getinfo, true, false, false }, { "getmininginfo", &getmininginfo, true, false, false }, @@ -318,194 +310,6 @@ const CRPCCommand *CRPCTable::operator[](string name) const return (*it).second; } -// -// HTTP protocol -// -// This ain't Apache. We're just using HTTP header for the length field -// and to be compatible with other JSON-RPC implementations. -// - -string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders) -{ - ostringstream s; - s << "POST / HTTP/1.1\r\n" - << "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n" - << "Host: 127.0.0.1\r\n" - << "Content-Type: application/json\r\n" - << "Content-Length: " << strMsg.size() << "\r\n" - << "Connection: close\r\n" - << "Accept: application/json\r\n"; - BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders) - s << item.first << ": " << item.second << "\r\n"; - s << "\r\n" << strMsg; - - return s.str(); -} - -string rfc1123Time() -{ - char buffer[64]; - time_t now; - time(&now); - struct tm* now_gmt = gmtime(&now); - string locale(setlocale(LC_TIME, NULL)); - setlocale(LC_TIME, "C"); // we want POSIX (aka "C") weekday/month strings - strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S +0000", now_gmt); - setlocale(LC_TIME, locale.c_str()); - return string(buffer); -} - -static string HTTPReply(int nStatus, const string& strMsg, bool keepalive) -{ - if (nStatus == HTTP_UNAUTHORIZED) - return strprintf("HTTP/1.0 401 Authorization Required\r\n" - "Date: %s\r\n" - "Server: bitcoin-json-rpc/%s\r\n" - "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" - "Content-Type: text/html\r\n" - "Content-Length: 296\r\n" - "\r\n" - "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n" - "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n" - "<HTML>\r\n" - "<HEAD>\r\n" - "<TITLE>Error</TITLE>\r\n" - "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n" - "</HEAD>\r\n" - "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n" - "</HTML>\r\n", rfc1123Time().c_str(), FormatFullVersion().c_str()); - const char *cStatus; - if (nStatus == HTTP_OK) cStatus = "OK"; - else if (nStatus == HTTP_BAD_REQUEST) cStatus = "Bad Request"; - else if (nStatus == HTTP_FORBIDDEN) cStatus = "Forbidden"; - else if (nStatus == HTTP_NOT_FOUND) cStatus = "Not Found"; - else if (nStatus == HTTP_INTERNAL_SERVER_ERROR) cStatus = "Internal Server Error"; - else cStatus = ""; - return strprintf( - "HTTP/1.1 %d %s\r\n" - "Date: %s\r\n" - "Connection: %s\r\n" - "Content-Length: %"PRIszu"\r\n" - "Content-Type: application/json\r\n" - "Server: bitcoin-json-rpc/%s\r\n" - "\r\n" - "%s", - nStatus, - cStatus, - rfc1123Time().c_str(), - keepalive ? "keep-alive" : "close", - strMsg.size(), - FormatFullVersion().c_str(), - strMsg.c_str()); -} - -bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, - string& http_method, string& http_uri) -{ - string str; - getline(stream, str); - - // HTTP request line is space-delimited - vector<string> vWords; - boost::split(vWords, str, boost::is_any_of(" ")); - if (vWords.size() < 2) - return false; - - // HTTP methods permitted: GET, POST - http_method = vWords[0]; - if (http_method != "GET" && http_method != "POST") - return false; - - // HTTP URI must be an absolute path, relative to current host - http_uri = vWords[1]; - if (http_uri.size() == 0 || http_uri[0] != '/') - return false; - - // parse proto, if present - string strProto = ""; - if (vWords.size() > 2) - strProto = vWords[2]; - - proto = 0; - const char *ver = strstr(strProto.c_str(), "HTTP/1."); - if (ver != NULL) - proto = atoi(ver+7); - - return true; -} - -int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto) -{ - string str; - getline(stream, str); - vector<string> vWords; - boost::split(vWords, str, boost::is_any_of(" ")); - if (vWords.size() < 2) - return HTTP_INTERNAL_SERVER_ERROR; - proto = 0; - const char *ver = strstr(str.c_str(), "HTTP/1."); - if (ver != NULL) - proto = atoi(ver+7); - return atoi(vWords[1].c_str()); -} - -int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet) -{ - int nLen = 0; - while (true) - { - string str; - std::getline(stream, str); - if (str.empty() || str == "\r") - break; - string::size_type nColon = str.find(":"); - if (nColon != string::npos) - { - string strHeader = str.substr(0, nColon); - boost::trim(strHeader); - boost::to_lower(strHeader); - string strValue = str.substr(nColon+1); - boost::trim(strValue); - mapHeadersRet[strHeader] = strValue; - if (strHeader == "content-length") - nLen = atoi(strValue.c_str()); - } - } - return nLen; -} - -int ReadHTTPMessage(std::basic_istream<char>& stream, map<string, - string>& mapHeadersRet, string& strMessageRet, - int nProto) -{ - mapHeadersRet.clear(); - strMessageRet = ""; - - // Read header - int nLen = ReadHTTPHeaders(stream, mapHeadersRet); - if (nLen < 0 || nLen > (int)MAX_SIZE) - return HTTP_INTERNAL_SERVER_ERROR; - - // Read message - if (nLen > 0) - { - vector<char> vch(nLen); - stream.read(&vch[0], nLen); - strMessageRet = string(vch.begin(), vch.end()); - } - - string sConHdr = mapHeadersRet["connection"]; - - if ((sConHdr != "close") && (sConHdr != "keep-alive")) - { - if (nProto >= 1) - mapHeadersRet["connection"] = "keep-alive"; - else - mapHeadersRet["connection"] = "close"; - } - - return HTTP_OK; -} bool HTTPAuthorized(map<string, string>& mapHeaders) { @@ -517,43 +321,6 @@ bool HTTPAuthorized(map<string, string>& mapHeaders) return TimingResistantEqual(strUserPass, strRPCUserColonPass); } -// -// JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, -// but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were -// unspecified (HTTP errors and contents of 'error'). -// -// 1.0 spec: http://json-rpc.org/wiki/specification -// 1.2 spec: http://groups.google.com/group/json-rpc/web/json-rpc-over-http -// http://www.codeproject.com/KB/recipes/JSON_Spirit.aspx -// - -string JSONRPCRequest(const string& strMethod, const Array& params, const Value& id) -{ - Object request; - request.push_back(Pair("method", strMethod)); - request.push_back(Pair("params", params)); - request.push_back(Pair("id", id)); - return write_string(Value(request), false) + "\n"; -} - -Object JSONRPCReplyObj(const Value& result, const Value& error, const Value& id) -{ - Object reply; - if (error.type() != null_type) - reply.push_back(Pair("result", Value::null)); - else - reply.push_back(Pair("result", result)); - reply.push_back(Pair("error", error)); - reply.push_back(Pair("id", id)); - return reply; -} - -string JSONRPCReply(const Value& result, const Value& error, const Value& id) -{ - Object reply = JSONRPCReplyObj(result, error, id); - return write_string(Value(reply), false) + "\n"; -} - void ErrorReply(std::ostream& stream, const Object& objError, const Value& id) { // Send error reply from json-rpc error object @@ -588,59 +355,6 @@ bool ClientAllowed(const boost::asio::ip::address& address) return false; } -// -// IOStream device that speaks SSL but can also speak non-SSL -// -template <typename Protocol> -class SSLIOStreamDevice : public iostreams::device<iostreams::bidirectional> { -public: - SSLIOStreamDevice(asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn) - { - fUseSSL = fUseSSLIn; - fNeedHandshake = fUseSSLIn; - } - - void handshake(ssl::stream_base::handshake_type role) - { - if (!fNeedHandshake) return; - fNeedHandshake = false; - stream.handshake(role); - } - std::streamsize read(char* s, std::streamsize n) - { - handshake(ssl::stream_base::server); // HTTPS servers read first - if (fUseSSL) return stream.read_some(asio::buffer(s, n)); - return stream.next_layer().read_some(asio::buffer(s, n)); - } - std::streamsize write(const char* s, std::streamsize n) - { - handshake(ssl::stream_base::client); // HTTPS clients write first - if (fUseSSL) return asio::write(stream, asio::buffer(s, n)); - return asio::write(stream.next_layer(), asio::buffer(s, n)); - } - bool connect(const std::string& server, const std::string& port) - { - ip::tcp::resolver resolver(stream.get_io_service()); - ip::tcp::resolver::query query(server.c_str(), port.c_str()); - ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); - ip::tcp::resolver::iterator end; - boost::system::error_code error = asio::error::host_not_found; - while (error && endpoint_iterator != end) - { - stream.lowest_layer().close(); - stream.lowest_layer().connect(*endpoint_iterator++, error); - } - if (error) - return false; - return true; - } - -private: - bool fNeedHandshake; - bool fUseSSL; - asio::ssl::stream<typename Protocol::socket>& stream; -}; - class AcceptedConnection { public: @@ -720,6 +434,7 @@ static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketA boost::asio::placeholders::error)); } + /** * Accept and handle incoming connection. */ @@ -910,7 +625,6 @@ void RPCRunLater(const std::string& name, boost::function<void(void)> func, int6 deadlineTimers[name]->async_wait(boost::bind(RPCRunHandler, _1, func)); } - class JSONRequest { public: @@ -952,6 +666,7 @@ void JSONRequest::parse(const Value& valRequest) throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); } + static Object JSONRPCExecOne(const Value& req) { Object rpc_result; @@ -1105,263 +820,4 @@ json_spirit::Value CRPCTable::execute(const std::string &strMethod, const json_s } } - -Object CallRPC(const string& strMethod, const Array& params) -{ - if (mapArgs["-rpcuser"] == "" && mapArgs["-rpcpassword"] == "") - throw runtime_error(strprintf( - _("You must set rpcpassword=<password> in the configuration file:\n%s\n" - "If the file does not exist, create it with owner-readable-only file permissions."), - GetConfigFile().string().c_str())); - - // Connect to localhost - bool fUseSSL = GetBoolArg("-rpcssl", false); - asio::io_service io_service; - ssl::context context(io_service, ssl::context::sslv23); - context.set_options(ssl::context::no_sslv2); - asio::ssl::stream<asio::ip::tcp::socket> sslStream(io_service, context); - SSLIOStreamDevice<asio::ip::tcp> d(sslStream, fUseSSL); - iostreams::stream< SSLIOStreamDevice<asio::ip::tcp> > stream(d); - - bool fWait = GetBoolArg("-rpcwait", false); // -rpcwait means try until server has started - do { - bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(Params().RPCPort()))); - if (fConnected) break; - if (fWait) - MilliSleep(1000); - else - throw runtime_error("couldn't connect to server"); - } while (fWait); - - // HTTP basic authentication - string strUserPass64 = EncodeBase64(mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]); - map<string, string> mapRequestHeaders; - mapRequestHeaders["Authorization"] = string("Basic ") + strUserPass64; - - // Send request - string strRequest = JSONRPCRequest(strMethod, params, 1); - string strPost = HTTPPost(strRequest, mapRequestHeaders); - stream << strPost << std::flush; - - // Receive HTTP reply status - int nProto = 0; - int nStatus = ReadHTTPStatus(stream, nProto); - - // Receive HTTP reply message headers and body - map<string, string> mapHeaders; - string strReply; - ReadHTTPMessage(stream, mapHeaders, strReply, nProto); - - if (nStatus == HTTP_UNAUTHORIZED) - throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); - else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR) - throw runtime_error(strprintf("server returned HTTP error %d", nStatus)); - else if (strReply.empty()) - throw runtime_error("no response from server"); - - // Parse reply - Value valReply; - if (!read_string(strReply, valReply)) - throw runtime_error("couldn't parse reply from server"); - const Object& reply = valReply.get_obj(); - if (reply.empty()) - throw runtime_error("expected reply to have result, error and id properties"); - - return reply; -} - - - - -template<typename T> -void ConvertTo(Value& value, bool fAllowNull=false) -{ - if (fAllowNull && value.type() == null_type) - return; - if (value.type() == str_type) - { - // reinterpret string as unquoted json value - Value value2; - string strJSON = value.get_str(); - if (!read_string(strJSON, value2)) - throw runtime_error(string("Error parsing JSON:")+strJSON); - ConvertTo<T>(value2, fAllowNull); - value = value2; - } - else - { - value = value.get_value<T>(); - } -} - -// Convert strings to command-specific RPC representation -Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams) -{ - Array params; - BOOST_FOREACH(const std::string ¶m, strParams) - params.push_back(param); - - int n = params.size(); - - // - // Special case non-string parameter types - // - if (strMethod == "stop" && n > 0) ConvertTo<bool>(params[0]); - if (strMethod == "getaddednodeinfo" && n > 0) ConvertTo<bool>(params[0]); - if (strMethod == "setgenerate" && n > 0) ConvertTo<bool>(params[0]); - if (strMethod == "setgenerate" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "getnetworkhashps" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "getnetworkhashps" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); - if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); - if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo<bool>(params[1]); - if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo<bool>(params[1]); - if (strMethod == "getbalance" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "getblockhash" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "move" && n > 2) ConvertTo<double>(params[2]); - if (strMethod == "move" && n > 3) ConvertTo<boost::int64_t>(params[3]); - if (strMethod == "sendfrom" && n > 2) ConvertTo<double>(params[2]); - if (strMethod == "sendfrom" && n > 3) ConvertTo<boost::int64_t>(params[3]); - if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); - if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "getblocktemplate" && n > 0) ConvertTo<Object>(params[0]); - if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "sendmany" && n > 1) ConvertTo<Object>(params[1]); - if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]); - if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "addmultisigaddress" && n > 1) ConvertTo<Array>(params[1]); - if (strMethod == "createmultisig" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "createmultisig" && n > 1) ConvertTo<Array>(params[1]); - if (strMethod == "listunspent" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "listunspent" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listunspent" && n > 2) ConvertTo<Array>(params[2]); - if (strMethod == "getblock" && n > 1) ConvertTo<bool>(params[1]); - if (strMethod == "getrawtransaction" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "createrawtransaction" && n > 0) ConvertTo<Array>(params[0]); - if (strMethod == "createrawtransaction" && n > 1) ConvertTo<Object>(params[1]); - if (strMethod == "signrawtransaction" && n > 1) ConvertTo<Array>(params[1], true); - if (strMethod == "signrawtransaction" && n > 2) ConvertTo<Array>(params[2], true); - if (strMethod == "sendrawtransaction" && n > 1) ConvertTo<bool>(params[1], true); - if (strMethod == "gettxout" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "gettxout" && n > 2) ConvertTo<bool>(params[2]); - if (strMethod == "lockunspent" && n > 0) ConvertTo<bool>(params[0]); - if (strMethod == "lockunspent" && n > 1) ConvertTo<Array>(params[1]); - if (strMethod == "importprivkey" && n > 2) ConvertTo<bool>(params[2]); - if (strMethod == "verifychain" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "verifychain" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "keypoolrefill" && n > 0) ConvertTo<boost::int64_t>(params[0]); - - return params; -} - -int CommandLineRPC(int argc, char *argv[]) -{ - string strPrint; - int nRet = 0; - try - { - // Skip switches - while (argc > 1 && IsSwitchChar(argv[1][0])) - { - argc--; - argv++; - } - - // Method - if (argc < 2) - throw runtime_error("too few parameters"); - string strMethod = argv[1]; - - // Parameters default to strings - std::vector<std::string> strParams(&argv[2], &argv[argc]); - Array params = RPCConvertValues(strMethod, strParams); - - // Execute - Object reply = CallRPC(strMethod, params); - - // Parse reply - const Value& result = find_value(reply, "result"); - const Value& error = find_value(reply, "error"); - - if (error.type() != null_type) - { - // Error - strPrint = "error: " + write_string(error, false); - int code = find_value(error.get_obj(), "code").get_int(); - nRet = abs(code); - } - else - { - // Result - if (result.type() == null_type) - strPrint = ""; - else if (result.type() == str_type) - strPrint = result.get_str(); - else - strPrint = write_string(result, true); - } - } - catch (boost::thread_interrupted) { - throw; - } - catch (std::exception& e) { - strPrint = string("error: ") + e.what(); - nRet = 87; - } - catch (...) { - PrintException(NULL, "CommandLineRPC()"); - } - - if (strPrint != "") - { - fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); - } - return nRet; -} - - - - -#ifdef TEST -int main(int argc, char *argv[]) -{ -#ifdef _MSC_VER - // Turn off Microsoft heap dump noise - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); - _CrtSetReportFile(_CRT_WARN, CreateFile("NUL", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0)); -#endif - setbuf(stdin, NULL); - setbuf(stdout, NULL); - setbuf(stderr, NULL); - - try - { - if (argc >= 2 && string(argv[1]) == "-server") - { - LogPrintf("server ready\n"); - ThreadRPCServer(NULL); - } - else - { - return CommandLineRPC(argc, argv); - } - } - catch (boost::thread_interrupted) { - throw; - } - catch (std::exception& e) { - PrintException(&e, "main()"); - } catch (...) { - PrintException(NULL, "main()"); - } - return 0; -} -#endif - const CRPCTable tableRPC; diff --git a/src/bitcoinrpc.h b/src/rpcserver.h index 9025ff9216..4d29e90c09 100644 --- a/src/bitcoinrpc.h +++ b/src/rpcserver.h @@ -3,10 +3,11 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef _BITCOINRPC_H_ -#define _BITCOINRPC_H_ 1 +#ifndef _BITCOINRPC_SERVER_H_ +#define _BITCOINRPC_SERVER_H_ 1 #include "uint256.h" +#include "rpcprotocol.h" #include <list> #include <map> @@ -18,66 +19,9 @@ #include "json/json_spirit_writer_template.h" class CBlockIndex; -class CReserveKey; - -// HTTP status codes -enum HTTPStatusCode -{ - HTTP_OK = 200, - HTTP_BAD_REQUEST = 400, - HTTP_UNAUTHORIZED = 401, - HTTP_FORBIDDEN = 403, - HTTP_NOT_FOUND = 404, - HTTP_INTERNAL_SERVER_ERROR = 500, -}; - -// Bitcoin RPC error codes -enum RPCErrorCode -{ - // Standard JSON-RPC 2.0 errors - RPC_INVALID_REQUEST = -32600, - RPC_METHOD_NOT_FOUND = -32601, - RPC_INVALID_PARAMS = -32602, - RPC_INTERNAL_ERROR = -32603, - RPC_PARSE_ERROR = -32700, - - // General application defined errors - RPC_MISC_ERROR = -1, // std::exception thrown in command handling - RPC_FORBIDDEN_BY_SAFE_MODE = -2, // Server is in safe mode, and command is not allowed in safe mode - RPC_TYPE_ERROR = -3, // Unexpected type was passed as parameter - RPC_INVALID_ADDRESS_OR_KEY = -5, // Invalid address or key - RPC_OUT_OF_MEMORY = -7, // Ran out of memory during operation - RPC_INVALID_PARAMETER = -8, // Invalid, missing or duplicate parameter - RPC_DATABASE_ERROR = -20, // Database error - RPC_DESERIALIZATION_ERROR = -22, // Error parsing or validating structure in raw format - RPC_SERVER_NOT_STARTED = -18, // RPC server was not started (StartRPCThreads() not called) - - // P2P client errors - RPC_CLIENT_NOT_CONNECTED = -9, // Bitcoin is not connected - 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 - - // Wallet errors - RPC_WALLET_ERROR = -4, // Unspecified problem with wallet (key not found etc.) - RPC_WALLET_INSUFFICIENT_FUNDS = -6, // Not enough funds in wallet or account - RPC_WALLET_INVALID_ACCOUNT_NAME = -11, // Invalid account name - RPC_WALLET_KEYPOOL_RAN_OUT = -12, // Keypool ran out, call keypoolrefill first - RPC_WALLET_UNLOCK_NEEDED = -13, // Enter the wallet passphrase with walletpassphrase first - RPC_WALLET_PASSPHRASE_INCORRECT = -14, // The wallet passphrase entered was incorrect - RPC_WALLET_WRONG_ENC_STATE = -15, // Command given in wrong wallet encryption state (encrypting an encrypted wallet etc.) - RPC_WALLET_ENCRYPTION_FAILED = -16, // Failed to encrypt the wallet - RPC_WALLET_ALREADY_UNLOCKED = -17, // Wallet is already unlocked -}; - -json_spirit::Object JSONRPCError(int code, const std::string& message); void StartRPCThreads(); void StopRPCThreads(); -int CommandLineRPC(int argc, char *argv[]); - -/** Convert parameter values for RPC call from strings to command-specific JSON objects. */ -json_spirit::Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams); /* Type-check arguments; throws JSONRPCError if wrong type given. Does not check that diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 8b14c0aca9..1fd061547e 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -3,10 +3,8 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - - #include "base58.h" -#include "bitcoinrpc.h" +#include "rpcserver.h" #include "init.h" #include "net.h" #include "netbase.h" diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 4fb2aeb6ae..76580bae43 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -1,4 +1,5 @@ -#include "bitcoinrpc.h" +#include "rpcserver.h" +#include "rpcclient.h" #include "base58.h" |