From 27798c369d307230f6af104389a9cdc63cec727b Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 7 Oct 2012 00:18:13 -0700 Subject: [PATCH 1/3] WS: Add submit command. Rough. --- src/WSDoor.cpp | 191 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 170 insertions(+), 21 deletions(-) diff --git a/src/WSDoor.cpp b/src/WSDoor.cpp index f45299ec32..25eabb907f 100644 --- a/src/WSDoor.cpp +++ b/src/WSDoor.cpp @@ -5,7 +5,6 @@ #include "Config.h" #include "Log.h" #include "NetworkOPs.h" -#include "NetworkOPs.h" #include "utils.h" #include @@ -60,14 +59,15 @@ protected: WSServerHandler* mHandler; connection_ptr mConnection; + NetworkOPs& mNetwork; public: - WSConnection() - : mHandler((WSServerHandler*)(NULL)), - mConnection(connection_ptr()) { ; } +// WSConnection() +// : mHandler((WSServerHandler*)(NULL)), +// mConnection(connection_ptr()) { ; } WSConnection(WSServerHandler* wshpHandler, connection_ptr cpConnection) - : mHandler(wshpHandler), mConnection(cpConnection) { ; } + : mHandler(wshpHandler), mConnection(cpConnection), mNetwork(theApp->getOPs()) { ; } virtual ~WSConnection(); @@ -82,6 +82,7 @@ public: void doLedgerClosed(Json::Value& jvResult, const Json::Value& jvRequest); void doLedgerCurrent(Json::Value& jvResult, const Json::Value& jvRequest); void doLedgerEntry(Json::Value& jvResult, const Json::Value& jvRequest); + void doSubmit(Json::Value& jvResult, const Json::Value& jvRequest); // Streaming Commands void doAccountInfoSubscribe(Json::Value& jvResult, const Json::Value& jvRequest); @@ -305,6 +306,7 @@ Json::Value WSConnection::invokeCommand(const Json::Value& jvRequest) { "ledger_closed", &WSConnection::doLedgerClosed }, { "ledger_current", &WSConnection::doLedgerCurrent }, { "ledger_entry", &WSConnection::doLedgerEntry }, + { "submit", &WSConnection::doSubmit }, // Streaming commands: { "account_info_subscribe", &WSConnection::doAccountInfoSubscribe }, @@ -422,7 +424,7 @@ void WSConnection::doAccountInfoSubscribe(Json::Value& jvResult, const Json::Val mSubAccountInfo.insert(naAccountID); } - theApp->getOPs().subAccountInfo(this, usnaAccoundIds); + mNetwork.subAccountInfo(this, usnaAccoundIds); } } } @@ -454,7 +456,7 @@ void WSConnection::doAccountInfoUnsubscribe(Json::Value& jvResult, const Json::V mSubAccountInfo.erase(naAccountID); } - theApp->getOPs().unsubAccountInfo(this, usnaAccoundIds); + mNetwork.unsubAccountInfo(this, usnaAccoundIds); } } } @@ -486,7 +488,7 @@ void WSConnection::doAccountTransactionSubscribe(Json::Value& jvResult, const Js mSubAccountTransaction.insert(naAccountID); } - theApp->getOPs().subAccountTransaction(this, usnaAccoundIds); + mNetwork.subAccountTransaction(this, usnaAccoundIds); } } } @@ -518,14 +520,14 @@ void WSConnection::doAccountTransactionUnsubscribe(Json::Value& jvResult, const mSubAccountTransaction.erase(naAccountID); } - theApp->getOPs().unsubAccountTransaction(this, usnaAccoundIds); + mNetwork.unsubAccountTransaction(this, usnaAccoundIds); } } } void WSConnection::doLedgerAccountsSubcribe(Json::Value& jvResult, const Json::Value& jvRequest) { - if (!theApp->getOPs().subLedgerAccounts(this)) + if (!mNetwork.subLedgerAccounts(this)) { jvResult["error"] = "ledgerAccountsSubscribed"; } @@ -533,7 +535,7 @@ void WSConnection::doLedgerAccountsSubcribe(Json::Value& jvResult, const Json::V void WSConnection::doLedgerAccountsUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) { - if (!theApp->getOPs().unsubLedgerAccounts(this)) + if (!mNetwork.unsubLedgerAccounts(this)) { jvResult["error"] = "ledgerAccountsNotSubscribed"; } @@ -541,20 +543,20 @@ void WSConnection::doLedgerAccountsUnsubscribe(Json::Value& jvResult, const Json void WSConnection::doLedgerClosed(Json::Value& jvResult, const Json::Value& jvRequest) { - uint256 uLedger = theApp->getOPs().getClosedLedger(); + uint256 uLedger = mNetwork.getClosedLedger(); - jvResult["ledger_closed_index"] = theApp->getOPs().getLedgerID(uLedger); + jvResult["ledger_closed_index"] = mNetwork.getLedgerID(uLedger); jvResult["ledger_closed"] = uLedger.ToString(); } void WSConnection::doLedgerCurrent(Json::Value& jvResult, const Json::Value& jvRequest) { - jvResult["ledger_current_index"] = theApp->getOPs().getCurrentLedgerID(); + jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); } void WSConnection::doLedgerEntry(Json::Value& jvResult, const Json::Value& jvRequest) { - NetworkOPs& noNetwork = theApp->getOPs(); + NetworkOPs& noNetwork = mNetwork; uint256 uLedger = jvRequest.isMember("ledger") ? uint256(jvRequest["ledger"].asString()) : 0; uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0; @@ -781,7 +783,7 @@ void WSConnection::doLedgerEntry(Json::Value& jvResult, const Json::Value& jvReq void WSConnection::doServerSubscribe(Json::Value& jvResult, const Json::Value& jvRequest) { - if (!theApp->getOPs().subLedger(this)) + if (!mNetwork.subLedger(this)) { jvResult["error"] = "serverSubscribed"; } @@ -792,22 +794,169 @@ void WSConnection::doServerSubscribe(Json::Value& jvResult, const Json::Value& j // XXX Make sure these values are available before returning them. // XXX return connected status. - jvResult["ledger_closed"] = theApp->getOPs().getClosedLedger().ToString(); - jvResult["ledger_current_index"] = theApp->getOPs().getCurrentLedgerID(); + jvResult["ledger_closed"] = mNetwork.getClosedLedger().ToString(); + jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); } } void WSConnection::doServerUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) { - if (!theApp->getOPs().unsubLedger(this)) + if (!mNetwork.unsubLedger(this)) { jvResult["error"] = "serverNotSubscribed"; } } +// XXX Current requires secret. Allow signed transaction as an alternative. +void WSConnection::doSubmit(Json::Value& jvResult, const Json::Value& jvRequest) +{ + NewcoinAddress naAccount; + + if (!jvRequest.isMember("transaction")) + { + jvResult["error"] = "fieldNotFoundTransaction"; + } + else if (!jvRequest["transaction"].isMember("Account")) + { + jvResult["error"] = "fieldNotFoundAccount"; + } + else if (!naAccount.setAccountID(jvRequest["transaction"]["Account"].asString())) + { + jvResult["error"] = "malformedAccount"; + } + else if (!jvRequest.isMember("secret")) + { + jvResult["error"] = "fieldNotFoundSecret"; + } + else + { + Ledger::pointer lpCurrent = mNetwork.getCurrentLedger(); + SLE::pointer sleAccountRoot = mNetwork.getSLE(lpCurrent, Ledger::getAccountRootIndex(naAccount.getAccountID())); + + if (!sleAccountRoot) + { + // XXX Ignore transactions for accounts not created. + + jvResult["error"] = "accountNotFound"; + return; + } + + bool bHaveAuthKey = false; + NewcoinAddress naAuthorizedPublic; +#if 0 + + if (sleAccountRoot->isFieldPresent(sfAuthorizedKey)) + { + naAuthorizedPublic = mLedgerEntry->getFieldAccount(sfAuthorizedKey); + // Json::Value obj = getMasterGenerator(uLedger, naRegularSeed, naMasterGenerator); + } +#endif + + NewcoinAddress naSecret = NewcoinAddress::createSeedGeneric(jvRequest["secret"].asString()); + NewcoinAddress naMasterGenerator = NewcoinAddress::createGeneratorPublic(naSecret); + + // Find the index of Account from the master generator, so we can generate the public and private keys. + NewcoinAddress naMasterAccountPublic; + unsigned int iIndex = 0; + bool bFound = false; + + // Don't look at ledger entries to determine if the account exists. Don't want to leak to thin server that these accounts are + // related. + while (!bFound && iIndex != theConfig.ACCOUNT_PROBE_MAX) + { + naMasterAccountPublic.setAccountPublic(naMasterGenerator, iIndex); + + Log(lsWARNING) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID() << " : " << naAccount.humanAccountID(); + + bFound = naAccount.getAccountID() == naMasterAccountPublic.getAccountID(); + if (!bFound) + ++iIndex; + } + + if (!bFound) + { + jvResult["error"] = "accountNotMatched"; + return; + } + + // Use the generator to determine the associated public and private keys. + NewcoinAddress naGenerator = NewcoinAddress::createGeneratorPublic(naSecret); + NewcoinAddress naAccountPublic = NewcoinAddress::createAccountPublic(naGenerator, iIndex); + NewcoinAddress naAccountPrivate = NewcoinAddress::createAccountPrivate(naGenerator, naSecret, iIndex); + + if (bHaveAuthKey + // The generated pair must match authorized... + && naAuthorizedPublic.getAccountID() != naAccountPublic.getAccountID() + // ... or the master key must have been used. + && naAccount.getAccountID() != naAccountPublic.getAccountID()) + { + // std::cerr << "iIndex: " << iIndex << std::endl; + // std::cerr << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID()) << std::endl; + // std::cerr << "naAccountPublic: " << strHex(naAccountPublic.getAccountID()) << std::endl; + + jvResult["error"] = "passwordChanged"; + return; + } + + std::auto_ptr sopTrans; + + try + { + sopTrans = STObject::parseJson(jvRequest["transaction"]); + } + catch (std::exception& e) + { + jvResult["error"] = "malformedTransaction"; + jvResult["error_exception"] = e.what(); + return; + } + + sopTrans->setFieldVL(sfSigningPubKey, naAccountPublic.getAccountPublic()); + + SerializedTransaction::pointer stpTrans = boost::make_shared(*sopTrans); + + stpTrans->sign(naAccountPrivate); + + Transaction::pointer tpTrans; + + try + { + tpTrans = boost::make_shared(stpTrans, false); + } + catch (std::exception& e) + { + jvResult["error"] = "internalTransaction"; + jvResult["error_exception"] = e.what(); + return; + } + + try + { + tpTrans = mNetwork.submitTransaction(tpTrans); + } + catch (std::exception& e) + { + jvResult["error"] = "internalSubmit"; + jvResult["error_exception"] = e.what(); + return; + } + + try + { + jvResult["submitted"] = tpTrans->getJson(0); + } + catch (std::exception& e) + { + jvResult["error"] = "internalJson"; + jvResult["error_exception"] = e.what(); + return; + } + } +} + void WSConnection::doTransactionSubcribe(Json::Value& jvResult, const Json::Value& jvRequest) { - if (!theApp->getOPs().subTransaction(this)) + if (!mNetwork.subTransaction(this)) { jvResult["error"] = "TransactionsSubscribed"; } @@ -815,7 +964,7 @@ void WSConnection::doTransactionSubcribe(Json::Value& jvResult, const Json::Valu void WSConnection::doTransactionUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest) { - if (!theApp->getOPs().unsubTransaction(this)) + if (!mNetwork.unsubTransaction(this)) { jvResult["error"] = "TransactionsNotSubscribed"; } From 78bc3f54add23f12f14a6af9f2398e52b740549f Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 7 Oct 2012 00:19:04 -0700 Subject: [PATCH 2/3] JS: Add support for sumbit transactions and send_xns transaction. --- js/remote.js | 62 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/js/remote.js b/js/remote.js index ca8ab5f363..d079a780a9 100644 --- a/js/remote.js +++ b/js/remote.js @@ -12,11 +12,12 @@ var util = require('util'); var WebSocket = require('ws'); // --> trusted: truthy, if remote is trusted -var Remote = function (trusted, websocket_ip, websocket_port, trace) { +var Remote = function (trusted, websocket_ip, websocket_port, config, trace) { this.trusted = trusted; this.websocket_ip = websocket_ip; this.websocket_port = websocket_port; this.id = 0; + this.config = config; this.trace = trace; this.ledger_closed = undefined; this.ledger_current_index = undefined; @@ -40,7 +41,18 @@ var Remote = function (trusted, websocket_ip, websocket_port, trace) { var remoteConfig = function (config, server, trace) { var serverConfig = config.servers[server]; - return new Remote(serverConfig.trusted, serverConfig.websocket_ip, serverConfig.websocket_port, trace); + return new Remote(serverConfig.trusted, serverConfig.websocket_ip, serverConfig.websocket_port, config, trace); +}; + +var flags = { + // OfferCreate flags: + 'tfPassive' : 0x00010000, + + // Payment flags: + 'tfCreateAccount' : 0x00010000, + 'tfPartialPayment' : 0x00020000, + 'tfLimitQuality' : 0x00040000, + 'tfNoRippleDirect' : 0x00080000, }; // XXX This needs to be determined from the network. @@ -257,13 +269,10 @@ Remote.method('request_ledger_entry', function (req, onDone, onFailure) { // Submit a json transaction. // done(value) // XXX <-> value: { 'status', status, 'result' : result, ... } -Remote.method('submit', function (request, onDone, onFailure) { - if (this.trace) console.log("remote: submit: %s", request); +Remote.method('submit', function (req, onDone, onFailure) { + if (this.trace) console.log("remote: submit: %s", JSON.stringify(req)); - var req = {}; - req.command = 'submit'; - req.request = request; if (req.secret && !this.trusted) { @@ -308,7 +317,7 @@ Remote.method('account_seq', function (account, advance, onDone, onFailure) { if (advance) account_root_entry.seq += 1; - onDone(advance); + onDone(seq); } else { @@ -323,9 +332,9 @@ Remote.method('account_seq', function (account, advance, onDone, onFailure) { // Extract the seqence number from the account root entry. var seq = node.Sequence; - if (!self.accounts[account]) self.accounts[account] = {}; + if (!account_root_entry) self.accounts[account] = {}; - self.accounts[account].seq = seq + 1; + self.accounts[account].seq = seq + !!advance; onDone(seq); }, @@ -335,14 +344,14 @@ Remote.method('account_seq', function (account, advance, onDone, onFailure) { }); // A submit that fills in the sequence number. -Remote.method('submit_seq', function (transaction, onDirty, onDone, onFailure) { +Remote.method('submit_seq', function (trans, onDirty, onDone, onFailure) { var self = this; // Get the next sequence number for the account. - this.account_seq(transaction.fields.Signer, true, + this.account_seq(trans.transaction.Account, true, function (seq) { - transaction.seq = seq; - self.submit(transaction, onDone, onFailure); + trans.transaction.Sequence = seq; + self.submit(trans, onDone, onFailure); }, onFailure); }); @@ -352,8 +361,33 @@ Remote.method('dirty_account_root', function (account) { delete this.ledgers.current.account_root.account; }); +// +// Transactions +// + +Remote.method('send_xns', function (secret, src, dst, amount, create, onDone) { + var secret = this.config.accounts[src] ? this.config.accounts[src].secret : secret; + var src_account = this.config.accounts[src] ? this.config.accounts[src].account : src; + var dst_account = this.config.accounts[dst] ? this.config.accounts[dst].account : dst; + + this.submit_seq( + { + 'transaction' : { + 'TransactionType' : 'Payment', + 'Account' : src_account, + 'Destination' : dst_account, + 'Fee' : create ? fees.account_create : fees['default'], + 'Flags' : create ? flags.tfCreateAccount : 0, + 'Amount' : amount, + }, + 'secret' : secret, + }, function () { + }, onDone); +}); + exports.Remote = Remote; exports.remoteConfig = remoteConfig; exports.fees = fees; +exports.flags = flags; // vim:sw=2:sts=2:ts=8 From d4db971fdd24fd84efd84d481581d694d4aaa89d Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Sun, 7 Oct 2012 00:19:45 -0700 Subject: [PATCH 3/3] UT: Add test for send_xns. --- test/server.js | 1 + test/standalone-test.js | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/test/server.js b/test/server.js index 9e20569a58..4095b2a356 100644 --- a/test/server.js +++ b/test/server.js @@ -52,6 +52,7 @@ Server.method('serverSpawnSync', function() { config.newcoind, [ "-a", + "-v", "--conf=newcoind.cfg" ], { diff --git a/test/standalone-test.js b/test/standalone-test.js index 600e0a9df6..8dfe15e336 100644 --- a/test/standalone-test.js +++ b/test/standalone-test.js @@ -184,6 +184,16 @@ buster.testCase("Websocket commands", { }); }); }, + + 'create account' : + function (done) { + alpha.send_xns(undefined, 'root', 'alice', 10000, true, function (r) { + console.log(r); + + buster.refute(r.error); + done(); + }); + }, }); // vim:sw=2:sts=2:ts=8