diff --git a/newcoin.vcxproj b/newcoin.vcxproj
index 3a318d339c..6e45fb534a 100644
--- a/newcoin.vcxproj
+++ b/newcoin.vcxproj
@@ -49,7 +49,7 @@
Level3
Disabled
BOOST_TEST_ALTERNATIVE_INIT_API;BOOST_TEST_NO_MAIN;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0501;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
- .\obj;..\OpenSSL\include;..\boost_1_47_0;..\protobuf-2.4.1\src\
+ .\src;..\OpenSSL\include;..\boost_1_47_0;..\protobuf-2.4.1\src\
ProgramDatabase
@@ -86,93 +86,95 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .\;..\OpenSSL\include;..\boost_1_47_0;..\protobuf-2.4.1\src\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -295,10 +297,10 @@
-
+
Document
- /code/protoc-2.4.1-win32/protoc -I=..\newcoin --cpp_out=\code\newcoin\obj\ ..\newcoin/src/ripple.proto
- \code\newcoin\obj\src\ripple.pb.h
+ /code/protoc-2.4.1-win32/protoc -I=..\newcoin --cpp_out=\code\newcoin\ ..\newcoin/src/cpp/ripple/ripple.proto
+ \code\newcoin\src\ripple.pb.h
diff --git a/newcoin.vcxproj.filters b/newcoin.vcxproj.filters
index 090e299712..de5c116e47 100644
--- a/newcoin.vcxproj.filters
+++ b/newcoin.vcxproj.filters
@@ -33,195 +33,6 @@
-
- Header Files\util
-
-
- Source Files\json
-
-
- Source Files\json
-
-
- Source Files\json
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files\database
-
-
- Source Files\database
-
-
- Source Files\database
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
Source Files\websocketpp
@@ -246,78 +57,267 @@
Source Files\websocketpp
-
+
+ Source Files\database
+
+
+ Source Files\database
+
+
+ Source Files\database
+
+
+ Source Files\json
+
+
+ Source Files\json
+
+
+ Source Files\json
+
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files\database
+
@@ -648,6 +648,6 @@
-
+
\ No newline at end of file
diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h
index eab46a592a..0997f42af8 100644
--- a/src/cpp/ripple/NetworkOPs.h
+++ b/src/cpp/ripple/NetworkOPs.h
@@ -28,6 +28,19 @@ public:
virtual ~InfoSub() { ; }
virtual void send(const Json::Value& jvObj) = 0;
+
+protected:
+ boost::unordered_set mSubAccountInfo;
+ boost::unordered_set mSubAccountTransaction;
+
+ boost::mutex mLock;
+
+public:
+ void insertSubAccountInfo(RippleAddress addr)
+ {
+ boost::mutex::scoped_lock sl(mLock);
+ mSubAccountInfo.insert(addr);
+ }
};
class NetworkOPs
diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp
index fbd24d555f..5d8ca3ef49 100644
--- a/src/cpp/ripple/RPCHandler.cpp
+++ b/src/cpp/ripple/RPCHandler.cpp
@@ -51,6 +51,7 @@ Json::Value RPCHandler::rpcError(int iError)
{ rpcNO_ACCOUNT, "noAccount", "No such account." },
{ rpcNO_CLOSED, "noClosed", "Closed ledger is unavailable." },
{ rpcNO_CURRENT, "noCurrent", "Current ledger is unavailable." },
+ { rpcNO_EVENTS, "noEvents", "Current transport does not support events." },
{ rpcNO_GEN_DECRPYT, "noGenDectypt", "Password failed to decrypt master public generator." },
{ rpcNO_NETWORK, "noNetwork", "Network not available." },
{ rpcNO_PERMISSION, "noPermission", "You don't have permission for this command." },
@@ -92,6 +93,13 @@ Json::Value RPCHandler::rpcError(int iError)
RPCHandler::RPCHandler(NetworkOPs* netOps)
{
mNetOps=netOps;
+ mInfoSub=NULL;
+}
+
+RPCHandler::RPCHandler(NetworkOPs* netOps, InfoSub* infoSub)
+{
+ mNetOps=netOps;
+ mInfoSub=infoSub;
}
int RPCHandler::getParamCount(const Json::Value& params)
@@ -1309,53 +1317,58 @@ Json::Value RPCHandler::doCommand(const std::string& command, Json::Value& param
int iMinParams;
int iMaxParams;
bool mAdminRequired;
+ bool mEvented;
unsigned int iOptions;
} commandsA[] = {
+ // Request-response methods
{ "accept_ledger", &RPCHandler::doAcceptLedger, 0, 0, true },
- { "account_info", &RPCHandler::doAccountInfo, 1, 2, false, optCurrent },
- { "account_tx", &RPCHandler::doAccountTransactions, 2, 3, false, optNetwork },
- { "connect", &RPCHandler::doConnect, 1, 2, true },
- { "data_delete", &RPCHandler::doDataDelete, 1, 1, true },
- { "data_fetch", &RPCHandler::doDataFetch, 1, 1, true },
- { "data_store", &RPCHandler::doDataStore, 2, 2, true },
- { "get_counts", &RPCHandler::doGetCounts, 0, 1, true },
- { "ledger", &RPCHandler::doLedger, 0, 2, false, optNetwork },
- { "ledger_accept", &RPCHandler::doLedgerAccept, 0, 0, true, optCurrent },
- { "ledger_closed", &RPCHandler::doLedgerClosed, 0, 0, false, optClosed },
- { "ledger_current", &RPCHandler::doLedgerCurrent, 0, 0, false, optCurrent },
- { "ledger_entry", &RPCHandler::doLedgerEntry, -1, -1, false, optCurrent },
- { "log_level", &RPCHandler::doLogLevel, 0, 2, true },
- { "logrotate", &RPCHandler::doLogRotate, 0, 0, true },
- { "nickname_info", &RPCHandler::doNicknameInfo, 1, 1, false, optCurrent },
- { "owner_info", &RPCHandler::doOwnerInfo, 1, 2, false, optCurrent },
+ { "account_info", &RPCHandler::doAccountInfo, 1, 2, false, false, optCurrent },
+ { "account_tx", &RPCHandler::doAccountTransactions, 2, 3, false, false, optNetwork },
+ { "connect", &RPCHandler::doConnect, 1, 2, true },
+ { "data_delete", &RPCHandler::doDataDelete, 1, 1, true },
+ { "data_fetch", &RPCHandler::doDataFetch, 1, 1, true },
+ { "data_store", &RPCHandler::doDataStore, 2, 2, true },
+ { "get_counts", &RPCHandler::doGetCounts, 0, 1, true },
+ { "ledger", &RPCHandler::doLedger, 0, 2, false, false, optNetwork },
+ { "ledger_accept", &RPCHandler::doLedgerAccept, 0, 0, true, false, optCurrent },
+ { "ledger_closed", &RPCHandler::doLedgerClosed, 0, 0, false, false, optClosed },
+ { "ledger_current", &RPCHandler::doLedgerCurrent, 0, 0, false, false, optCurrent },
+ { "ledger_entry", &RPCHandler::doLedgerEntry, -1, -1, false, false, optCurrent },
+ { "log_level", &RPCHandler::doLogLevel, 0, 2, true },
+ { "logrotate", &RPCHandler::doLogRotate, 0, 0, true },
+ { "nickname_info", &RPCHandler::doNicknameInfo, 1, 1, false, false, optCurrent },
+ { "owner_info", &RPCHandler::doOwnerInfo, 1, 2, false, false, optCurrent },
{ "peers", &RPCHandler::doPeers, 0, 0, true },
- { "profile", &RPCHandler::doProfile, 1, 9, false, optCurrent },
- { "ripple_lines_get", &RPCHandler::doRippleLinesGet, 1, 2, false, optCurrent },
- { "submit", &RPCHandler::doSubmit, 2, 2, false, optCurrent },
- { "submit_json", &RPCHandler::doSubmitJson, -1, -1, false, optCurrent },
- { "server_info", &RPCHandler::doServerInfo, 0, 0, true },
- { "stop", &RPCHandler::doStop, 0, 0, true },
- { "transaction_entry", &RPCHandler::doTransactionEntry, -1, -1, false, optCurrent },
- { "tx", &RPCHandler::doTx, 1, 1, true },
- { "tx_history", &RPCHandler::doTxHistory, 1, 1, false, },
+ { "profile", &RPCHandler::doProfile, 1, 9, false, false, optCurrent },
+ { "ripple_lines_get", &RPCHandler::doRippleLinesGet, 1, 2, false, false, optCurrent },
+ { "submit", &RPCHandler::doSubmit, 2, 2, false, false, optCurrent },
+ { "submit_json", &RPCHandler::doSubmitJson, -1, -1, false, false, optCurrent },
+ { "server_info", &RPCHandler::doServerInfo, 0, 0, true },
+ { "stop", &RPCHandler::doStop, 0, 0, true },
+ { "transaction_entry", &RPCHandler::doTransactionEntry, -1, -1, false, false, optCurrent },
+ { "tx", &RPCHandler::doTx, 1, 1, true },
+ { "tx_history", &RPCHandler::doTxHistory, 1, 1, false, },
- { "unl_add", &RPCHandler::doUnlAdd, 1, 2, true },
- { "unl_delete", &RPCHandler::doUnlDelete, 1, 1, true },
- { "unl_list", &RPCHandler::doUnlList, 0, 0, true },
- { "unl_load", &RPCHandler::doUnlLoad, 0, 0, true },
- { "unl_network", &RPCHandler::doUnlNetwork, 0, 0, true },
- { "unl_reset", &RPCHandler::doUnlReset, 0, 0, true },
- { "unl_score", &RPCHandler::doUnlScore, 0, 0, true },
+ { "unl_add", &RPCHandler::doUnlAdd, 1, 2, true },
+ { "unl_delete", &RPCHandler::doUnlDelete, 1, 1, true },
+ { "unl_list", &RPCHandler::doUnlList, 0, 0, true },
+ { "unl_load", &RPCHandler::doUnlLoad, 0, 0, true },
+ { "unl_network", &RPCHandler::doUnlNetwork, 0, 0, true },
+ { "unl_reset", &RPCHandler::doUnlReset, 0, 0, true },
+ { "unl_score", &RPCHandler::doUnlScore, 0, 0, true },
- { "validation_create", &RPCHandler::doValidationCreate, 0, 1, false },
- { "validation_seed", &RPCHandler::doValidationSeed, 0, 1, false },
+ { "validation_create", &RPCHandler::doValidationCreate, 0, 1, false },
+ { "validation_seed", &RPCHandler::doValidationSeed, 0, 1, false },
- { "wallet_accounts", &RPCHandler::doWalletAccounts, 1, 1, false, optCurrent },
- { "wallet_propose", &RPCHandler::doWalletPropose, 0, 1, false, },
- { "wallet_seed", &RPCHandler::doWalletSeed, 0, 1, false, },
+ { "wallet_accounts", &RPCHandler::doWalletAccounts, 1, 1, false, false, optCurrent },
+ { "wallet_propose", &RPCHandler::doWalletPropose, 0, 1, false, },
+ { "wallet_seed", &RPCHandler::doWalletSeed, 0, 1, false, },
- { "login", &RPCHandler::doLogin, 2, 2, true },
- };
+ { "login", &RPCHandler::doLogin, 2, 2, true },
+
+ // Evented methods
+ { "subscribe", &RPCHandler::doSubscribe, -1, -1, false, true },
+ { "unsubscribe", &RPCHandler::doUnsubscribe, -1, -1, false, true }, };
int i = NUMBER(commandsA);
@@ -1370,6 +1383,10 @@ Json::Value RPCHandler::doCommand(const std::string& command, Json::Value& param
{
return rpcError(rpcNO_PERMISSION);
}
+ else if (commandsA[i].mEvented && mInfoSub == NULL)
+ {
+ return rpcError(rpcNO_EVENTS);
+ }
else if (commandsA[i].iMinParams >= 0
? commandsA[i].iMaxParams
? (params.size() < commandsA[i].iMinParams
@@ -1933,4 +1950,183 @@ Json::Value RPCHandler::doLedgerEntry(const Json::Value& jvRequest)
return jvResult;
}
+
+boost::unordered_set RPCHandler::parseAccountIds(const Json::Value& jvArray)
+{
+ boost::unordered_set usnaResult;
+
+ for (Json::Value::const_iterator it = jvArray.begin(); it != jvArray.end(); it++)
+ {
+ RippleAddress naString;
+
+ if (!(*it).isString() || !naString.setAccountID((*it).asString()))
+ {
+ usnaResult.clear();
+ break;
+ }
+ else
+ {
+ (void) usnaResult.insert(naString);
+ }
+ }
+
+ return usnaResult;
+}
+
+/*
+server : Sends a message anytime the server status changes such as network connectivity.
+ledger : Sends a message at every ledger close.
+transactions : Sends a message for every transaction that makes it into a ledger.
+rt_transactions
+accounts
+rt_accounts
+*/
+Json::Value RPCHandler::doSubscribe(const Json::Value& jvRequest)
+{
+ Json::Value jvResult(Json::objectValue);
+
+ if (jvRequest.isMember("streams"))
+ {
+ for (Json::Value::iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++)
+ {
+ if ((*it).isString())
+ {
+ std::string streamName=(*it).asString();
+
+ if(streamName=="server")
+ {
+ mNetOps->subServer(mInfoSub, jvResult);
+ }else if(streamName=="ledger")
+ {
+ mNetOps->subLedger(mInfoSub, jvResult);
+ }else if(streamName=="transactions")
+ {
+ mNetOps->subTransactions(mInfoSub);
+ }else if(streamName=="rt_transactions")
+ {
+ mNetOps->subRTTransactions(mInfoSub);
+ }else
+ {
+ jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName);
+ }
+ }else
+ {
+ jvResult["error"] = "malformedSteam";
+ }
+ }
+ }
+
+ if (jvRequest.isMember("rt_accounts"))
+ {
+ boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["rt_accounts"]);
+
+ if (usnaAccoundIds.empty())
+ {
+ jvResult["error"] = "malformedAccount";
+ }else
+ {
+ BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
+ {
+ mInfoSub->insertSubAccountInfo(naAccountID);
+ }
+
+ mNetOps->subAccount(mInfoSub, usnaAccoundIds, true);
+ }
+ }
+
+ if (jvRequest.isMember("accounts"))
+ {
+ boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]);
+
+ if (usnaAccoundIds.empty())
+ {
+ jvResult["error"] = "malformedAccount";
+ }else
+ {
+ BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
+ {
+ mInfoSub->insertSubAccountInfo(naAccountID);
+ }
+
+ mNetOps->subAccount(mInfoSub, usnaAccoundIds, false);
+ }
+ }
+
+ return jvResult;
+}
+
+Json::Value RPCHandler::doUnsubscribe(const Json::Value& jvRequest)
+{
+ Json::Value jvResult(Json::objectValue);
+
+ if (jvRequest.isMember("streams"))
+ {
+ for (Json::Value::iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++)
+ {
+ if ((*it).isString() )
+ {
+ std::string streamName=(*it).asString();
+
+ if(streamName=="server")
+ {
+ mNetOps->unsubServer(mInfoSub);
+ }else if(streamName=="ledger")
+ {
+ mNetOps->unsubLedger(mInfoSub);
+ }else if(streamName=="transactions")
+ {
+ mNetOps->unsubTransactions(mInfoSub);
+ }else if(streamName=="rt_transactions")
+ {
+ mNetOps->unsubRTTransactions(mInfoSub);
+ }else
+ {
+ jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName);
+ }
+ }else
+ {
+ jvResult["error"] = "malformedSteam";
+ }
+ }
+ }
+
+ if (jvRequest.isMember("rt_accounts"))
+ {
+ boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["rt_accounts"]);
+
+ if (usnaAccoundIds.empty())
+ {
+ jvResult["error"] = "malformedAccount";
+ }else
+ {
+ BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
+ {
+ mInfoSub->insertSubAccountInfo(naAccountID);
+ }
+
+ mNetOps->unsubAccount(mInfoSub, usnaAccoundIds,true);
+ }
+ }
+
+ if (jvRequest.isMember("accounts"))
+ {
+ boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]);
+
+ if (usnaAccoundIds.empty())
+ {
+ jvResult["error"] = "malformedAccount";
+ }else
+ {
+ BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
+ {
+ mInfoSub->insertSubAccountInfo(naAccountID);
+ }
+
+ mNetOps->unsubAccount(mInfoSub, usnaAccoundIds,false);
+ }
+ }
+
+ return jvResult;
+}
+
// vim:ts=4
diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h
index ca71d7baca..7be97a6d5a 100644
--- a/src/cpp/ripple/RPCHandler.h
+++ b/src/cpp/ripple/RPCHandler.h
@@ -6,7 +6,8 @@ class NetworkOPs;
class RPCHandler
{
- NetworkOPs* mNetOps;
+ NetworkOPs* mNetOps;
+ InfoSub* mInfoSub;
typedef Json::Value (RPCHandler::*doFuncPtr)(const Json::Value ¶ms);
enum {
@@ -15,6 +16,9 @@ class RPCHandler
optClosed = 4+optNetwork, // Need closed ledger
};
+ // Utilities
+ void addSubmitPath(Json::Value& txJSON);
+ boost::unordered_set parseAccountIds(const Json::Value& jvArray);
int getParamCount(const Json::Value& params);
bool extractString(std::string& param, const Json::Value& params, int index);
@@ -84,8 +88,9 @@ class RPCHandler
Json::Value doLedgerEntry(const Json::Value& params);
Json::Value doTransactionEntry(const Json::Value& params);
+ Json::Value doSubscribe(const Json::Value& params);
+ Json::Value doUnsubscribe(const Json::Value& params);
- void addSubmitPath(Json::Value& txJSON);
public:
@@ -95,6 +100,7 @@ public:
// Misc failure
rpcLOAD_FAILED,
rpcNO_PERMISSION,
+ rpcNO_EVENTS,
rpcNOT_STANDALONE,
// Networking
@@ -151,8 +157,9 @@ public:
enum { GUEST, USER, ADMIN };
RPCHandler(NetworkOPs* netOps);
+ RPCHandler(NetworkOPs* netOps, InfoSub* infoSub);
- Json::Value doCommand(const std::string& command, Json::Value& params,int role);
+ Json::Value doCommand(const std::string& command, Json::Value& params, int role);
Json::Value rpcError(int iError);
Json::Value handleJSONSubmit(const Json::Value& jvRequest);
diff --git a/src/cpp/ripple/RPCServer.cpp b/src/cpp/ripple/RPCServer.cpp
index 01568d9996..3b87189a01 100644
--- a/src/cpp/ripple/RPCServer.cpp
+++ b/src/cpp/ripple/RPCServer.cpp
@@ -26,7 +26,7 @@ SETUP_LOG();
#endif
RPCServer::RPCServer(boost::asio::io_service& io_service , NetworkOPs* nopNetwork)
- : mNetOps(nopNetwork), mRPCHandler(nopNetwork), mSocket(io_service)
+ : mNetOps(nopNetwork), mSocket(io_service)
{
mRole = RPCHandler::GUEST;
@@ -142,6 +142,8 @@ std::string RPCServer::handleRequest(const std::string& requestStr)
else if (!valParams.isArray())
return(HTTPReply(400, "params unparseable"));
+ RPCHandler mRPCHandler(mNetOps);
+
cLog(lsTRACE) << valParams;
Json::Value result = mRPCHandler.doCommand(strMethod, valParams,mRole);
cLog(lsTRACE) << result;
diff --git a/src/cpp/ripple/RPCServer.h b/src/cpp/ripple/RPCServer.h
index a2b51caab3..c96774354d 100644
--- a/src/cpp/ripple/RPCServer.h
+++ b/src/cpp/ripple/RPCServer.h
@@ -22,9 +22,7 @@ public:
private:
-
NetworkOPs* mNetOps;
- RPCHandler mRPCHandler;
boost::asio::ip::tcp::socket mSocket;
diff --git a/src/cpp/ripple/WSConnection.cpp b/src/cpp/ripple/WSConnection.cpp
index 7037836b16..ae6a70ee88 100644
--- a/src/cpp/ripple/WSConnection.cpp
+++ b/src/cpp/ripple/WSConnection.cpp
@@ -18,8 +18,8 @@ WSConnection::~WSConnection()
mNetwork.unsubRTTransactions(this);
mNetwork.unsubLedger(this);
mNetwork.unsubServer(this);
- mNetwork.unsubAccount(this, mSubAccountInfo,true);
- mNetwork.unsubAccount(this, mSubAccountInfo,false);
+ mNetwork.unsubAccount(this, mSubAccountInfo, true);
+ mNetwork.unsubAccount(this, mSubAccountInfo, false);
}
void WSConnection::send(const Json::Value& jvObj)
@@ -33,17 +33,6 @@ void WSConnection::send(const Json::Value& jvObj)
Json::Value WSConnection::invokeCommand(Json::Value& jvRequest)
{
- static struct {
- const char* pCommand;
- doFuncPtr dfpFunc;
- } commandsA[] = {
- // Request-Response Commands:
- { "submit", &WSConnection::doSubmit },
- { "subscribe", &WSConnection::doSubscribe },
- { "unsubscribe", &WSConnection::doUnsubscribe },
- { "rpc", &WSConnection::doRPC },
- };
-
if (!jvRequest.isMember("command"))
{
Json::Value jvResult(Json::objectValue);
@@ -56,22 +45,27 @@ Json::Value WSConnection::invokeCommand(Json::Value& jvRequest)
return jvResult;
}
- std::string strCommand = jvRequest["command"].asString();
-
- int i = NUMBER(commandsA);
-
- while (i-- && strCommand != commandsA[i].pCommand)
- ;
-
+ RPCHandler mRPCHandler(&mNetwork, this);
Json::Value jvResult(Json::objectValue);
- if (i < 0)
+ // Regular RPC command
+ jvResult["result"] = mRPCHandler.doCommand(
+ jvRequest["command"].asString(),
+ jvRequest.isMember("params")
+ ? jvRequest["params"]
+ : jvRequest,
+ mHandler->getPublic() ? RPCHandler::GUEST : RPCHandler::ADMIN);
+
+ // Currently we will simply unwrap errors returned by the RPC
+ // API, in the future maybe we can make the responses
+ // consistent.
+ if (jvResult["result"].isObject() && jvResult["result"].isMember("error"))
{
- jvResult["error"] = "unknownCommand"; // Unknown command.
- }
- else
- {
- (this->*(commandsA[i].dfpFunc))(jvResult, jvRequest);
+ jvResult = jvResult["result"];
+ jvResult["status"] = "error";
+ jvResult["request"] = jvRequest;
+ } else {
+ jvResult["status"] = "success";
}
if (jvRequest.isMember("id"))
@@ -79,237 +73,9 @@ Json::Value WSConnection::invokeCommand(Json::Value& jvRequest)
jvResult["id"] = jvRequest["id"];
}
- if (jvResult.isMember("error"))
- {
- jvResult["result"] = "error";
- jvResult["request"] = jvRequest;
- }
- else
- {
- jvResult["result"] = "success";
- }
-
- jvResult["type"] = "response";
+ jvResult["type"] = "response";
return jvResult;
}
-boost::unordered_set WSConnection::parseAccountIds(const Json::Value& jvArray)
-{
- boost::unordered_set usnaResult;
-
- for (Json::Value::const_iterator it = jvArray.begin(); it != jvArray.end(); it++)
- {
- RippleAddress naString;
-
- if (!(*it).isString() || !naString.setAccountID((*it).asString()))
- {
- usnaResult.clear();
- break;
- }
- else
- {
- (void) usnaResult.insert(naString);
- }
- }
-
- return usnaResult;
-}
-
-//
-// Commands
-//
-
-/*
-server : Sends a message anytime the server status changes such as network connectivity.
-ledger : Sends a message at every ledger close.
-transactions : Sends a message for every transaction that makes it into a ledger.
-rt_transactions
-accounts
-rt_accounts
-*/
-void WSConnection::doSubscribe(Json::Value& jvResult, Json::Value& jvRequest)
-{
- if (jvRequest.isMember("streams"))
- {
- for (Json::Value::iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++)
- {
- if ((*it).isString())
- {
- std::string streamName=(*it).asString();
-
- if(streamName=="server")
- {
- mNetwork.subServer(this, jvResult);
- }else if(streamName=="ledger")
- {
- mNetwork.subLedger(this, jvResult);
- }else if(streamName=="transactions")
- {
- mNetwork.subTransactions(this);
- }else if(streamName=="rt_transactions")
- {
- mNetwork.subRTTransactions(this);
- }else
- {
- jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName);
- }
- }else
- {
- jvResult["error"] = "malformedSteam";
- }
- }
- }
-
- if (jvRequest.isMember("rt_accounts"))
- {
- boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["rt_accounts"]);
-
- if (usnaAccoundIds.empty())
- {
- jvResult["error"] = "malformedAccount";
- }else
- {
- boost::mutex::scoped_lock sl(mLock);
-
- BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
- {
- mSubAccountInfo.insert(naAccountID);
- }
-
- mNetwork.subAccount(this, usnaAccoundIds,true);
- }
- }
-
- if (jvRequest.isMember("accounts"))
- {
- boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]);
-
- if (usnaAccoundIds.empty())
- {
- jvResult["error"] = "malformedAccount";
- }else
- {
- boost::mutex::scoped_lock sl(mLock);
-
- BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
- {
- mSubAccountInfo.insert(naAccountID);
- }
-
- mNetwork.subAccount(this, usnaAccoundIds,false);
- }
- }
-}
-
-void WSConnection::doUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest)
-{
- if (jvRequest.isMember("streams"))
- {
- for (Json::Value::iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++)
- {
- if ((*it).isString() )
- {
- std::string streamName=(*it).asString();
-
- if(streamName=="server")
- {
- mNetwork.unsubServer(this);
- }else if(streamName=="ledger")
- {
- mNetwork.unsubLedger(this);
- }else if(streamName=="transactions")
- {
- mNetwork.unsubTransactions(this);
- }else if(streamName=="rt_transactions")
- {
- mNetwork.unsubRTTransactions(this);
- }else
- {
- jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName);
- }
- }else
- {
- jvResult["error"] = "malformedSteam";
- }
- }
- }
-
- if (jvRequest.isMember("rt_accounts"))
- {
- boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["rt_accounts"]);
-
- if (usnaAccoundIds.empty())
- {
- jvResult["error"] = "malformedAccount";
- }else
- {
- boost::mutex::scoped_lock sl(mLock);
-
- BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
- {
- mSubAccountInfo.insert(naAccountID);
- }
-
- mNetwork.unsubAccount(this, usnaAccoundIds,true);
- }
- }
-
- if (jvRequest.isMember("accounts"))
- {
- boost::unordered_set usnaAccoundIds = parseAccountIds(jvRequest["accounts"]);
-
- if (usnaAccoundIds.empty())
- {
- jvResult["error"] = "malformedAccount";
- }else
- {
- boost::mutex::scoped_lock sl(mLock);
-
- BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds)
- {
- mSubAccountInfo.insert(naAccountID);
- }
-
- mNetwork.unsubAccount(this, usnaAccoundIds,false);
- }
- }
-}
-
-void WSConnection::doRPC(Json::Value& jvResult, Json::Value& jvRequest)
-{
- if (jvRequest.isMember("rpc_command") )
- {
- jvResult = theApp->getRPCHandler().doCommand(
- jvRequest["rpc_command"].asString(),
- jvRequest.isMember("params")
- ? jvRequest["params"]
- : jvRequest,
- mHandler->getPublic() ? RPCHandler::GUEST : RPCHandler::ADMIN);
-
- jvResult["type"] = "response";
- }
- else
- {
- jvResult["error"] = "fieldNotCommand";
- }
-}
-
-// XXX Currently requires secret. Allow signed transaction as an alternative.
-void WSConnection::doSubmit(Json::Value& jvResult, Json::Value& jvRequest)
-{
- if (!jvRequest.isMember("tx_json"))
- {
- jvResult["error"] = "fieldNotFoundTxJson";
- }else if (!jvRequest.isMember("secret"))
- {
- jvResult["error"] = "fieldNotFoundSecret";
- }else
- {
- jvResult=theApp->getRPCHandler().handleJSONSubmit(jvRequest);
-
- // TODO: track the transaction mNetwork.subSubmit(this, jvResult["tx hash"] );
- }
-}
-
// vim:ts=4
diff --git a/src/cpp/ripple/WSConnection.h b/src/cpp/ripple/WSConnection.h
index 47cdf898b8..2bc4914d4c 100644
--- a/src/cpp/ripple/WSConnection.h
+++ b/src/cpp/ripple/WSConnection.h
@@ -21,10 +21,6 @@ public:
protected:
typedef void (WSConnection::*doFuncPtr)(Json::Value& jvResult, Json::Value &jvRequest);
- boost::mutex mLock;
- boost::unordered_set mSubAccountInfo;
- boost::unordered_set mSubAccountTransaction;
-
WSServerHandler* mHandler;
connection_ptr mConnection;
NetworkOPs& mNetwork;
@@ -44,13 +40,6 @@ public:
// Utilities
Json::Value invokeCommand(Json::Value& jvRequest);
- boost::unordered_set parseAccountIds(const Json::Value& jvArray);
-
- // Commands
- void doSubmit(Json::Value& jvResult, Json::Value& jvRequest);
- void doRPC(Json::Value& jvResult, Json::Value& jvRequest);
- void doSubscribe(Json::Value& jvResult, Json::Value& jvRequest);
- void doUnsubscribe(Json::Value& jvResult, Json::Value& jvRequest);
};
diff --git a/src/js/remote.js b/src/js/remote.js
index 20af9af4f0..40e931f9d7 100644
--- a/src/js/remote.js
+++ b/src/js/remote.js
@@ -147,6 +147,21 @@ Request.prototype.ripple_state = function (account, issuer, currency) {
return this;
};
+Request.prototype.accounts = function (accounts) {
+ if ("object" !== typeof accounts) {
+ accounts = [accounts];
+ }
+
+ // Process accounts parameters
+ var procAccounts = [];
+ for (var i = 0, l = accounts.length; i < l; i++) {
+ procAccounts.push(UInt160.json_rewrite(accounts[i]));
+ }
+ this.message.accounts = procAccounts;
+
+ return this;
+};
+
//
// Remote - access to a remote Ripple server via websocket.
//
@@ -434,13 +449,13 @@ Remote.prototype._connect_message = function (ws, json) {
if (!request) {
unexpected = true;
}
- else if ('success' === message.result) {
- if (this.trace) console.log("message: %s", json);
+ else if ('success' === message.status) {
+ if (this.trace) console.log("remote: response: %s", json);
- request.emit('success', message);
+ request.emit('success', message.result);
}
else if (message.error) {
- if (this.trace) console.log("message: %s", json);
+ if (this.trace) console.log("remote: error: %s", json);
request.emit('error', {
'error' : 'remoteError',
@@ -514,13 +529,33 @@ Remote.prototype.request = function (request) {
}
};
+Remote.prototype.request_server_info = function () {
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'server_info';
+
+ return request;
+};
+
+Remote.prototype.request_ledger = function (params) {
+ // XXX Does this require the server to be trusted?
+ //assert(this.trusted);
+
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'ledger';
+ request.message.params = params;
+
+ return request;
+};
+
// Only for unit testing.
Remote.prototype.request_ledger_hash = function () {
assert(this.trusted); // If not trusted, need to check proof.
var request = new Request(this, 'rpc');
- request.message.rpc_command = 'ledger_closed';
+ request.message.command = 'ledger_closed';
return request;
};
@@ -530,7 +565,7 @@ Remote.prototype.request_ledger_hash = function () {
Remote.prototype.request_ledger_current = function () {
var request = new Request(this, 'rpc');
- request.message.rpc_command = 'ledger_current';
+ request.message.command = 'ledger_current';
return request;
};
@@ -545,7 +580,7 @@ Remote.prototype.request_ledger_entry = function (type) {
var self = this;
var request = new Request(this, 'rpc');
- request.message.rpc_command = 'ledger_entry';
+ request.message.command = 'ledger_entry';
if (type)
this.type = type;
@@ -598,10 +633,28 @@ Remote.prototype.request_ledger_entry = function (type) {
return request;
};
-Remote.prototype.request_subscribe = function () {
+Remote.prototype.request_subscribe = function (streams) {
var request = new Request(this, 'subscribe');
- request.message.streams = [ 'ledger', 'server' ];
+ if (streams) {
+ if ("object" !== typeof streams) {
+ streams = [streams];
+ }
+ request.message.streams = streams;
+ }
+
+ return request;
+};
+
+Remote.prototype.request_unsubscribe = function (streams) {
+ var request = new Request(this, 'unsubscribe');
+
+ if (streams) {
+ if ("object" !== typeof streams) {
+ streams = [streams];
+ }
+ request.message.streams = streams;
+ }
return request;
};
@@ -611,18 +664,55 @@ Remote.prototype.request_transaction_entry = function (hash) {
var request = new Request(this, 'rpc');
- request.message.rpc_command = 'transaction_entry';
+ request.message.command = 'transaction_entry';
return request
.tx_hash(hash);
};
+Remote.prototype.request_ripple_lines_get = function (accountID) {
+ // XXX Does this require the server to be trusted?
+ //assert(this.trusted);
+
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'ripple_lines_get';
+ // XXX Convert API call to JSON
+ request.message.params = [accountID];
+
+ return request;
+};
+
+Remote.prototype.request_wallet_accounts = function (key) {
+ // XXX Does this require the server to be trusted?
+ //assert(this.trusted);
+
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'wallet_accounts';
+ // XXX Convert API call to JSON
+ request.message.params = [key];
+
+ return request;
+};
+
+Remote.prototype.request_account_tx = function (accountID, minLedger, maxLedger) {
+ // XXX Does this require the server to be trusted?
+ //assert(this.trusted);
+
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'account_tx';
+ // XXX Convert API call to JSON
+ request.message.params = [accountID, minLedger, maxLedger];
+
+ return request;
+};
+
// Submit a transaction.
Remote.prototype.submit = function (transaction) {
var self = this;
- if (this.trace) console.log("remote: submit: %s", JSON.stringify(transaction.tx_json));
-
if (transaction.secret && !this.trusted)
{
transaction.emit('error', {
@@ -663,7 +753,7 @@ Remote.prototype.submit = function (transaction) {
else {
var submit_request = new Request(this, 'rpc');
- submit_request.message.rpc_command = 'submit_json';
+ submit_request.message.command = 'submit_json';
submit_request.tx_json(transaction.tx_json);
submit_request.secret(transaction.secret);
@@ -690,7 +780,7 @@ Remote.prototype.submit = function (transaction) {
Remote.prototype._server_subscribe = function () {
var self = this;
- this.request_subscribe()
+ this.request_subscribe([ 'ledger', 'server' ])
.on('success', function (message) {
self.stand_alone = !!message.stand_alone;
@@ -720,7 +810,7 @@ Remote.prototype.ledger_accept = function () {
{
var request = new Request(this, 'rpc');
- request.message.rpc_command = 'ledger_accept';
+ request.message.command = 'ledger_accept';
request
.request();
@@ -771,7 +861,7 @@ Remote.prototype.set_account_seq = function (account, seq) {
var account = UInt160.json_rewrite(account);
if (!this.accounts[account]) this.accounts[account] = {};
-
+
this.accounts[account].seq = seq;
}
@@ -811,7 +901,7 @@ Remote.prototype.account_seq_cache = function (account, current) {
account_info.caching_seq_request = request;
}
- return request
+ return request;
};
// Mark an account's root node as dirty.
@@ -866,6 +956,49 @@ Remote.prototype.request_ripple_balance = function (account, issuer, currency, c
});
}
+Remote.prototype.request_unl_list = function () {
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'unl_list';
+
+ return request;
+};
+
+Remote.prototype.request_unl_add = function (addr, note) {
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'unl_add';
+ request.message.params = [addr, note];
+
+ return request;
+};
+
+Remote.prototype.request_unl_delete = function (publicKey) {
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'unl_delete';
+ request.message.params = [publicKey];
+
+ return request;
+};
+
+Remote.prototype.request_peers = function () {
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'peers';
+
+ return request;
+};
+
+Remote.prototype.request_connect = function (ip, port) {
+ var request = new Request(this, 'rpc');
+
+ request.message.command = 'connect';
+ request.message.params = [ip, port];
+
+ return request;
+};
+
Remote.prototype.transaction = function () {
return new Transaction(this);
};