mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-26 11:13:23 -03:00
22f721dbf2
added SetBitcoinAddress and GetBitcoinAddress methods on CScript, critsect interlocks around mapAddressBook, added some random delays in tx broadcast to improve privacy, now compiles with MSVC 8.0
641 lines
19 KiB
C++
641 lines
19 KiB
C++
// Copyright (c) 2010 Satoshi Nakamoto
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
// file license.txt or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include "headers.h"
|
|
#undef printf
|
|
#include <boost/asio.hpp>
|
|
#include "json/json_spirit_reader_template.h"
|
|
#include "json/json_spirit_writer_template.h"
|
|
#include "json/json_spirit_utils.h"
|
|
#define printf OutputDebugStringF
|
|
// MinGW 3.4.5 gets "fatal error: had to relocate PCH" if the json headers are
|
|
// precompiled in headers.h. The problem might be when the pch file goes over
|
|
// a certain size around 145MB. If we need access to json_spirit outside this
|
|
// file, we could use the compiled json_spirit option.
|
|
|
|
using boost::asio::ip::tcp;
|
|
using namespace json_spirit;
|
|
|
|
void ThreadRPCServer2(void* parg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///
|
|
/// Note: I'm not finished designing this interface, it's still subject to change.
|
|
///
|
|
|
|
|
|
|
|
Value stop(const Array& params)
|
|
{
|
|
if (params.size() != 0)
|
|
throw runtime_error(
|
|
"stop (no parameters)\n"
|
|
"Stop bitcoin server.");
|
|
|
|
// Shutdown will take long enough that the response should get back
|
|
CreateThread(Shutdown, NULL);
|
|
return "bitcoin server stopping";
|
|
}
|
|
|
|
|
|
Value getblockcount(const Array& params)
|
|
{
|
|
if (params.size() != 0)
|
|
throw runtime_error(
|
|
"getblockcount (no parameters)\n"
|
|
"Returns the number of blocks in the longest block chain.");
|
|
|
|
return nBestHeight + 1;
|
|
}
|
|
|
|
|
|
Value getblocknumber(const Array& params)
|
|
{
|
|
if (params.size() != 0)
|
|
throw runtime_error(
|
|
"getblocknumber (no parameters)\n"
|
|
"Returns the block number of the latest block in the longest block chain.");
|
|
|
|
return nBestHeight;
|
|
}
|
|
|
|
|
|
Value getdifficulty(const Array& params)
|
|
{
|
|
if (params.size() != 0)
|
|
throw runtime_error(
|
|
"getdifficulty (no parameters)\n"
|
|
"Returns the proof-of-work difficulty as a multiple of the minimum difficulty.");
|
|
|
|
if (pindexBest == NULL)
|
|
throw runtime_error("block chain not loaded");
|
|
|
|
// Floating point number that is a multiple of the minimum difficulty,
|
|
// minimum difficulty = 1.0.
|
|
int nShift = 256 - 32 - 31; // to fit in a uint
|
|
double dMinimum = (CBigNum().SetCompact(bnProofOfWorkLimit.GetCompact()) >> nShift).getuint();
|
|
double dCurrently = (CBigNum().SetCompact(pindexBest->nBits) >> nShift).getuint();
|
|
return dMinimum / dCurrently;
|
|
}
|
|
|
|
|
|
Value getnewaddress(const Array& params)
|
|
{
|
|
if (params.size() > 1)
|
|
throw runtime_error(
|
|
"getnewaddress [label]\n"
|
|
"Returns a new bitcoin address for receiving payments. "
|
|
"If [label] is specified (recommended), it is added to the address book "
|
|
"so payments received with the address will be labeled.");
|
|
|
|
// Parse the label first so we don't generate a key if there's an error
|
|
string strLabel;
|
|
if (params.size() > 0)
|
|
strLabel = params[0].get_str();
|
|
|
|
// Generate a new key that is added to wallet
|
|
string strAddress = PubKeyToAddress(GenerateNewKey());
|
|
|
|
if (params.size() > 0)
|
|
SetAddressBookName(strAddress, strLabel);
|
|
return strAddress;
|
|
}
|
|
|
|
|
|
Value sendtoaddress(const Array& params)
|
|
{
|
|
if (params.size() < 2 || params.size() > 4)
|
|
throw runtime_error(
|
|
"sendtoaddress <bitcoinaddress> <amount> [comment] [comment-to]\n"
|
|
"<amount> is a real and is rounded to the nearest 0.01");
|
|
|
|
string strAddress = params[0].get_str();
|
|
|
|
// Amount
|
|
if (params[1].get_real() <= 0.0 || params[1].get_real() > 21000000.0)
|
|
throw runtime_error("Invalid amount");
|
|
int64 nAmount = roundint64(params[1].get_real() * 100.00) * CENT;
|
|
|
|
// Wallet comments
|
|
CWalletTx wtx;
|
|
if (params.size() > 2 && params[2].type() != null_type && !params[2].get_str().empty())
|
|
wtx.mapValue["message"] = params[2].get_str();
|
|
if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty())
|
|
wtx.mapValue["to"] = params[3].get_str();
|
|
|
|
string strError = SendMoneyToBitcoinAddress(strAddress, nAmount, wtx);
|
|
if (strError != "")
|
|
throw runtime_error(strError);
|
|
return "sent";
|
|
}
|
|
|
|
|
|
Value listtransactions(const Array& params)
|
|
{
|
|
if (params.size() > 2)
|
|
throw runtime_error(
|
|
"listtransactions [count=10] [includegenerated=false]\n"
|
|
"Returns up to [count] most recent transactions.");
|
|
|
|
int64 nCount = 10;
|
|
if (params.size() > 0)
|
|
nCount = params[0].get_int64();
|
|
bool fGenerated = false;
|
|
if (params.size() > 1)
|
|
fGenerated = params[1].get_bool();
|
|
|
|
Array ret;
|
|
//// not finished
|
|
ret.push_back("not implemented yet");
|
|
return ret;
|
|
}
|
|
|
|
|
|
Value getamountpaid(const Array& params)
|
|
{
|
|
if (params.size() < 1 || params.size() > 2)
|
|
throw runtime_error(
|
|
"getamountpaid <bitcoinaddress> [minconf=1]\n"
|
|
"Returns the total amount paid to <bitcoinaddress> in transactions with at least [minconf] confirmations.");
|
|
|
|
// Bitcoin address
|
|
string strAddress = params[0].get_str();
|
|
CScript scriptPubKey;
|
|
if (!scriptPubKey.SetBitcoinAddress(strAddress))
|
|
throw runtime_error("Invalid bitcoin address");
|
|
|
|
// Minimum confirmations
|
|
int nMinDepth = 1;
|
|
if (params.size() > 1)
|
|
nMinDepth = params[1].get_int();
|
|
|
|
// Tally
|
|
int64 nAmount = 0;
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
|
|
{
|
|
const CWalletTx& wtx = (*it).second;
|
|
if (wtx.IsCoinBase() || !wtx.IsFinal())
|
|
continue;
|
|
|
|
foreach(const CTxOut& txout, wtx.vout)
|
|
if (txout.scriptPubKey == scriptPubKey)
|
|
if (wtx.GetDepthInMainChain() >= nMinDepth)
|
|
nAmount += txout.nValue;
|
|
}
|
|
}
|
|
|
|
return (double)nAmount / (double)COIN;
|
|
}
|
|
|
|
|
|
struct tallyitem
|
|
{
|
|
int64 nAmount;
|
|
int nConf;
|
|
tallyitem()
|
|
{
|
|
nAmount = 0;
|
|
nConf = INT_MAX;
|
|
}
|
|
};
|
|
|
|
Value getallpayments(const Array& params)
|
|
{
|
|
if (params.size() > 1)
|
|
throw runtime_error(
|
|
"getallpayments [minconf=1]\n"
|
|
"[minconf] is the minimum number of confirmations before payments are included.\n"
|
|
"Returns an array of objects containing:\n"
|
|
" \"address\" : bitcoin address\n"
|
|
" \"amount\" : total amount paid to the address\n"
|
|
" \"conf\" : number of confirmations\n"
|
|
" \"label\" : the label set for this address when it was created by getnewaddress");
|
|
|
|
// Minimum confirmations
|
|
int nMinDepth = 1;
|
|
if (params.size() > 0)
|
|
nMinDepth = params[0].get_int();
|
|
|
|
// Tally
|
|
map<uint160, tallyitem> mapTally;
|
|
CRITICAL_BLOCK(cs_mapWallet)
|
|
{
|
|
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
|
|
{
|
|
const CWalletTx& wtx = (*it).second;
|
|
if (wtx.IsCoinBase() || !wtx.IsFinal())
|
|
continue;
|
|
|
|
int nDepth = wtx.GetDepthInMainChain();
|
|
if (nDepth >= nMinDepth)
|
|
{
|
|
foreach(const CTxOut& txout, wtx.vout)
|
|
{
|
|
uint160 hash160 = txout.scriptPubKey.GetBitcoinAddressHash160();
|
|
if (hash160 == 0 || !mapPubKeys.count(hash160))
|
|
continue;
|
|
|
|
tallyitem& item = mapTally[hash160];
|
|
item.nAmount += txout.nValue;
|
|
item.nConf = min(item.nConf, nDepth);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reply
|
|
Array ret;
|
|
CRITICAL_BLOCK(cs_mapAddressBook)
|
|
{
|
|
for (map<uint160, tallyitem>::iterator it = mapTally.begin(); it != mapTally.end(); ++it)
|
|
{
|
|
string strAddress = Hash160ToAddress((*it).first);
|
|
string strLabel;
|
|
map<string, string>::iterator mi = mapAddressBook.find(strAddress);
|
|
if (mi != mapAddressBook.end())
|
|
strLabel = (*mi).second;
|
|
|
|
Object obj;
|
|
obj.push_back(Pair("address", strAddress));
|
|
obj.push_back(Pair("amount", (double)(*it).second.nAmount / (double)COIN));
|
|
obj.push_back(Pair("conf", (*it).second.nConf));
|
|
obj.push_back(Pair("label", strLabel));
|
|
ret.push_back(obj);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Call Table
|
|
//
|
|
|
|
typedef Value(*rpcfn_type)(const Array& params);
|
|
pair<string, rpcfn_type> pCallTable[] =
|
|
{
|
|
make_pair("stop", &stop),
|
|
make_pair("getblockcount", &getblockcount),
|
|
make_pair("getblocknumber", &getblocknumber),
|
|
make_pair("getdifficulty", &getdifficulty),
|
|
make_pair("getnewaddress", &getnewaddress),
|
|
make_pair("sendtoaddress", &sendtoaddress),
|
|
make_pair("listtransactions", &listtransactions),
|
|
make_pair("getamountpaid", &getamountpaid),
|
|
make_pair("getallpayments", &getallpayments),
|
|
};
|
|
map<string, rpcfn_type> mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0]));
|
|
|
|
|
|
|
|
|
|
//
|
|
// 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)
|
|
{
|
|
return strprintf(
|
|
"POST / HTTP/1.1\r\n"
|
|
"User-Agent: json-rpc/1.0\r\n"
|
|
"Host: 127.0.0.1\r\n"
|
|
"Content-Type: application/json\r\n"
|
|
"Content-Length: %d\r\n"
|
|
"Accept: application/json\r\n"
|
|
"\r\n"
|
|
"%s",
|
|
strMsg.size(),
|
|
strMsg.c_str());
|
|
}
|
|
|
|
string HTTPReply(const string& strMsg, int nStatus=200)
|
|
{
|
|
string strStatus;
|
|
if (nStatus == 200) strStatus = "OK";
|
|
if (nStatus == 500) strStatus = "Internal Server Error";
|
|
return strprintf(
|
|
"HTTP/1.1 %d %s\r\n"
|
|
"Connection: close\r\n"
|
|
"Content-Length: %d\r\n"
|
|
"Content-Type: application/json\r\n"
|
|
"Date: Sat, 08 Jul 2006 12:04:08 GMT\r\n"
|
|
"Server: json-rpc/1.0\r\n"
|
|
"\r\n"
|
|
"%s",
|
|
nStatus,
|
|
strStatus.c_str(),
|
|
strMsg.size(),
|
|
strMsg.c_str());
|
|
}
|
|
|
|
int ReadHTTPHeader(tcp::iostream& stream)
|
|
{
|
|
int nLen = 0;
|
|
loop
|
|
{
|
|
string str;
|
|
std::getline(stream, str);
|
|
if (str.empty() || str == "\r")
|
|
break;
|
|
if (str.substr(0,15) == "Content-Length:")
|
|
nLen = atoi(str.substr(15));
|
|
}
|
|
return nLen;
|
|
}
|
|
|
|
inline string ReadHTTP(tcp::iostream& stream)
|
|
{
|
|
// Read header
|
|
int nLen = ReadHTTPHeader(stream);
|
|
if (nLen <= 0)
|
|
return string();
|
|
|
|
// Read message
|
|
vector<char> vch(nLen);
|
|
stream.read(&vch[0], nLen);
|
|
return string(vch.begin(), vch.end());
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// JSON-RPC protocol
|
|
//
|
|
// http://json-rpc.org/wiki/specification
|
|
// 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";
|
|
}
|
|
|
|
string JSONRPCReply(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 write_string(Value(reply), false) + "\n";
|
|
}
|
|
|
|
|
|
|
|
|
|
void ThreadRPCServer(void* parg)
|
|
{
|
|
IMPLEMENT_RANDOMIZE_STACK(ThreadRPCServer(parg));
|
|
try
|
|
{
|
|
vnThreadsRunning[4]++;
|
|
ThreadRPCServer2(parg);
|
|
vnThreadsRunning[4]--;
|
|
}
|
|
catch (std::exception& e) {
|
|
vnThreadsRunning[4]--;
|
|
PrintException(&e, "ThreadRPCServer()");
|
|
} catch (...) {
|
|
vnThreadsRunning[4]--;
|
|
PrintException(NULL, "ThreadRPCServer()");
|
|
}
|
|
printf("ThreadRPCServer exiting\n");
|
|
}
|
|
|
|
void ThreadRPCServer2(void* parg)
|
|
{
|
|
printf("ThreadRPCServer started\n");
|
|
|
|
// Bind to loopback 127.0.0.1 so the socket can only be accessed locally
|
|
boost::asio::io_service io_service;
|
|
tcp::endpoint endpoint(boost::asio::ip::address_v4::loopback(), 8332);
|
|
tcp::acceptor acceptor(io_service, endpoint);
|
|
|
|
loop
|
|
{
|
|
// Accept connection
|
|
tcp::iostream stream;
|
|
tcp::endpoint peer;
|
|
vnThreadsRunning[4]--;
|
|
acceptor.accept(*stream.rdbuf(), peer);
|
|
vnThreadsRunning[4]++;
|
|
if (fShutdown)
|
|
return;
|
|
|
|
// Shouldn't be possible for anyone else to connect, but just in case
|
|
if (peer.address().to_string() != "127.0.0.1")
|
|
continue;
|
|
|
|
// Receive request
|
|
string strRequest = ReadHTTP(stream);
|
|
printf("ThreadRPCServer request=%s", strRequest.c_str());
|
|
|
|
// Handle multiple invocations per request
|
|
string::iterator begin = strRequest.begin();
|
|
while (skipspaces(begin), begin != strRequest.end())
|
|
{
|
|
string::iterator prev = begin;
|
|
Value id;
|
|
try
|
|
{
|
|
// Parse request
|
|
Value valRequest;
|
|
if (!read_range(begin, strRequest.end(), valRequest))
|
|
throw runtime_error("Parse error.");
|
|
const Object& request = valRequest.get_obj();
|
|
if (find_value(request, "method").type() != str_type ||
|
|
find_value(request, "params").type() != array_type)
|
|
throw runtime_error("Invalid request.");
|
|
|
|
string strMethod = find_value(request, "method").get_str();
|
|
const Array& params = find_value(request, "params").get_array();
|
|
id = find_value(request, "id");
|
|
|
|
// Execute
|
|
map<string, rpcfn_type>::iterator mi = mapCallTable.find(strMethod);
|
|
if (mi == mapCallTable.end())
|
|
throw runtime_error("Method not found.");
|
|
Value result = (*(*mi).second)(params);
|
|
|
|
// Send reply
|
|
string strReply = JSONRPCReply(result, Value::null, id);
|
|
stream << HTTPReply(strReply, 200) << std::flush;
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
// Send error reply
|
|
string strReply = JSONRPCReply(Value::null, e.what(), id);
|
|
stream << HTTPReply(strReply, 500) << std::flush;
|
|
}
|
|
if (begin == prev)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
Value CallRPC(const string& strMethod, const Array& params)
|
|
{
|
|
// Connect to localhost
|
|
tcp::iostream stream("127.0.0.1", "8332");
|
|
if (stream.fail())
|
|
throw runtime_error("unable to connect to server");
|
|
|
|
// Send request
|
|
string strRequest = JSONRPCRequest(strMethod, params, 1);
|
|
stream << HTTPPost(strRequest) << std::flush;
|
|
|
|
// Receive reply
|
|
string strReply = ReadHTTP(stream);
|
|
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");
|
|
|
|
const Value& result = find_value(reply, "result");
|
|
const Value& error = find_value(reply, "error");
|
|
const Value& id = find_value(reply, "id");
|
|
|
|
if (error.type() == str_type)
|
|
throw runtime_error(error.get_str());
|
|
else if (error.type() != null_type)
|
|
throw runtime_error(write_string(error, false));
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename T>
|
|
void ConvertTo(Value& value)
|
|
{
|
|
if (value.type() == str_type)
|
|
{
|
|
// reinterpret string as unquoted json value
|
|
Value value2;
|
|
if (!read_string(value.get_str(), value2))
|
|
throw runtime_error("type mismatch");
|
|
value = value2.get_value<T>();
|
|
}
|
|
else
|
|
{
|
|
value = value.get_value<T>();
|
|
}
|
|
}
|
|
|
|
int CommandLineRPC(int argc, char *argv[])
|
|
{
|
|
try
|
|
{
|
|
// Check that method exists
|
|
if (argc < 2)
|
|
throw runtime_error("too few parameters");
|
|
string strMethod = argv[1];
|
|
if (!mapCallTable.count(strMethod))
|
|
throw runtime_error(strprintf("unknown command: %s", strMethod.c_str()));
|
|
|
|
// Parameters default to strings
|
|
Array params;
|
|
for (int i = 2; i < argc; i++)
|
|
params.push_back(argv[i]);
|
|
|
|
// Special case other types
|
|
int n = params.size();
|
|
if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]);
|
|
if (strMethod == "listtransactions" && n > 0) ConvertTo<boost::int64_t>(params[0]);
|
|
if (strMethod == "listtransactions" && n > 1) ConvertTo<bool>(params[1]);
|
|
if (strMethod == "getamountpaid" && n > 1) ConvertTo<boost::int64_t>(params[1]);
|
|
if (strMethod == "getallpayments" && n > 0) ConvertTo<boost::int64_t>(params[0]);
|
|
|
|
// Execute
|
|
Value result = CallRPC(strMethod, params);
|
|
|
|
// Print result
|
|
string strResult = (result.type() == str_type ? result.get_str() : write_string(result, true));
|
|
if (result.type() != null_type)
|
|
{
|
|
if (fWindows)
|
|
// Windows GUI apps can't print to command line,
|
|
// so for now settle for a message box yuck
|
|
wxMessageBox(strResult.c_str(), "Bitcoin", wxOK);
|
|
else
|
|
fprintf(stdout, "%s\n", strResult.c_str());
|
|
}
|
|
return 0;
|
|
}
|
|
catch (std::exception& e) {
|
|
if (fWindows)
|
|
wxMessageBox(strprintf("error: %s\n", e.what()).c_str(), "Bitcoin", wxOK);
|
|
else
|
|
fprintf(stderr, "error: %s\n", e.what());
|
|
} catch (...) {
|
|
PrintException(NULL, "CommandLineRPC()");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
|
|
#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")
|
|
{
|
|
printf("server ready\n");
|
|
ThreadRPCServer(NULL);
|
|
}
|
|
else
|
|
{
|
|
return CommandLineRPC(argc, argv);
|
|
}
|
|
}
|
|
catch (std::exception& e) {
|
|
PrintException(&e, "main()");
|
|
} catch (...) {
|
|
PrintException(NULL, "main()");
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|