diff options
40 files changed, 1214 insertions, 793 deletions
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/coins.cpp b/src/coins.cpp index ed82c9df8b..86b2a6ef17 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -178,3 +178,19 @@ bool CCoinsViewCache::HaveInputs(const CTransaction& tx) } return true; } + +double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) +{ + if (tx.IsCoinBase()) + return 0.0; + double dResult = 0.0; + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + const CCoins &coins = GetCoins(txin.prevout.hash); + if (!coins.IsAvailable(txin.prevout.n)) continue; + if (coins.nHeight < nHeight) { + dResult += coins.vout[txin.prevout.n].nValue * (nHeight-coins.nHeight); + } + } + return tx.ComputePriority(dResult); +} diff --git a/src/coins.h b/src/coins.h index 2c72ee88e1..0ad28524a1 100644 --- a/src/coins.h +++ b/src/coins.h @@ -340,13 +340,15 @@ public: @param[in] tx transaction for which we are checking input total @return Sum of value of all inputs (scriptSigs) - @see CTransaction::FetchInputs */ int64_t GetValueIn(const CTransaction& tx); // Check whether all prevouts of the transaction are present in the UTXO set represented by this view bool HaveInputs(const CTransaction& tx); + // Return priority of tx at height nHeight + double GetPriority(const CTransaction &tx, int nHeight); + const CTxOut &GetOutputFor(const CTxIn& input); private: diff --git a/src/compat.h b/src/compat.h index 1deef493c2..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 @@ -26,6 +28,7 @@ #include <windows.h> #include <ws2tcpip.h> #else +#include <sys/types.h> #include <arpa/inet.h> #include <ifaddrs.h> #include <limits.h> @@ -35,7 +38,6 @@ #include <sys/fcntl.h> #include <sys/mman.h> #include <sys/socket.h> -#include <sys/types.h> #include <unistd.h> #endif diff --git a/src/core.cpp b/src/core.cpp index 7a1c90e587..f41ea87fea 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -106,6 +106,37 @@ bool CTransaction::IsNewerThan(const CTransaction& old) const return fNewer; } +int64_t CTransaction::GetValueOut() const +{ + int64_t nValueOut = 0; + BOOST_FOREACH(const CTxOut& txout, vout) + { + nValueOut += txout.nValue; + if (!MoneyRange(txout.nValue) || !MoneyRange(nValueOut)) + throw std::runtime_error("CTransaction::GetValueOut() : value out of range"); + } + return nValueOut; +} + +double CTransaction::ComputePriority(double dPriorityInputs, unsigned int nTxSize) const +{ + // In order to avoid disincentivizing cleaning up the UTXO set we don't count + // the constant overhead for each txin and up to 110 bytes of scriptSig (which + // is enough to cover a compressed pubkey p2sh redemption) for priority. + // Providing any more cleanup incentive than making additional inputs free would + // risk encouraging people to create junk outputs to redeem later. + if (nTxSize == 0) + nTxSize = ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION); + BOOST_FOREACH(const CTxIn& txin, vin) + { + unsigned int offset = 41U + std::min(110U, (unsigned int)txin.scriptSig.size()); + if (nTxSize > offset) + nTxSize -= offset; + } + if (nTxSize == 0) return 0.0; + return dPriorityInputs / nTxSize; +} + std::string CTransaction::ToString() const { std::string str; diff --git a/src/core.h b/src/core.h index e31a7e6582..e61cad90ec 100644 --- a/src/core.h +++ b/src/core.h @@ -14,6 +14,10 @@ class CTransaction; +/** No amount larger than this (in satoshi) is valid */ +static const int64_t MAX_MONEY = 21000000 * COIN; +inline bool MoneyRange(int64_t nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); } + /** An outpoint - a combination of a transaction hash and an index n into its vout */ class COutPoint { @@ -50,11 +54,11 @@ public: class CInPoint { public: - CTransaction* ptx; + const CTransaction* ptx; unsigned int n; CInPoint() { SetNull(); } - CInPoint(CTransaction* ptxIn, unsigned int nIn) { ptx = ptxIn; n = nIn; } + CInPoint(const CTransaction* ptxIn, unsigned int nIn) { ptx = ptxIn; n = nIn; } void SetNull() { ptx = NULL; n = (unsigned int) -1; } bool IsNull() const { return (ptx == NULL && n == (unsigned int) -1); } }; @@ -217,6 +221,14 @@ public: uint256 GetHash() const; bool IsNewerThan(const CTransaction& old) const; + // Return sum of txouts. + int64_t GetValueOut() const; + // GetValueIn() is a method on CCoinsViewCache, because + // inputs must be known to compute value in. + + // Compute priority, given priority of inputs and (optionally) tx size + double ComputePriority(double dPriorityInputs, unsigned int nTxSize=0) const; + bool IsCoinBase() const { return (vin.size() == 1 && vin[0].prevout.IsNull()); 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 705e94fabb..457fc941e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -379,21 +379,6 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) return true; } -/** Amount of bitcoins spent by the transaction. - @return sum of all outputs (note: does not include fees) - */ -int64_t GetValueOut(const CTransaction& tx) -{ - int64_t nValueOut = 0; - BOOST_FOREACH(const CTxOut& txout, tx.vout) - { - nValueOut += txout.nValue; - if (!MoneyRange(txout.nValue) || !MoneyRange(nValueOut)) - throw std::runtime_error("GetValueOut() : value out of range"); - } - return nValueOut; -} - // // Check transaction inputs, and make sure any // pay-to-script-hash transactions are evaluating IsStandard scripts @@ -660,7 +645,6 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return false; // Check for conflicts with in-memory transactions - CTransaction* ptxOld = NULL; { LOCK(pool.cs); // protect pool.mapNextTx for (unsigned int i = 0; i < tx.vin.size(); i++) @@ -670,22 +654,6 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa { // Disable replacement feature for now return false; - - // Allow replacing with a newer version of the same transaction - if (i != 0) - return false; - ptxOld = pool.mapNextTx[outpoint].ptx; - if (IsFinalTx(*ptxOld)) - return false; - if (!tx.IsNewerThan(*ptxOld)) - return false; - for (unsigned int i = 0; i < tx.vin.size(); i++) - { - COutPoint outpoint = tx.vin[i].prevout; - if (!pool.mapNextTx.count(outpoint) || pool.mapNextTx[outpoint].ptx != ptxOld) - return false; - } - break; } } } @@ -734,8 +702,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // you should add code here to check that the transaction does a // reasonable number of ECDSA signature verifications. - int64_t nFees = view.GetValueIn(tx)-GetValueOut(tx); - unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); + int64_t nValueIn = view.GetValueIn(tx); + int64_t nValueOut = tx.GetValueOut(); + int64_t nFees = nValueIn-nValueOut; + double dPriority = view.GetPriority(tx, chainActive.Height()); + + CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height()); + unsigned int nSize = entry.GetTxSize(); // Don't accept it if it can't get into a block int64_t txMinFee = GetMinFee(tx, nSize, true, GMF_RELAY); @@ -779,27 +752,12 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa { return error("AcceptToMemoryPool: : ConnectInputs failed %s", hash.ToString().c_str()); } + // Store transaction in memory + pool.addUnchecked(hash, entry); } - // Store transaction in memory - { - if (ptxOld) - { - LogPrint("mempool", "AcceptToMemoryPool: : replacing tx %s with new version\n", ptxOld->GetHash().ToString().c_str()); - pool.remove(*ptxOld); - } - pool.addUnchecked(hash, tx); - } - - ///// are we sure this is ok when loading transactions or restoring block txes - // If updated, erase old tx from wallet - if (ptxOld) - 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; } @@ -1373,12 +1331,12 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, CCoinsViewCach } - if (nValueIn < GetValueOut(tx)) + if (nValueIn < tx.GetValueOut()) return state.DoS(100, error("CheckInputs() : %s value in < value out", tx.GetHash().ToString().c_str()), REJECT_INVALID, "in < out"); // Tally transaction fees - int64_t nTxFee = nValueIn - GetValueOut(tx); + int64_t nTxFee = nValueIn - tx.GetValueOut(); if (nTxFee < 0) return state.DoS(100, error("CheckInputs() : %s nTxFee < 0", tx.GetHash().ToString().c_str()), REJECT_INVALID, "fee < 0"); @@ -1631,7 +1589,7 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C REJECT_INVALID, "too many sigops"); } - nFees += view.GetValueIn(tx)-GetValueOut(tx); + nFees += view.GetValueIn(tx)-tx.GetValueOut(); std::vector<CScriptCheck> vChecks; if (!CheckInputs(tx, state, view, fScriptChecks, flags, nScriptCheckThreads ? &vChecks : NULL)) @@ -1651,10 +1609,10 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C if (fBenchmark) LogPrintf("- Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin)\n", (unsigned)block.vtx.size(), 0.001 * nTime, 0.001 * nTime / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * nTime / (nInputs-1)); - if (GetValueOut(block.vtx[0]) > GetBlockValue(pindex->nHeight, nFees)) + if (block.vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees)) return state.DoS(100, error("ConnectBlock() : coinbase pays too much (actual=%"PRId64" vs limit=%"PRId64")", - GetValueOut(block.vtx[0]), GetBlockValue(pindex->nHeight, nFees)), + block.vtx[0].GetValueOut(), GetBlockValue(pindex->nHeight, nFees)), REJECT_INVALID, "coinbase too large"); if (!control.Wait()) @@ -3100,8 +3058,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 +3128,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 +3387,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 +3441,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 +3581,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..c4e1839443 100644 --- a/src/main.h +++ b/src/main.h @@ -49,9 +49,6 @@ static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB /** The pre-allocation chunk size for rev?????.dat files (since 0.8) */ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB -/** No amount larger than this (in satoshi) is valid */ -static const int64_t MAX_MONEY = 21000000 * COIN; -inline bool MoneyRange(int64_t nValue) { return (nValue >= 0 && nValue <= MAX_MONEY); } /** Coinbase transaction outputs can only be spent after this number of new blocks (network rule) */ static const int COINBASE_MATURITY = 100; /** Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp. */ @@ -320,11 +317,6 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason); bool IsFinalTx(const CTransaction &tx, int nBlockHeight = 0, int64_t nBlockTime = 0); -/** Amount of bitcoins spent by the transaction. - @return sum of all outputs (note: does not include fees) - */ -int64_t GetValueOut(const CTransaction& tx); - /** Undo information for a CBlock */ class CBlockUndo { @@ -937,7 +929,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..ecc40ac708 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -93,12 +93,12 @@ unsigned int static ScanHash_CryptoPP(char* pmidstate, char* pdata, char* phash1 class COrphan { public: - CTransaction* ptx; + const CTransaction* ptx; set<uint256> setDependsOn; double dPriority; double dFeePerKb; - COrphan(CTransaction* ptxIn) + COrphan(const CTransaction* ptxIn) { ptx = ptxIn; dPriority = dFeePerKb = 0; @@ -118,7 +118,7 @@ uint64_t nLastBlockTx = 0; uint64_t nLastBlockSize = 0; // We want to sort transactions by priority and fee, so: -typedef boost::tuple<double, double, CTransaction*> TxPriority; +typedef boost::tuple<double, double, const CTransaction*> TxPriority; class TxPriorityCompare { bool byFee; @@ -191,9 +191,10 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) // This vector will be sorted into a priority queue: vector<TxPriority> vecPriority; vecPriority.reserve(mempool.mapTx.size()); - for (map<uint256, CTransaction>::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi) + for (map<uint256, CTxMemPoolEntry>::iterator mi = mempool.mapTx.begin(); + mi != mempool.mapTx.end(); ++mi) { - CTransaction& tx = (*mi).second; + const CTransaction& tx = mi->second.GetTx(); if (tx.IsCoinBase() || !IsFinalTx(tx)) continue; @@ -228,7 +229,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) } mapDependers[txin.prevout.hash].push_back(porphan); porphan->setDependsOn.insert(txin.prevout.hash); - nTotalIn += mempool.mapTx[txin.prevout.hash].vout[txin.prevout.n].nValue; + nTotalIn += mempool.mapTx[txin.prevout.hash].GetTx().vout[txin.prevout.n].nValue; continue; } const CCoins &coins = view.GetCoins(txin.prevout.hash); @@ -244,24 +245,12 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) // Priority is sum(valuein * age) / modified_txsize unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); - unsigned int nTxSizeMod = nTxSize; - // In order to avoid disincentivizing cleaning up the UTXO set we don't count - // the constant overhead for each txin and up to 110 bytes of scriptSig (which - // is enough to cover a compressed pubkey p2sh redemption) for priority. - // Providing any more cleanup incentive than making additional inputs free would - // risk encouraging people to create junk outputs to redeem later. - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { - unsigned int offset = 41U + min(110U, (unsigned int)txin.scriptSig.size()); - if (nTxSizeMod > offset) - nTxSizeMod -= offset; - } - dPriority /= nTxSizeMod; + dPriority = tx.ComputePriority(dPriority, nTxSize); // This is a more accurate fee-per-kilobyte than is used by the client code, because the // client code rounds up the size to the nearest 1K. That's good, because it gives an // incentive to create smaller transactions. - double dFeePerKb = double(nTotalIn-GetValueOut(tx)) / (double(nTxSize)/1000.0); + double dFeePerKb = double(nTotalIn-tx.GetValueOut()) / (double(nTxSize)/1000.0); if (porphan) { @@ -269,7 +258,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) porphan->dFeePerKb = dFeePerKb; } else - vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &(*mi).second)); + vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &mi->second.GetTx())); } // Collect transactions into block @@ -286,7 +275,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) // Take highest priority transaction off the priority queue: double dPriority = vecPriority.front().get<0>(); double dFeePerKb = vecPriority.front().get<1>(); - CTransaction& tx = *(vecPriority.front().get<2>()); + const CTransaction& tx = *(vecPriority.front().get<2>()); std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer); vecPriority.pop_back(); @@ -318,7 +307,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) if (!view.HaveInputs(tx)) continue; - int64_t nTxFees = view.GetValueIn(tx)-GetValueOut(tx); + int64_t nTxFees = view.GetValueIn(tx)-tx.GetValueOut(); nTxSigOps += GetP2SHSigOpCount(tx, view); if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) @@ -650,11 +639,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/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 8bd2bf564a..c0c4d53732 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -194,7 +194,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, int vout, int u strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nValue) + "<br>"; } - int64_t nTxFee = nDebit - GetValueOut(wtx); + int64_t nTxFee = nDebit - wtx.GetValueOut(); if (nTxFee > 0) strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -nTxFee) + "<br>"; } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 675daa9c9c..6823557ebc 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -95,7 +95,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * // // Debit // - int64_t nTxFee = nDebit - GetValueOut(wtx); + int64_t nTxFee = nDebit - wtx.GetValueOut(); for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) { diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 6f0b353e34..34ae6e0543 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" @@ -155,28 +153,79 @@ Value settxfee(const Array& params, bool fHelp) Value getrawmempool(const Array& params, bool fHelp) { - if (fHelp || params.size() != 0) + if (fHelp || params.size() > 1) throw runtime_error( - "getrawmempool\n" + "getrawmempool ( verbose )\n" "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" - "\nResult:\n" - "[ (json array of string)\n" + "\nArguments:\n" + "1. verbose (boolean, optional, default=false) true for a json object, false for array of transaction ids\n" + "\nResult: (for verbose = false):\n" + "[ (json array of string)\n" " \"transactionid\" (string) The transaction id\n" " ,...\n" "]\n" + "\nResult: (for verbose = true):\n" + "{ (json object)\n" + " \"transactionid\" : { (json object)\n" + " \"size\" : n, (numeric) transaction size in bytes\n" + " \"fee\" : n, (numeric) transaction fee in bitcoins\n" + " \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n" + " \"height\" : n, (numeric) block height when transaction entered pool\n" + " \"startingpriority\" : n, (numeric) priority when transaction entered pool\n" + " \"currentpriority\" : n, (numeric) transaction priority now\n" + " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n" + " \"transactionid\", (string) parent transaction id\n" + " ... ]\n" + " }, ...\n" + "]\n" "\nExamples\n" - + HelpExampleCli("getrawmempool", "") - + HelpExampleRpc("getrawmempool", "") + + HelpExampleCli("getrawmempool", "true") + + HelpExampleRpc("getrawmempool", "true") ); - vector<uint256> vtxid; - mempool.queryHashes(vtxid); + bool fVerbose = false; + if (params.size() > 0) + fVerbose = params[0].get_bool(); + + if (fVerbose) + { + LOCK(mempool.cs); + Object o; + BOOST_FOREACH(const PAIRTYPE(uint256, CTxMemPoolEntry)& entry, mempool.mapTx) + { + const uint256& hash = entry.first; + const CTxMemPoolEntry& e = entry.second; + Object info; + info.push_back(Pair("size", (int)e.GetTxSize())); + info.push_back(Pair("fee", ValueFromAmount(e.GetFee()))); + info.push_back(Pair("time", (boost::int64_t)e.GetTime())); + info.push_back(Pair("height", (int)e.GetHeight())); + info.push_back(Pair("startingpriority", e.GetPriority(e.GetHeight()))); + info.push_back(Pair("currentpriority", e.GetPriority(chainActive.Height()))); + const CTransaction& tx = e.GetTx(); + set<string> setDepends; + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + if (mempool.exists(txin.prevout.hash)) + setDepends.insert(txin.prevout.hash.ToString()); + } + Array depends(setDepends.begin(), setDepends.end()); + info.push_back(Pair("depends", depends)); + o.push_back(Pair(hash.ToString(), info)); + } + return o; + } + else + { + vector<uint256> vtxid; + mempool.queryHashes(vtxid); - Array a; - BOOST_FOREACH(const uint256& hash, vtxid) - a.push_back(hash.ToString()); + Array a; + BOOST_FOREACH(const uint256& hash, vtxid) + a.push_back(hash.ToString()); - return a; + return a; + } } Value getblockhash(const Array& params, bool fHelp) diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp new file mode 100644 index 0000000000..f571ca52d6 --- /dev/null +++ b/src/rpcclient.cpp @@ -0,0 +1,247 @@ +// 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]); + if (strMethod == "getrawmempool" && n > 0) ConvertTo<bool>(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..b4e522de8f 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" @@ -1675,7 +1673,7 @@ Value gettransaction(const Array& params, bool fHelp) int64_t nCredit = wtx.GetCredit(); int64_t nDebit = wtx.GetDebit(); int64_t nNet = nCredit - nDebit; - int64_t nFee = (wtx.IsFromMe() ? GetValueOut(wtx) - nDebit : 0); + int64_t nFee = (wtx.IsFromMe() ? wtx.GetValueOut() - nDebit : 0); entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); if (wtx.IsFromMe()) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index dcb7f9abd4..8001c4f65a 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -99,7 +99,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { tx.vout[0].nValue -= 1000000; hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); @@ -119,7 +119,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { tx.vout[0].nValue -= 10000000; hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); tx.vin[0].prevout.hash = hash; } BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); @@ -128,7 +128,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) // orphan in mempool hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); delete pblocktemplate; mempool.clear(); @@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].prevout.hash = txFirst[1]->GetHash(); tx.vout[0].nValue = 4900000000LL; hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); tx.vin[0].prevout.hash = hash; tx.vin.resize(2); tx.vin[1].scriptSig = CScript() << OP_1; @@ -146,7 +146,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[1].prevout.n = 0; tx.vout[0].nValue = 5900000000LL; hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); delete pblocktemplate; mempool.clear(); @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; tx.vout[0].nValue = 0; hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); delete pblocktemplate; mempool.clear(); @@ -170,12 +170,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) script = CScript() << OP_0; tx.vout[0].scriptPubKey.SetDestination(script.GetID()); hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); tx.vin[0].prevout.hash = hash; tx.vin[0].scriptSig = CScript() << (std::vector<unsigned char>)script; tx.vout[0].nValue -= 1000000; hash = tx.GetHash(); - mempool.addUnchecked(hash,tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); delete pblocktemplate; mempool.clear(); @@ -186,10 +186,10 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) tx.vout[0].nValue = 4900000000LL; tx.vout[0].scriptPubKey = CScript() << OP_1; hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); tx.vout[0].scriptPubKey = CScript() << OP_2; hash = tx.GetHash(); - mempool.addUnchecked(hash, tx); + mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11)); BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); delete pblocktemplate; mempool.clear(); 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" diff --git a/src/txmempool.cpp b/src/txmempool.cpp index d501b89ecf..be251d1d64 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -8,6 +8,33 @@ using namespace std; +CTxMemPoolEntry::CTxMemPoolEntry() +{ + nHeight = MEMPOOL_HEIGHT; +} + +CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction& _tx, int64_t _nFee, + int64_t _nTime, double _dPriority, + unsigned int _nHeight): + tx(_tx), nFee(_nFee), nTime(_nTime), dPriority(_dPriority), nHeight(_nHeight) +{ + nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); +} + +CTxMemPoolEntry::CTxMemPoolEntry(const CTxMemPoolEntry& other) +{ + *this = other; +} + +double +CTxMemPoolEntry::GetPriority(unsigned int currentHeight) const +{ + int64_t nValueIn = tx.GetValueOut()+nFee; + double deltaPriority = ((double)(currentHeight-nHeight)*nValueIn)/nTxSize; + double dResult = dPriority + deltaPriority; + return dResult; +} + CTxMemPool::CTxMemPool() { // Sanity checks off by default for performance, because otherwise @@ -42,16 +69,17 @@ void CTxMemPool::AddTransactionsUpdated(unsigned int n) } -bool CTxMemPool::addUnchecked(const uint256& hash, const CTransaction &tx) +bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry) { // Add to memory pool without checking anything. // Used by main.cpp AcceptToMemoryPool(), which DOES do // all the appropriate checks. LOCK(cs); { - mapTx[hash] = tx; + mapTx[hash] = entry; + const CTransaction& tx = mapTx[hash].GetTx(); for (unsigned int i = 0; i < tx.vin.size(); i++) - mapNextTx[tx.vin[i].prevout] = CInPoint(&mapTx[hash], i); + mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i); nTransactionsUpdated++; } return true; @@ -113,13 +141,15 @@ void CTxMemPool::check(CCoinsViewCache *pcoins) const LogPrint("mempool", "Checking mempool with %u transactions and %u inputs\n", (unsigned int)mapTx.size(), (unsigned int)mapNextTx.size()); LOCK(cs); - for (std::map<uint256, CTransaction>::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { + for (std::map<uint256, CTxMemPoolEntry>::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { unsigned int i = 0; - BOOST_FOREACH(const CTxIn &txin, it->second.vin) { + const CTransaction& tx = it->second.GetTx(); + BOOST_FOREACH(const CTxIn &txin, tx.vin) { // Check that every mempool transaction's inputs refer to available coins, or other mempool tx's. - std::map<uint256, CTransaction>::const_iterator it2 = mapTx.find(txin.prevout.hash); + std::map<uint256, CTxMemPoolEntry>::const_iterator it2 = mapTx.find(txin.prevout.hash); if (it2 != mapTx.end()) { - assert(it2->second.vout.size() > txin.prevout.n && !it2->second.vout[txin.prevout.n].IsNull()); + const CTransaction& tx2 = it2->second.GetTx(); + assert(tx2.vout.size() > txin.prevout.n && !tx2.vout[txin.prevout.n].IsNull()); } else { CCoins &coins = pcoins->GetCoins(txin.prevout.hash); assert(coins.IsAvailable(txin.prevout.n)); @@ -127,37 +157,38 @@ void CTxMemPool::check(CCoinsViewCache *pcoins) const // Check whether its inputs are marked in mapNextTx. std::map<COutPoint, CInPoint>::const_iterator it3 = mapNextTx.find(txin.prevout); assert(it3 != mapNextTx.end()); - assert(it3->second.ptx == &it->second); + assert(it3->second.ptx == &tx); assert(it3->second.n == i); i++; } } for (std::map<COutPoint, CInPoint>::const_iterator it = mapNextTx.begin(); it != mapNextTx.end(); it++) { uint256 hash = it->second.ptx->GetHash(); - std::map<uint256, CTransaction>::const_iterator it2 = mapTx.find(hash); + map<uint256, CTxMemPoolEntry>::const_iterator it2 = mapTx.find(hash); + const CTransaction& tx = it2->second.GetTx(); assert(it2 != mapTx.end()); - assert(&it2->second == it->second.ptx); - assert(it2->second.vin.size() > it->second.n); + assert(&tx == it->second.ptx); + assert(tx.vin.size() > it->second.n); assert(it->first == it->second.ptx->vin[it->second.n].prevout); } } -void CTxMemPool::queryHashes(std::vector<uint256>& vtxid) +void CTxMemPool::queryHashes(vector<uint256>& vtxid) { vtxid.clear(); LOCK(cs); vtxid.reserve(mapTx.size()); - for (map<uint256, CTransaction>::iterator mi = mapTx.begin(); mi != mapTx.end(); ++mi) + for (map<uint256, CTxMemPoolEntry>::iterator mi = mapTx.begin(); mi != mapTx.end(); ++mi) vtxid.push_back((*mi).first); } bool CTxMemPool::lookup(uint256 hash, CTransaction& result) const { LOCK(cs); - std::map<uint256, CTransaction>::const_iterator i = mapTx.find(hash); + map<uint256, CTxMemPoolEntry>::const_iterator i = mapTx.find(hash); if (i == mapTx.end()) return false; - result = i->second; + result = i->second.GetTx(); return true; } diff --git a/src/txmempool.h b/src/txmempool.h index 57b92789fb..a652c424a4 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -13,6 +13,33 @@ static const unsigned int MEMPOOL_HEIGHT = 0x7FFFFFFF; /* + * CTxMemPool stores these: + */ +class CTxMemPoolEntry +{ +private: + CTransaction tx; + int64_t nFee; // Cached to avoid expensive parent-transaction lookups + size_t nTxSize; // ... and avoid recomputing tx size + int64_t nTime; // Local time when entering the mempool + double dPriority; // Priority when entering the mempool + unsigned int nHeight; // Chain height when entering the mempool + +public: + CTxMemPoolEntry(const CTransaction& _tx, int64_t _nFee, + int64_t _nTime, double _dPriority, unsigned int _nHeight); + CTxMemPoolEntry(); + CTxMemPoolEntry(const CTxMemPoolEntry& other); + + const CTransaction& GetTx() const { return this->tx; } + double GetPriority(unsigned int currentHeight) const; + int64_t GetFee() const { return nFee; } + size_t GetTxSize() const { return nTxSize; } + int64_t GetTime() const { return nTime; } + unsigned int GetHeight() const { return nHeight; } +}; + +/* * CTxMemPool stores valid-according-to-the-current-best-chain * transactions that may be included in the next block. * @@ -30,7 +57,7 @@ private: public: mutable CCriticalSection cs; - std::map<uint256, CTransaction> mapTx; + std::map<uint256, CTxMemPoolEntry> mapTx; std::map<COutPoint, CInPoint> mapNextTx; CTxMemPool(); @@ -44,7 +71,7 @@ public: void check(CCoinsViewCache *pcoins) const; void setSanityCheck(bool _fSanityCheck) { fSanityCheck = _fSanityCheck; } - bool addUnchecked(const uint256& hash, const CTransaction &tx); + bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry); bool remove(const CTransaction &tx, bool fRecursive = false); bool removeConflicts(const CTransaction &tx); void clear(); diff --git a/src/wallet.cpp b/src/wallet.cpp index db957cbd05..b9110d1271 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -655,7 +655,7 @@ void CWalletTx::GetAmounts(list<pair<CTxDestination, int64_t> >& listReceived, int64_t nDebit = GetDebit(); if (nDebit > 0) // debit>0 means we signed/sent this transaction { - int64_t nValueOut = GetValueOut(*this); + int64_t nValueOut = GetValueOut(); nFee = nDebit - nValueOut; } @@ -1342,15 +1342,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, strFailReason = _("Transaction too large"); return false; } - unsigned int nTxSizeMod = nBytes; - // See miner.c's dPriority logic for the matching network-node side code. - BOOST_FOREACH(const CTxIn& txin, (*(CTransaction*)&wtxNew).vin) - { - unsigned int offset = 41U + min(110U, (unsigned int)txin.scriptSig.size()); - if (nTxSizeMod > offset) - nTxSizeMod -= offset; - } - dPriority /= nTxSizeMod; + dPriority = wtxNew.ComputePriority(dPriority, nBytes); // Check that enough fee is included int64_t nPayFee = nTransactionFee * (1 + (int64_t)nBytes / 1000); |