From 56a7e48cd98cce586ea2bc8c5a118e86b285bed3 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Tue, 16 Oct 2012 20:32:40 -0700 Subject: [PATCH 01/17] UT: Allow tests to use existing server. --- test/config.js | 4 ++-- test/server.js | 36 +++++++++++++++++++++++------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/test/config.js b/test/config.js index dcafa641f4..6d448ae172 100644 --- a/test/config.js +++ b/test/config.js @@ -18,8 +18,8 @@ exports.servers = { 'rpc_port' : 5005, 'websocket_ip' : "127.0.0.1", 'websocket_port' : 6005, - 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h", - 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta" + // 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h", + // 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta" } }; diff --git a/test/server.js b/test/server.js index be6d8e7be2..85efe695b9 100644 --- a/test/server.js +++ b/test/server.js @@ -19,8 +19,9 @@ var child = require("child_process"); var servers = {}; // Create a server object -var Server = function (name) { +var Server = function (name, mock) { this.name = name; + this.mock = mock; }; // Return a server's rippled.cfg as string. @@ -92,20 +93,29 @@ Server.prototype.makeBase = function (done) { Server.prototype.start = function (done) { var self = this; - this.makeBase(function (e) { - if (e) { - throw e; - } - else { - self.serverSpawnSync(); - done(); - } - }); + if (this.mock) { + done(); + } + else { + this.makeBase(function (e) { + if (e) { + throw e; + } + else { + self.serverSpawnSync(); + done(); + } + }); + } }; // Stop a standalone server. Server.prototype.stop = function (done) { - if (this.child) { + if (this.mock) { + console.log("server: stop: mock"); + done(); + } + else if (this.child) { // Update the on exit to invoke done. this.child.on('exit', function (code, signal) { console.log("server: stop: server exited"); @@ -121,14 +131,14 @@ Server.prototype.stop = function (done) { }; // Start the named server. -exports.start = function (name, done) { +exports.start = function (name, done, mock) { if (servers[name]) { console.log("server: start: server already started."); } else { - var server = new Server(name); + var server = new Server(name, mock); servers[name] = server; From 305b037f595825b4c7d0e0dcbbdb20d3969f1954 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Wed, 17 Oct 2012 06:07:18 -0700 Subject: [PATCH 02/17] Don't allow a SerializerIterator to bind to a temporary Serializer. This was causing a bunch of deserialization bugs. --- src/Ledger.cpp | 10 ++++++---- src/Ledger.h | 2 +- src/SHAMap.h | 3 +-- src/SHAMapNodes.cpp | 12 ++++++------ src/SerializedLedger.cpp | 2 +- src/Serializer.h | 4 +++- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Ledger.cpp b/src/Ledger.cpp index 697e0eb1e2..dd67647b51 100644 --- a/src/Ledger.cpp +++ b/src/Ledger.cpp @@ -92,13 +92,15 @@ Ledger::Ledger(bool /* dummy */, Ledger& prevLedger) : Ledger::Ledger(const std::vector& rawLedger) : mClosed(false), mValidHash(false), mAccepted(false), mImmutable(true) { - setRaw(Serializer(rawLedger)); + Serializer s(rawLedger); + setRaw(s); } Ledger::Ledger(const std::string& rawLedger) : mClosed(false), mValidHash(false), mAccepted(false), mImmutable(true) { - setRaw(Serializer(rawLedger)); + Serializer s(rawLedger); + setRaw(s); } void Ledger::updateHash() @@ -118,7 +120,7 @@ void Ledger::updateHash() mValidHash = true; } -void Ledger::setRaw(const Serializer &s) +void Ledger::setRaw(Serializer &s) { SerializerIterator sit(s); mLedgerSeq = sit.get32(); @@ -311,7 +313,7 @@ bool Ledger::getTransaction(const uint256& txID, Transaction::pointer& txn, Tran } else if (type == SHAMapTreeNode::tnTRANSACTION_MD) { // in tree with metadata - SerializerIterator it(item->getData()); + SerializerIterator it(item->peekSerializer()); txn = theApp->getMasterTransaction().fetch(txID, false); if (!txn) txn = Transaction::sharedTransaction(it.getVL(), true); diff --git a/src/Ledger.h b/src/Ledger.h index aa9cc9f142..2b03b88ea9 100644 --- a/src/Ledger.h +++ b/src/Ledger.h @@ -112,7 +112,7 @@ public: // ledger signature operations void addRaw(Serializer &s) const; - void setRaw(const Serializer& s); + void setRaw(Serializer& s); uint256 getHash(); const uint256& getParentHash() const { return mParentHash; } diff --git a/src/SHAMap.h b/src/SHAMap.h index a30c1f8808..2518669b27 100644 --- a/src/SHAMap.h +++ b/src/SHAMap.h @@ -98,8 +98,7 @@ public: std::vector getData() const { return mData.getData(); } const std::vector& peekData() const { return mData.peekData(); } Serializer& peekSerializer() { return mData; } - void addRaw(Serializer &s) { s.addRaw(mData); } - void addRaw(std::vector& s) { s.insert(s.end(), mData.begin(), mData.end()); } + void addRaw(std::vector& s) const { s.insert(s.end(), mData.begin(), mData.end()); } void updateData(const std::vector& data) { mData=data; } diff --git a/src/SHAMapNodes.cpp b/src/SHAMapNodes.cpp index 2eaa0ed806..b14516813e 100644 --- a/src/SHAMapNodes.cpp +++ b/src/SHAMapNodes.cpp @@ -404,12 +404,12 @@ void SHAMapTreeNode::addRaw(Serializer& s, SHANodeFormat format) if (format == snfPREFIX) { s.add32(sHP_LeafNode); - mItem->addRaw(s); + s.addRaw(mItem->peekData()); s.add256(mItem->getTag()); } else { - mItem->addRaw(s); + s.addRaw(mItem->peekData()); s.add256(mItem->getTag()); s.add8(1); } @@ -419,11 +419,11 @@ void SHAMapTreeNode::addRaw(Serializer& s, SHANodeFormat format) if (format == snfPREFIX) { s.add32(sHP_TransactionID); - mItem->addRaw(s); + s.addRaw(mItem->peekData()); } else { - mItem->addRaw(s); + s.addRaw(mItem->peekData()); s.add8(0); } } @@ -432,12 +432,12 @@ void SHAMapTreeNode::addRaw(Serializer& s, SHANodeFormat format) if (format == snfPREFIX) { s.add32(sHP_TransactionNode); - mItem->addRaw(s); + s.addRaw(mItem->peekData()); s.add256(mItem->getTag()); } else { - mItem->addRaw(s); + s.addRaw(mItem->peekData()); s.add256(mItem->getTag()); s.add8(4); } diff --git a/src/SerializedLedger.cpp b/src/SerializedLedger.cpp index 1e29a99c22..4ffecf2311 100644 --- a/src/SerializedLedger.cpp +++ b/src/SerializedLedger.cpp @@ -21,7 +21,7 @@ SerializedLedgerEntry::SerializedLedgerEntry(SerializerIterator& sit, const uint SerializedLedgerEntry::SerializedLedgerEntry(const Serializer& s, const uint256& index) : STObject(sfLedgerEntry), mIndex(index) { - SerializerIterator sit(s); + SerializerIterator sit(const_cast(s)); // we know 's' isn't going away set(sit); uint16 type = getFieldU16(sfLedgerEntryType); diff --git a/src/Serializer.h b/src/Serializer.h index 8666efe4c3..93add9d61d 100644 --- a/src/Serializer.h +++ b/src/Serializer.h @@ -143,7 +143,9 @@ protected: int mPos; public: - SerializerIterator(const Serializer& s) : mSerializer(s), mPos(0) { ; } + + // Reference is not const because we don't want to bind to a temporary + SerializerIterator(Serializer& s) : mSerializer(s), mPos(0) { ; } void reset(void) { mPos = 0; } void setPos(int p) { mPos = p; } From 67be1a8a55326a3caaed0d73b062ef1e2a556096 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Wed, 17 Oct 2012 07:03:54 -0700 Subject: [PATCH 03/17] This floods the log during testing. --- src/SHAMap.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/SHAMap.cpp b/src/SHAMap.cpp index b556a18021..7bb86ac300 100644 --- a/src/SHAMap.cpp +++ b/src/SHAMap.cpp @@ -704,10 +704,7 @@ void SHAMapItem::dump() SHAMapTreeNode::pointer SHAMap::fetchNodeExternal(const SHAMapNode& id, const uint256& hash) { if (!theApp->running()) - { - cLog(lsTRACE) << "Trying to fetch external node with application not running"; throw SHAMapMissingNode(mType, id, hash); - } HashedObject::pointer obj(theApp->getHashedObjectStore().retrieve(hash)); if (!obj) From 9ee4ec06499ebf287f1de9d3d6e88443dda2b462 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Wed, 17 Oct 2012 10:23:03 -0700 Subject: [PATCH 04/17] Some cleanups. --- src/LedgerProposal.cpp | 6 +++--- src/ScriptData.h | 5 +---- src/Transaction.cpp | 4 +--- src/TransactionMeta.h | 2 +- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/LedgerProposal.cpp b/src/LedgerProposal.cpp index a334a612f7..e7953abad5 100644 --- a/src/LedgerProposal.cpp +++ b/src/LedgerProposal.cpp @@ -22,10 +22,10 @@ LedgerProposal::LedgerProposal(const uint256& pLgr, uint32 seq, const uint256& t LedgerProposal::LedgerProposal(const NewcoinAddress& naSeed, const uint256& prevLgr, const uint256& position, uint32 closeTime) : - mPreviousLedger(prevLgr), mCurrentHash(position), mCloseTime(closeTime), mProposeSeq(0) + mPreviousLedger(prevLgr), mCurrentHash(position), mCloseTime(closeTime), mProposeSeq(0), + mPublicKey(NewcoinAddress::createNodePublic(naSeed)), + mPrivateKey(NewcoinAddress::createNodePrivate(naSeed)) { - mPublicKey = NewcoinAddress::createNodePublic(naSeed); - mPrivateKey = NewcoinAddress::createNodePrivate(naSeed); mPeerID = mPublicKey.getNodeID(); mTime = boost::posix_time::second_clock::universal_time(); } diff --git a/src/ScriptData.h b/src/ScriptData.h index 66fe2ed3e4..0e84e8f920 100644 --- a/src/ScriptData.h +++ b/src/ScriptData.h @@ -57,10 +57,7 @@ class Uint160Data : public Data { uint160 mValue; public: - Uint160Data(uint160 value) - { - mValue=value; - } + Uint160Data(uint160 value) : mValue(value) { ; } bool isUint160(){ return(true); } uint160 getUint160(){ return(mValue); } }; diff --git a/src/Transaction.cpp b/src/Transaction.cpp index 9a368c1c64..6ef87abaf7 100644 --- a/src/Transaction.cpp +++ b/src/Transaction.cpp @@ -60,10 +60,8 @@ Transaction::Transaction( uint32 uSeq, const STAmount& saFee, uint32 uSourceTag) : - mStatus(NEW), mResult(temUNCERTAIN) + mAccountFrom(naSourceAccount), mFromPubKey(naPublicKey), mStatus(NEW), mResult(temUNCERTAIN) { - mAccountFrom = naSourceAccount; - mFromPubKey = naPublicKey; assert(mFromPubKey.isValid()); mTransaction = boost::make_shared(ttKind); diff --git a/src/TransactionMeta.h b/src/TransactionMeta.h index 78f3378f5b..81b8c2a44d 100644 --- a/src/TransactionMeta.h +++ b/src/TransactionMeta.h @@ -28,7 +28,7 @@ protected: public: TransactionMetaSet() : mLedger(0), mResult(255) { ; } - TransactionMetaSet(const uint256& txID, uint32 ledger) : mTransactionID(txID), mLedger(ledger) { ; } + TransactionMetaSet(const uint256& txID, uint32 ledger) : mTransactionID(txID), mLedger(ledger), mResult(255) { ; } TransactionMetaSet(const uint256& txID, uint32 ledger, const std::vector&); void init(const uint256& transactionID, uint32 ledger); From 851768848b890f045e0b9f72b0a3e4619d03ac66 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 13:25:34 -0700 Subject: [PATCH 05/17] WS: Do not announce unvalidated ledgers. --- src/NetworkOPs.cpp | 4 ++++ src/WSDoor.cpp | 11 +++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/NetworkOPs.cpp b/src/NetworkOPs.cpp index 477cca2275..c5059ee6ad 100644 --- a/src/NetworkOPs.cpp +++ b/src/NetworkOPs.cpp @@ -935,6 +935,10 @@ void NetworkOPs::pubAccountInfo(const NewcoinAddress& naAccountID, const Json::V void NetworkOPs::pubLedger(Ledger::ref lpAccepted) { + // Don't publish to clients ledgers we don't trust. + if (NetworkOPs::omDISCONNECTED == getOperatingMode()) + return; + { boost::interprocess::sharable_lock sl(mMonitorLock); diff --git a/src/WSDoor.cpp b/src/WSDoor.cpp index d5c4a03a31..acdd76a78a 100644 --- a/src/WSDoor.cpp +++ b/src/WSDoor.cpp @@ -801,6 +801,9 @@ void WSConnection::doLedgerEntry(Json::Value& jvResult, const Json::Value& jvReq } } +// The objective is to allow the client to know the server's status. The only thing that show the server is fully operating is the +// stream of ledger_closeds. Therefore, that is all that is provided. A client can drop servers that do not provide recent +// ledger_closeds. void WSConnection::doServerSubscribe(Json::Value& jvResult, const Json::Value& jvRequest) { if (!mNetwork.subLedger(this)) @@ -812,10 +815,10 @@ void WSConnection::doServerSubscribe(Json::Value& jvResult, const Json::Value& j if (theConfig.RUN_STANDALONE) jvResult["stand_alone"] = 1; - // XXX Make sure these values are available before returning them. - // XXX return connected status. - jvResult["ledger_closed"] = mNetwork.getClosedLedger().ToString(); - jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); + if (NetworkOPs::omDISCONNECTED != mNetwork.getOperatingMode()) { + jvResult["ledger_closed"] = mNetwork.getClosedLedger().ToString(); + jvResult["ledger_current_index"] = mNetwork.getCurrentLedgerID(); + } } } From edf47a7a07c1085ad96441f8d2859b170e74cfa4 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 13:26:31 -0700 Subject: [PATCH 06/17] JS: Automatically maintain connection. --- js/amount.js | 2 +- js/remote.js | 363 ++++++++++++++++++++++++----------------- test/remote-test.js | 26 +-- test/websocket-test.js | 20 ++- 4 files changed, 235 insertions(+), 176 deletions(-) diff --git a/js/amount.js b/js/amount.js index 51e18a5630..652e8438ff 100644 --- a/js/amount.js +++ b/js/amount.js @@ -1,4 +1,4 @@ -// Represent Newcoin amounts and currencies. +// Represent Ripple amounts and currencies. // - Numbers in hex are big-endian. var utils = require('./utils.js'); diff --git a/js/remote.js b/js/remote.js index bed251dd7c..1f300b569e 100644 --- a/js/remote.js +++ b/js/remote.js @@ -94,6 +94,18 @@ Request.prototype.transaction = function (t) { return this; }; +// +// Remote - access to a remote Ripple server via websocket. +// +// Events: +// 'connectted' +// 'disconnected' +// 'state': +// - 'online' : connectted and subscribed +// - 'offline' : not subscribed or not connectted. +// 'ledger_closed' +// + // --> trusted: truthy, if remote is trusted var Remote = function (trusted, websocket_ip, websocket_port, config, trace) { this.trusted = trusted; @@ -105,6 +117,11 @@ var Remote = function (trusted, websocket_ip, websocket_port, config, trace) { this.ledger_closed = undefined; this.ledger_current_index = undefined; this.stand_alone = undefined; + this.online_target = false; + this.online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' + this.state = 'offline'; // 'online', 'offline' + this.retry_timer = undefined; + this.retry = undefined; // Cache information for accounts. this.accounts = { @@ -159,186 +176,232 @@ var fees = { 'offer' : Amount.from_json("100"), }; -Remote.prototype.connect_helper = function () { +// Set the emited state: 'online' or 'offline' +Remote.prototype._set_state = function (state) { + if (this.trace) console.log("remote: set_state: %s", state); + + if (this.state !== state) { + this.state = state; + + this.emit('state', state); + switch (state) { + case 'online': + this.online_state = 'open'; + this.emit('connected'); + break; + case 'offline': + this.online_state = 'closed'; + this.emit('disconnected'); + break; + } + } +}; + +// Set the target online state. Defaults to false. +Remote.prototype.connect = function (online) { + var target = undefined === online || online; + + if (this.online_target != target) { + this.online_target = target; + + // If we were in a stable state, go dynamic. + switch (this.online_state) { + case 'open': + if (!target) this._connect_stop(); + break; + + case 'closed': + if (target) this._connect_retry(); + break; + } + } + + return this; +}; + +// Stop from open state. +Remote.prototype._connect_stop = function () { + delete this.ws.onerror; + delete this.ws.onclose; + + this.ws.terminate(); + delete this.ws; + + this._set_state('offline'); +}; + +// Implictly we are not connected. +Remote.prototype._connect_retry = function () { + var self = this; + + if (!self.online_target) { + // Do not continue trying to connect. + this._set_state('offline'); + } + else if ('connecting' !== this.online_state) { + // New to connecting state. + this.online_state = 'connecting'; + this.retry = 0; + + this._connect_start(); + } + else + { + // Delay and retry. + this.retry += 1; + this.retry_timer = setTimeout(function () { + if (self.trace) console.log("remote: retry"); + + if (self.online_target) { + self._connect_start(); + } + else { + self._connect_retry(); + } + }, this.retry < 40 ? 1000/20 : 1000); // 20 times per second for 2 seconds then once per second. + } +}; + +Remote.prototype._connect_start = function () { + // Note: as a browser client can't make encrypted connections to random ips + // with self-signed certs as the user must have pre-approved the self-signed certs. + var self = this; + var url = util.format("ws://%s:%s", this.websocket_ip, this.websocket_port); - if (this.trace) console.log("remote: connect: %s", this.url); + if (this.trace) console.log("remote: connect: %s", url); - var ws = this.ws = new WebSocket(this.url);; + var ws = this.ws = new WebSocket(url); ws.response = {}; ws.onopen = function () { - if (self.trace) console.log("remote: onopen: %s", ws.readyState); + if (self.trace) console.log("remote: onopen: %s: online_target=%s", ws.readyState, self.online_target); - ws.onclose = undefined; - ws.onerror = undefined; - - clearTimeout(self.connect_timer); delete self.connect_timer; - clearTimeout(self.retry_timer); delete self.retry_timer; + ws.onerror = function () { + if (self.trace) console.log("remote: onerror: %s", ws.readyState); - self.done(ws.readyState); + delete ws.onclose; + + self._connect_retry(); + }; + + ws.onclose = function () { + if (self.trace) console.log("remote: onclose: %s", ws.readyState); + + delete ws.onerror; + + self._connect_retry(); + }; + + if (self.online_target) { + if (self.trace) console.log("remote: onopen: %s: online_target2=%s", ws.readyState, self.online_target); + self._set_state('online'); + } + else { + if (self.trace) console.log("remote: onopen: %s: online_target3=%s", ws.readyState, self.online_target); + self._connect_stop(); + } }; ws.onerror = function () { if (self.trace) console.log("remote: onerror: %s", ws.readyState); + + delete ws.onclose; - ws.onclose = undefined; - - if (self.expire) { - if (self.trace) console.log("remote: was expired"); - - ws.onerror = undefined; - self.done(ws.readyState); - - } else { - // Delay and retry. - - clearTimeout(self.retry_timer); - self.retry_timer = setTimeout(function () { - if (self.trace) console.log("remote: retry"); - - self.connect_helper(); - }, 50); // Retry rate 50ms. - } + self._connect_retry(); }; - - // Covers failure to open. + + // Failure to open. ws.onclose = function () { if (self.trace) console.log("remote: onclose: %s", ws.readyState); - ws.onerror = undefined; + delete ws.onerror; - clearTimeout(self.retry_timer); - delete self.retry_timer; - - self.done(ws.readyState); + self._connect_retry(); }; // Node's ws module doesn't pass arguments to onmessage. ws.on('message', function (json, flags) { - var message = JSON.parse(json); - var unexpected = false; - var request; - - if ('object' !== typeof message) { - unexpected = true; - } - else { - switch (message.type) { - case 'response': - { - request = ws.response[message.id]; - - if (!request) { - unexpected = true; - } - else if ('success' === message.result) { - if (self.trace) console.log("message: %s", json); - - request.emit('success', message); - } - else if (message.error) { - if (self.trace) console.log("message: %s", json); - - request.emit('error', { - 'error' : 'remoteError', - 'error_message' : 'Remote reported an error.', - 'remote' : message, - }); - } - } - break; - - case 'ledgerClosed': - // XXX If not trusted, need to verify we consider ledger closed. - // XXX Also need to consider a slow server or out of order response. - // XXX Be more defensive fields could be missing or of wrong type. - // YYY Might want to do some cache management. - - self.ledger_closed = message.ledger_closed; - self.ledger_current_index = message.ledger_closed_index + 1; - - self.emit('ledger_closed', self.ledger_closed, self.ledger_closed_index); - break; - - default: - unexpected = true; - break; - } - } - - if (!unexpected) { - } - // Unexpected response from remote. - // XXX This isn't so robust. Hard fails should probably only happen in a debugging scenairo. - else if (self.trusted) { - // Remote is trusted, report an error. - console.log("unexpected message from trusted remote: %s", json); - - (request || self).emit('error', { - 'error' : 'remoteUnexpected', - 'error_message' : 'Unexpected response from remote.' - }); - } - else { - // Treat as a disconnect. - if (self.trace) console.log("unexpected message from untrusted remote: %s", json); - - // XXX All pending request need this treatment and need to actionally disconnect. - (request || self).emit('error', { - 'error' : 'remoteDisconnected', - 'error_message' : 'Remote disconnected.' - }); - } - }); + self._connect_message(json, flags); + }); }; -// Target state is connectted. -// XXX Get rid of 'done' use event model. -// done(readyState): -// --> readyState: OPEN, CLOSED -Remote.prototype.connect = function (done, timeout) { - var self = this; - - this.url = util.format("ws://%s:%s", this.websocket_ip, this.websocket_port); - this.done = done; - - if (timeout) { - if (this.trace) console.log("remote: expire: false"); - - this.expire = false; +Remote.prototype._connect_message = function (json, flags) { + var message = JSON.parse(json); + var unexpected = false; + var request; - this.connect_timer = setTimeout(function () { - if (self.trace) console.log("remote: expire: timeout"); - - delete self.connect_timer; - self.expire = true; - }, timeout); - - } else { - if (this.trace) console.log("remote: expire: false"); - this.expire = true; + if ('object' !== typeof message) { + unexpected = true; } - - this.connect_helper(); -}; + else { + switch (message.type) { + case 'response': + { + request = this.ws.response[message.id]; -// Target stated is disconnected. -// Note: if exiting or other side is going away, don't need to disconnect. -Remote.prototype.disconnect = function (done) { - var self = this; - var ws = this.ws; + if (!request) { + unexpected = true; + } + else if ('success' === message.result) { + if (this.trace) console.log("message: %s", json); - if (this.trace) console.log("remote: disconnect"); - - ws.onclose = function () { - if (self.trace) console.log("remote: onclose: %s", ws.readyState); - done(ws.readyState); - }; + request.emit('success', message); + } + else if (message.error) { + if (this.trace) console.log("message: %s", json); - // ws package has a hard coded 30 second timeout. - ws.close(); + request.emit('error', { + 'error' : 'remoteError', + 'error_message' : 'Remote reported an error.', + 'remote' : message, + }); + } + } + break; + + case 'ledgerClosed': + // XXX If not trusted, need to verify we consider ledger closed. + // XXX Also need to consider a slow server or out of order response. + // XXX Be more defensive fields could be missing or of wrong type. + // YYY Might want to do some cache management. + + this.ledger_closed = message.ledger_closed; + this.ledger_current_index = message.ledger_closed_index + 1; + + this.emit('ledger_closed', this.ledger_closed, this.ledger_closed_index); + break; + + default: + unexpected = true; + break; + } + } + + if (!unexpected) { + } + // Unexpected response from remote. + // XXX This isn't so robust. Hard fails should probably only happen in a debugging scenairo. + else if (this.trusted) { + // Remote is trusted, report an error. + console.log("unexpected message from trusted remote: %s", json); + + (request || this).emit('error', { + 'error' : 'remoteUnexpected', + 'error_message' : 'Unexpected response from remote.' + }); + } + else { + // Treat as a disconnect. + if (this.trace) console.log("unexpected message from untrusted remote: %s", json); + + // XXX All pending request need this treatment and need to actionally disconnect. + (request || this).emit('error', { + 'error' : 'remoteDisconnected', + 'error_message' : 'Remote disconnected.' + }); + } }; // Send a request. diff --git a/test/remote-test.js b/test/remote-test.js index b45f1a62ab..ebcdf731b5 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -10,7 +10,7 @@ var Amount = amount.Amount; var fastTearDown = true; // How long to wait for server to start. -var serverDelay = 1500; +var serverDelay = 1500; // XXX Not implemented. buster.testRunner.timeout = 5000; @@ -23,32 +23,22 @@ buster.testCase("Remote functions", { alpha = remote.remoteConfig(config, "alpha"); - alpha.connect(function (stat) { - buster.assert(1 == stat); // OPEN - done(); - }, serverDelay); + alpha + .on('connected', done) + .connect(); }); }, 'tearDown' : function (done) { - if (fastTearDown) { - // Fast tearDown - server.stop("alpha", function (e) { - buster.refute(e); - done(); - }); - } - else { - alpha.disconnect(function (stat) { - buster.assert(3 == stat); // CLOSED - + alpha + .on('disconnected', function () { server.stop("alpha", function (e) { buster.refute(e); done(); }); - }); - } + }) + .connect(false); }, 'request_ledger_current' : diff --git a/test/websocket-test.js b/test/websocket-test.js index 9e6e872614..055a0aab23 100644 --- a/test/websocket-test.js +++ b/test/websocket-test.js @@ -32,14 +32,20 @@ buster.testCase("WebSocket connection", { function (done) { var alpha = remote.remoteConfig(config, "alpha", 'TRACE'); - alpha.connect(function (stat) { - buster.assert.equals(stat, 1); // OPEN + alpha + .on('connected', function () { + // OPEN + buster.assert(true); - alpha.disconnect(function (stat) { - buster.assert.equals(stat, 3); // CLOSED - done(); - }); - }, serverDelay); + alpha + .on('disconnected', function () { + // CLOSED + buster.assert(true); + done(); + }) + .connect(false); + }) + .connect(); }, }); From c3d03a9f280cb653136346c666e5ad5da8d74b19 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 14:55:00 -0700 Subject: [PATCH 07/17] Fix parseJson for Amount. --- src/Amount.cpp | 80 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/src/Amount.cpp b/src/Amount.cpp index 7b9252e8fd..8abd8bbfc4 100644 --- a/src/Amount.cpp +++ b/src/Amount.cpp @@ -12,6 +12,8 @@ #include "SerializedTypes.h" #include "utils.h" +SETUP_LOG(); + uint64 STAmount::uRateOne = STAmount::getRate(STAmount(1), STAmount(1)); // --> sCurrency: "", "XNS", or three letter ISO code. @@ -63,15 +65,21 @@ STAmount::STAmount(SField::ref n, const Json::Value& v) if (v.isObject()) { - value = v["value"]; - currency = v["currency"]; - issuer = v["issuer"]; + cLog(lsTRACE) + << boost::str(boost::format("value='%s', currency='%s', issuer='%s'") + % v["value"].asString() + % v["currency"].asString() + % v["issuer"].asString()); + + value = v["value"]; + currency = v["currency"]; + issuer = v["issuer"]; } else if (v.isArray()) { - value = v.get(Json::UInt(0), 0); - currency = v.get(Json::UInt(1), Json::nullValue); - issuer = v.get(Json::UInt(2), Json::nullValue); + value = v.get(Json::UInt(0), 0); + currency = v.get(Json::UInt(1), Json::nullValue); + issuer = v.get(Json::UInt(2), Json::nullValue); } else if (v.isString()) { @@ -93,6 +101,31 @@ STAmount::STAmount(SField::ref n, const Json::Value& v) mIsNative = !currency.isString() || currency.asString().empty() || (currency.asString() == SYSTEM_CURRENCY_CODE); + if (!mIsNative) { + if (!currencyFromString(mCurrency, currency.asString())) + throw std::runtime_error("invalid currency"); + + if (!issuer.isString()) + throw std::runtime_error("invalid issuer"); + + if (issuer.size() == (160/4)) + { + mIssuer.SetHex(issuer.asString()); + } + else + { + NewcoinAddress is; + + if(!is.setAccountID(issuer.asString())) + throw std::runtime_error("invalid issuer"); + + mIssuer = is.getAccountID(); + } + + if (mIssuer.isZero()) + throw std::runtime_error("invalid issuer"); + } + if (value.isInt()) { if (value.asInt() >= 0) @@ -102,9 +135,15 @@ STAmount::STAmount(SField::ref n, const Json::Value& v) mValue = -value.asInt(); mIsNegative = true; } + + canonicalize(); } else if (value.isUInt()) + { mValue = v.asUInt(); + + canonicalize(); + } else if (value.isString()) { if (mIsNative) @@ -117,35 +156,18 @@ STAmount::STAmount(SField::ref n, const Json::Value& v) mValue = -val; mIsNegative = true; } + + canonicalize(); } else + { setValue(value.asString()); + } } else throw std::runtime_error("invalid amount type"); - if (mIsNative) - return; - - if (!currencyFromString(mCurrency, currency.asString())) - throw std::runtime_error("invalid currency"); - - if (!issuer.isString()) - throw std::runtime_error("invalid issuer"); - - if (issuer.size() == (160/4)) - mIssuer.SetHex(issuer.asString()); - else - { - NewcoinAddress is; - if(!is.setAccountID(issuer.asString())) - throw std::runtime_error("invalid issuer"); - mIssuer = is.getAccountID(); - } - if (mIssuer.isZero()) - throw std::runtime_error("invalid issuer"); - - canonicalize(); + cLog(lsTRACE) << "Parsed: " << this->getJson(0); } std::string STAmount::createHumanCurrency(const uint160& uCurrency) @@ -196,7 +218,7 @@ std::string STAmount::createHumanCurrency(const uint160& uCurrency) // Assumes trusted input. bool STAmount::setValue(const std::string& sAmount) -{ // Note: mIsNative must be set already! +{ // Note: mIsNative and mCurrency must be set already! uint64 uValue; int iOffset; size_t uDecimal = sAmount.find_first_of(mIsNative ? "^" : "."); From b23f596e3fb4d61779d5c015abb58bbcfce4c1ab Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 15:37:23 -0700 Subject: [PATCH 08/17] JS: Automatically subscribe to server. --- js/remote.js | 25 +++++++++++++++---------- test/remote-test.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/js/remote.js b/js/remote.js index 1f300b569e..e36f790081 100644 --- a/js/remote.js +++ b/js/remote.js @@ -189,6 +189,7 @@ Remote.prototype._set_state = function (state) { this.online_state = 'open'; this.emit('connected'); break; + case 'offline': this.online_state = 'closed'; this.emit('disconnected'); @@ -295,11 +296,11 @@ Remote.prototype._connect_start = function () { }; if (self.online_target) { - if (self.trace) console.log("remote: onopen: %s: online_target2=%s", ws.readyState, self.online_target); + self._server_subscribe(); // Automatically subscribe. + self._set_state('online'); } else { - if (self.trace) console.log("remote: onopen: %s: online_target3=%s", ws.readyState, self.online_target); self._connect_stop(); } }; @@ -323,11 +324,12 @@ Remote.prototype._connect_start = function () { // Node's ws module doesn't pass arguments to onmessage. ws.on('message', function (json, flags) { - self._connect_message(json, flags); + self._connect_message(ws, json, flags); }); }; -Remote.prototype._connect_message = function (json, flags) { +// It is possible for messages to be dispatched after the connection is closed. +Remote.prototype._connect_message = function (ws, json, flags) { var message = JSON.parse(json); var unexpected = false; var request; @@ -339,7 +341,7 @@ Remote.prototype._connect_message = function (json, flags) { switch (message.type) { case 'response': { - request = this.ws.response[message.id]; + request = ws.response[message.id]; if (!request) { unexpected = true; @@ -558,20 +560,23 @@ Remote.prototype.submit = function (transaction) { // Subscribe to a server to get 'ledger_closed' events. // 'subscribed' : This command was successful. // 'ledger_closed : ledger_closed and ledger_current_index are updated. -Remote.prototype.server_subscribe = function () { +Remote.prototype._server_subscribe = function () { var self = this; var request = new Request(this, 'server_subscribe'); request. on('success', function (message) { - self.ledger_closed = message.ledger_closed; - self.ledger_current_index = message.ledger_current_index; self.stand_alone = !!message.stand_alone; - self.emit('subscribed'); + if (message.ledger_closed && message.ledger_current_index) { + self.ledger_closed = message.ledger_closed; + self.ledger_current_index = message.ledger_current_index; - self.emit('ledger_closed', self.ledger_closed, self.ledger_current_index-1); + self.emit('ledger_closed', self.ledger_closed, self.ledger_current_index-1); + } + + self.emit('subscribed'); }) .request(); diff --git a/test/remote-test.js b/test/remote-test.js index ebcdf731b5..5a99b43c27 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -213,6 +213,46 @@ buster.testCase("Remote functions", { }) .submit(); }, + + "create account final" : + function (done) { + var got_proposed; + var got_success; + + alpha.transaction() + .payment('root', 'alice', Amount.from_json("10000")) + .flags('CreateAccount') + .on('success', function (r) { + console.log("create_account: %s", JSON.stringify(r)); + + got_success = true; + }) + .on('error', function (m) { + console.log("error: %s", m); + + buster.assert(false); + }) + .on('final', function (m) { + console.log("final: %s", JSON.stringify(m)); + + buster.assert(got_success && got_proposed); + done(); + }) + .on('proposed', function (m) { + console.log("proposed: %s", JSON.stringify(m)); + + // buster.assert.equals(m.result, 'terNO_DST'); + buster.assert.equals(m.result, 'tesSUCCESS'); + + got_proposed = true; + + alpha.ledger_accept(); + }) + .on('status', function (s) { + console.log("status: %s", JSON.stringify(s)); + }) + .submit(); + }, }); // vim:sw=2:sts=2:ts=8 From 2f4867caec2c6c20820d5e022a23d960a65c5be3 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 15:41:18 -0700 Subject: [PATCH 09/17] JS: report final transaction status. --- js/remote.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/js/remote.js b/js/remote.js index e36f790081..90f04c4b38 100644 --- a/js/remote.js +++ b/js/remote.js @@ -813,11 +813,7 @@ Transaction.prototype.submit = function () { // XXX make sure self.hash is available. self.remote.request_transaction_entry(self.hash, ledger_closed) .on('success', function (message) { - // XXX Fake results for now. - if (!message.metadata.result) - message.metadata.result = 'tesSUCCESS'; - - self.set_state(message.metadata.result); // XXX Untested. + self.set_state(message.metadata.TransactionResult); self.emit('final', message); }) .on('error', function (message) { From d7ccf1f7a6ae7a870f6f08bd1b95a3104414e191 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 16:50:41 -0700 Subject: [PATCH 10/17] Cosmetic. --- src/TransactionErr.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TransactionErr.h b/src/TransactionErr.h index adb381909f..e2aa0e4436 100644 --- a/src/TransactionErr.h +++ b/src/TransactionErr.h @@ -121,3 +121,4 @@ std::string transToken(TER terCode); std::string transHuman(TER terCode); #endif +// vim:ts=4 From bacfcff48cb9b114c4b97f22b444118da041d2bd Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 18:15:04 -0700 Subject: [PATCH 11/17] Improve remote.js and test starts. - Transactions now emit 'pending' and 'lost' - Remote now supports once(). - Tests now use 'ledger_closed' to know when to start. - Fix quick close of remote. --- js/remote.js | 60 +++++++++++++++++++-------- test/remote-test.js | 2 +- test/send-test.js | 99 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 18 deletions(-) create mode 100644 test/send-test.js diff --git a/js/remote.js b/js/remote.js index 90f04c4b38..ed89987bd7 100644 --- a/js/remote.js +++ b/js/remote.js @@ -26,13 +26,17 @@ var Amount = amount.Amount; // 'remoteUnexpected' // 'remoteDisconnected' var Request = function (remote, command) { + var self = this; + this.message = { 'command' : command, 'id' : undefined, }; this.remote = remote; - this.on('request', this.request_default); + this.on('request', function () { + self.request_default(); + }); }; Request.prototype = new EventEmitter; @@ -44,6 +48,12 @@ Request.prototype.on = function (e, c) { return this; }; +Request.prototype.once = function (e, c) { + EventEmitter.prototype.once.call(this, e, c); + + return this; +}; + // Send the request to a remote. Request.prototype.request = function (remote) { this.emit('request', remote); @@ -103,7 +113,8 @@ Request.prototype.transaction = function (t) { // 'state': // - 'online' : connectted and subscribed // - 'offline' : not subscribed or not connectted. -// 'ledger_closed' +// 'ledger_closed': A good indicate of ready to serve. +// 'subscribed' : This indicates stand-alone is available. // // --> trusted: truthy, if remote is trusted @@ -296,9 +307,10 @@ Remote.prototype._connect_start = function () { }; if (self.online_target) { - self._server_subscribe(); // Automatically subscribe. - self._set_state('online'); + + // Note, we could get disconnected before tis go through. + self._server_subscribe(); // Automatically subscribe. } else { self._connect_stop(); @@ -372,7 +384,7 @@ Remote.prototype._connect_message = function (ws, json, flags) { this.ledger_closed = message.ledger_closed; this.ledger_current_index = message.ledger_closed_index + 1; - this.emit('ledger_closed', this.ledger_closed, this.ledger_closed_index); + this.emit('ledger_closed', message.ledger_closed, message.ledger_closed_index); break; default: @@ -409,13 +421,20 @@ Remote.prototype._connect_message = function (ws, json, flags) { // Send a request. // <-> request: what to send, consumed. Remote.prototype.request = function (request) { - this.ws.response[request.message.id = this.id] = request; - - this.id += 1; // Advance id. - - if (this.trace) console.log("remote: request: %s", JSON.stringify(request.message)); - - this.ws.send(JSON.stringify(request.message)); + if (this.ws) { + // Only bother if we are still connected. + + this.ws.response[request.message.id = this.id] = request; + + this.id += 1; // Advance id. + + if (this.trace) console.log("remote: request: %s", JSON.stringify(request.message)); + + this.ws.send(JSON.stringify(request.message)); + } + else { + if (this.trace) console.log("remote: request: DROPPING: %s", JSON.stringify(request.message)); + } }; Remote.prototype.request_ledger_closed = function () { @@ -480,7 +499,7 @@ Remote.prototype.request_ledger_entry = function (type) { // This type not cached. } - this.request_default(remote); + this.request_default(); } } }); @@ -668,6 +687,8 @@ Remote.prototype.transaction = function () { // - The client should only trust this when talking to a trusted server. // 'final' : Final status of transaction. // - Only expect a final from honest clients after a tesSUCCESS or ter*. +// 'lost' : Gave up looking for on ledger_closed. +// 'pending' : Transaction was not found on ledger_closed. // 'state' : Follow the state of a transaction. // 'clientSubmitted' - Sent to remote // |- 'remoteError' - Remote rejected transaction. @@ -802,8 +823,8 @@ Transaction.prototype.submit = function () { } } - if (this.listeners('final').length) { - // There are listeners for 'final' arrange to emit it. + if (this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) { + // There are listeners for 'final', 'lost', or 'pending' arrange to emit them. this.submit_index = this.remote.ledger_current_index; @@ -820,11 +841,16 @@ Transaction.prototype.submit = function () { if ('remoteError' === message.error && 'transactionNotFound' === message.remote.error) { if (self.submit_index + SUBMIT_LOST < ledger_closed_index) { - self.set_state('clientLost'); // Gave up. + self.set_state('client_lost'); // Gave up. + self.emit('lost'); stop = true; } else if (self.submit_index + SUBMIT_MISSING < ledger_closed_index) { - self.set_state('clientMissing'); // We don't know what happened to transaction, still might find. + self.set_state('client_missing'); // We don't know what happened to transaction, still might find. + self.emit('pending'); + } + else { + self.emit('pending'); } } // XXX Could log other unexpectedness. diff --git a/test/remote-test.js b/test/remote-test.js index 5a99b43c27..34eceb1aa7 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -24,7 +24,7 @@ buster.testCase("Remote functions", { alpha = remote.remoteConfig(config, "alpha"); alpha - .on('connected', done) + .once('ledger_closed', done) .connect(); }); }, diff --git a/test/send-test.js b/test/send-test.js new file mode 100644 index 0000000000..145ce34105 --- /dev/null +++ b/test/send-test.js @@ -0,0 +1,99 @@ +var buster = require("buster"); + +var config = require("./config.js"); +var server = require("./server.js"); +var amount = require("../js/amount.js"); +var remote = require("../js/remote.js"); + +var Amount = amount.Amount; + +// How long to wait for server to start. +var serverDelay = 1500; + +buster.testRunner.timeout = 5000; + +buster.testCase("Sending", { + 'setUp' : + function (done) { + server.start("alpha", + function (e) { + buster.refute(e); + + alpha = remote.remoteConfig(config, "alpha"); + + alpha + .once('ledger_closed', done) + .connect(); + }); + }, + + 'tearDown' : + function (done) { + alpha + .on('disconnected', function () { + server.stop("alpha", function (e) { + buster.refute(e); + done(); + }); + }) + .connect(false); + }, + + "send to non-existant account without create." : + function (done) { + var got_proposed; + var ledgers = 20; + + alpha.transaction() + .payment('root', 'alice', Amount.from_json("10000")) + .on('success', function (r) { + // Transaction sent. + + console.log("success: %s", JSON.stringify(r)); + }) + .on('pending', function() { + // Moving ledgers along. + console.log("missing: %d", ledgers); + + ledgers -= 1; + if (ledgers) { + alpha.ledger_accept(); + } + else { + buster.assert(false, "Final never received."); + done(); + } + }) + .on('lost', function () { + // Transaction did not make it in. + console.log("lost"); + + buster.assert(true); + done(); + }) + .on('proposed', function (m) { + // Transaction got an error. + console.log("proposed: %s", JSON.stringify(m)); + + buster.assert.equals(m.result, 'terNO_DST'); + + got_proposed = true; + + alpha.ledger_accept(); // Move it along. + }) + .on('final', function (m) { + console.log("final: %s", JSON.stringify(m)); + + buster.assert(false, "Should not have got a final."); + done(); + }) + .on('error', function(m) { + console.log("error: %s", m); + + buster.assert(false); + }) + .submit(); + }, +}); + +// vim:sw=2:sts=2:ts=8 From 4083ca2a8a1e10c352b5f4d2711611f00e76114d Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 23:27:19 -0700 Subject: [PATCH 12/17] JS: Add support for config accounts to UInt160.parseJson(). --- js/amount.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/js/amount.js b/js/amount.js index 652e8438ff..099913479b 100644 --- a/js/amount.js +++ b/js/amount.js @@ -14,7 +14,7 @@ var UInt160 = function () { }; UInt160.from_json = function (j) { - return (new UInt160()).parse_json(j); + return (new UInt160()).parse_json(j in accounts ? accounts[j].account : j); }; UInt160.prototype.clone = function() { @@ -140,6 +140,12 @@ Currency.prototype.to_human = function() { return this.value ? this.value : "XNS"; }; +var accounts = {}; + +var setAccounts = function (accounts_new) { + accounts = accounts_new; +}; + var Amount = function () { // Json format: // integer : XNS @@ -394,9 +400,10 @@ Amount.prototype.parse_json = function(j) { return this; }; -exports.Amount = Amount; -exports.Currency = Currency; -exports.UInt160 = UInt160; +exports.setAccounts = setAccounts; +exports.Amount = Amount; +exports.Currency = Currency; +exports.UInt160 = UInt160; exports.consts = { 'address_xns' : "rrrrrrrrrrrrrrrrrrrrrhoLvTp", From 660c7e3b2b4b4efcf652823af00bca6e07b1bfb9 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Wed, 17 Oct 2012 23:27:57 -0700 Subject: [PATCH 13/17] JS: Fix ripple_line_set. --- js/remote.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/js/remote.js b/js/remote.js index ed89987bd7..41c0899317 100644 --- a/js/remote.js +++ b/js/remote.js @@ -686,19 +686,19 @@ Remote.prototype.transaction = function () { // - malformed error: local server thought it was malformed. // - The client should only trust this when talking to a trusted server. // 'final' : Final status of transaction. -// - Only expect a final from honest clients after a tesSUCCESS or ter*. +// - Only expect a final from dishonest servers after a tesSUCCESS or ter*. // 'lost' : Gave up looking for on ledger_closed. // 'pending' : Transaction was not found on ledger_closed. // 'state' : Follow the state of a transaction. -// 'clientSubmitted' - Sent to remote -// |- 'remoteError' - Remote rejected transaction. -// \- 'clientProposed' - Remote provisionally accepted transaction. -// |- 'clientMissing' - Transaction has not appeared in ledger as expected. -// | |- 'clientLost' - No longer monitoring missing transaction. +// 'client_submitted' - Sent to remote +// |- 'remoteError' - Remote rejected transaction. +// \- 'client_proposed' - Remote provisionally accepted transaction. +// |- 'client_missing' - Transaction has not appeared in ledger as expected. +// | |\- 'client_lost' - No longer monitoring missing transaction. // |/ -// |- 'tesSUCCESS' - Transaction in ledger as expected. -// |- 'ter...' - Transaction failed. -// |- 'tep...' - Transaction partially succeeded. +// |- 'tesSUCCESS' - Transaction in ledger as expected. +// |- 'ter...' - Transaction failed. +// \- 'tep...' - Transaction partially succeeded. // // Notes: // - All transactions including locally errors and malformed errors may be @@ -734,7 +734,7 @@ var Transaction = function (remote) { if (message.engine_result) { self.hash = message.transaction.hash; - self.set_state('clientProposed'); + self.set_state('client_proposed'); self.emit('proposed', { 'result' : message.engine_result, @@ -866,7 +866,7 @@ Transaction.prototype.submit = function () { this.remote.on('ledger_closed', on_ledger_closed); } - this.set_state('clientSubmitted'); + this.set_state('client_submitted'); this.remote.submit(this); @@ -973,7 +973,7 @@ Transaction.prototype.payment = function (src, dst, deliver_amount) { return this; } -Remote.prototype.ripple_line_set = function (src, limit, quaility_in, quality_out) { +Transaction.prototype.ripple_line_set = function (src, limit, quality_in, quality_out) { this.secret = this.account_secret(src); this.transaction.TransactionType = 'CreditSet'; this.transaction.Account = this.account_default(src); @@ -982,11 +982,11 @@ Remote.prototype.ripple_line_set = function (src, limit, quaility_in, quality_ou if (undefined !== limit) this.transaction.LimitAmount = limit.to_json(); - if (quaility_in) - this.transaction.QualityIn = quaility_in; + if (quality_in) + this.transaction.QualityIn = quality_in; - if (quaility_out) - this.transaction.QualityOut = quaility_out; + if (quality_out) + this.transaction.QualityOut = quality_out; // XXX Throw an error if nothing is set. From e534f0ba783d90726a690ea7ce79104dbded4178 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Wed, 17 Oct 2012 23:39:14 -0700 Subject: [PATCH 14/17] Allow log levels to be tweaked at run time. --- src/Log.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++++--- src/Log.h | 17 ++++++------ src/RPCServer.cpp | 50 +++++++++++++++++++++++++++++++--- src/RPCServer.h | 1 + 4 files changed, 123 insertions(+), 14 deletions(-) diff --git a/src/Log.cpp b/src/Log.cpp index 74904c6a3d..c2314dd778 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -4,6 +4,7 @@ #include #include +#include boost::recursive_mutex Log::sLock; @@ -15,6 +16,28 @@ uint32 Log::logRotateCounter = 0; LogPartition* LogPartition::headLog = NULL; +LogPartition::LogPartition(const char *name) : mNextLog(headLog), mMinSeverity(lsWARNING) +{ + const char *ptr = strrchr(name, '/'); + mName = (ptr == NULL) ? name : (ptr + 1); + + size_t p = mName.find(".cpp"); + if (p != std::string::npos) + mName.erase(mName.begin() + p, mName.end()); + + headLog = this; +} + +std::vector< std::pair > LogPartition::getSeverities() +{ + std::vector< std::pair > sevs; + + for (LogPartition *l = headLog; l != NULL; l = l->mNextLog) + sevs.push_back(std::make_pair(l->mName, Log::severityToString(l->mMinSeverity))); + + return sevs; +} + Log::~Log() { std::string logMsg = boost::posix_time::to_simple_string(boost::posix_time::second_clock::universal_time()); @@ -26,6 +49,7 @@ Log::~Log() case lsWARNING: logMsg += " WARN "; break; case lsERROR: logMsg += " EROR "; break; case lsFATAL: logMsg += " FATL "; break; + case lsINVALID: assert(false); return; } logMsg += oss.str(); boost::recursive_mutex::scoped_lock sl(sLock); @@ -84,6 +108,44 @@ void Log::setMinSeverity(LogSeverity s) LogPartition::setSeverity(s); } +LogSeverity Log::getMinSeverity() +{ + boost::recursive_mutex::scoped_lock sl(sLock); + return sMinSeverity; +} + +std::string Log::severityToString(LogSeverity s) +{ + switch (s) + { + case lsTRACE: return "Trace"; + case lsDEBUG: return "Debug"; + case lsINFO: return "Info"; + case lsWARNING: return "Warning"; + case lsERROR: return "Error"; + case lsFATAL: return "Fatal"; + default: assert(false); return "Unknown"; + } + +} + +LogSeverity Log::stringToSeverity(const std::string& s) +{ + if (boost::iequals(s, "trace")) + return lsTRACE; + if (boost::iequals(s, "debug")) + return lsDEBUG; + if (boost::iequals(s, "info") || boost::iequals(s, "information")) + return lsINFO; + if (boost::iequals(s, "warn") || boost::iequals(s, "warning") || boost::iequals(s, "warnings")) + return lsWARNING; + if (boost::iequals(s, "error") || boost::iequals(s, "errors")) + return lsERROR; + if (boost::iequals(s, "fatal") || boost::iequals(s, "fatals")) + return lsFATAL; + return lsINVALID; +} + void Log::setLogFile(boost::filesystem::path path) { std::ofstream* newStream = new std::ofstream(path.c_str(), std::fstream::app); @@ -103,14 +165,15 @@ void Log::setLogFile(boost::filesystem::path path) pathToLog = new boost::filesystem::path(path); } -void LogPartition::setSeverity(const char *partition, LogSeverity severity) +bool LogPartition::setSeverity(const std::string& partition, LogSeverity severity) { for (LogPartition *p = headLog; p != NULL; p = p->mNextLog) - if (p->mName == partition) + if (boost::iequals(p->mName, partition)) { p->mMinSeverity = severity; - return; + return true; } + return false; } void LogPartition::setSeverity(LogSeverity severity) diff --git a/src/Log.h b/src/Log.h index 10c18fd0eb..a0521f9097 100644 --- a/src/Log.h +++ b/src/Log.h @@ -28,6 +28,7 @@ enum LogSeverity { + lsINVALID = -1, // used to indicate an invalid severity lsTRACE = 0, // Very low-level progress information, details inside an operation lsDEBUG = 1, // Function-level progress information, operations lsINFO = 2, // Server-level progress information, major operations @@ -46,20 +47,16 @@ protected: std::string mName; public: - LogPartition(const char *name) : mNextLog(headLog), mMinSeverity(lsWARNING) - { - const char *ptr = strrchr(name, '/'); - mName = (ptr == NULL) ? name : ptr; - headLog = this; - } + LogPartition(const char *name); - bool doLog(enum LogSeverity s) + bool doLog(LogSeverity s) { return s >= mMinSeverity; } - static void setSeverity(const char *partition, LogSeverity severity); + static bool setSeverity(const std::string& partition, LogSeverity severity); static void setSeverity(LogSeverity severity); + static std::vector< std::pair > getSeverities(); }; class Log @@ -95,6 +92,10 @@ public: return oss; } + static std::string severityToString(LogSeverity); + static LogSeverity stringToSeverity(const std::string&); + + static LogSeverity getMinSeverity(); static void setMinSeverity(LogSeverity); static void setLogFile(boost::filesystem::path); static std::string rotateLog(void); diff --git a/src/RPCServer.cpp b/src/RPCServer.cpp index 07d8376cb8..7e5c587837 100644 --- a/src/RPCServer.cpp +++ b/src/RPCServer.cpp @@ -1875,8 +1875,8 @@ Json::Value RPCServer::doSend(const Json::Value& params) if (asDst) { // Destination exists, ordinary send. - STPathSet spsPaths; - uint160 srcCurrencyID; + STPathSet spsPaths; + uint160 srcCurrencyID; if (!saSrcAmountMax.isNative() || !saDstAmount.isNative()) { @@ -2610,6 +2610,49 @@ Json::Value RPCServer::doLogin(const Json::Value& params) } } +Json::Value RPCServer::doLogSeverity(const Json::Value& params) +{ + if (params.size() == 0) + { // get log severities + Json::Value ret = Json::objectValue; + + ret["base"] = Log::severityToString(Log::getMinSeverity()); + + std::vector< std::pair > logTable = LogPartition::getSeverities(); + for (std::vector< std::pair >::iterator it = logTable.begin(); + it != logTable.end(); ++it) + ret[it->first] = it->second; + return ret; + } + + if (params.size() == 1) + { // set base log severity + LogSeverity sv = Log::stringToSeverity(params[0u].asString()); + if (sv == lsINVALID) + { + Log(lsWARNING) << "Unable to parse severity: " << params[0u].asString(); + return RPCError(rpcINVALID_PARAMS); + } + Log::setMinSeverity(sv); + return RPCError(rpcSUCCESS); + } + + if (params.size() == 2) + { // set partition severity + LogSeverity sv = Log::stringToSeverity(params[1u].asString()); + if (sv == lsINVALID) + return RPCError(rpcINVALID_PARAMS); + if (params[2u].asString() == "base") + Log::setMinSeverity(sv); + else if (!LogPartition::setSeverity(params[0u].asString(), sv)) + return RPCError(rpcINVALID_PARAMS); + return RPCError(rpcSUCCESS); + } + + assert(false); + return RPCError(rpcINVALID_PARAMS); +} + Json::Value RPCServer::doLogRotate(const Json::Value& params) { return Log::rotateLog(); @@ -2641,7 +2684,8 @@ Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params { "data_fetch", &RPCServer::doDataFetch, 1, 1, true }, { "data_store", &RPCServer::doDataStore, 2, 2, true }, { "ledger", &RPCServer::doLedger, 0, 2, false, optNetwork }, - { "logrotate", &RPCServer::doLogRotate, 0, 0, true }, + { "logrotate", &RPCServer::doLogRotate, 0, 0, true }, + { "logseverity", &RPCServer::doLogSeverity, 0, 2, true }, { "nickname_info", &RPCServer::doNicknameInfo, 1, 1, false, optCurrent }, { "nickname_set", &RPCServer::doNicknameSet, 2, 3, false, optCurrent }, { "offer_create", &RPCServer::doOfferCreate, 9, 10, false, optCurrent }, diff --git a/src/RPCServer.h b/src/RPCServer.h index ce859c8431..8997707d1b 100644 --- a/src/RPCServer.h +++ b/src/RPCServer.h @@ -160,6 +160,7 @@ private: Json::Value doServerInfo(const Json::Value& params); Json::Value doSessionClose(const Json::Value& params); Json::Value doSessionOpen(const Json::Value& params); + Json::Value doLogSeverity(const Json::Value& params); Json::Value doStop(const Json::Value& params); Json::Value doTransitSet(const Json::Value& params); Json::Value doTx(const Json::Value& params); From 04f369886a2e72b729b89976f1fa6f21aa141b8f Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 18 Oct 2012 00:12:48 -0700 Subject: [PATCH 15/17] JS: Have remote look in the current ledger to get Sequence for new accounts. --- js/remote.js | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/js/remote.js b/js/remote.js index 41c0899317..9f624f135d 100644 --- a/js/remote.js +++ b/js/remote.js @@ -539,19 +539,26 @@ Remote.prototype.submit = function (transaction) { } if (!transaction.transaction.Sequence) { - var cache_request = this.account_cache(transaction.transaction.Account); - - cache_request.on('success_account_cache', function () { + // Look in the last closed ledger. + this.account_cache(transaction.transaction.Account, false) + .on('success_account_cache', function () { // Try again. self.submit(transaction); - }); - - cache_request.on('error', function (message) { - // Forward errors. - transaction.emit('error', message); - }); - - cache_request.request(); + }) + .on('error', function (message) { + // Look in the current ledger. + self.account_cache(transaction.transaction.Account, true) + .on('success_account_cache', function () { + // Try again. + self.submit(transaction); + }) + .on('error', function (message) { + // Forward errors. + transaction.emit('error', message); + }) + .request(); + }) + .request(); } else { var submit_request = new Request(this, 'submit'); @@ -639,16 +646,13 @@ Remote.prototype.account_seq = function (account, advance) { } // Return a request to refresh accounts[account].seq. -Remote.prototype.account_cache = function (account) { +Remote.prototype.account_cache = function (account, current) { var self = this; - var request = this.request_ledger_entry('account_root') + var request = this.request_ledger_entry('account_root'); - // Only care about a closed ledger. - // YYY Might be more advanced and work with a changing current ledger. - request.ledger(this.ledger_closed); // XXX Requires active server_subscribe - request.account_root(account); - - request.on('success', function (message) { + request + .account_root(account) + .on('success', function (message) { var seq = message.node.Sequence; if (!self.accounts[account]) @@ -660,7 +664,15 @@ Remote.prototype.account_cache = function (account) { request.emit('success_account_cache'); }); - return request; + if (current) + { + request.ledger_index(this.ledger_current_index); + } + else { + request.ledger(this.ledger_closed); + } + + return request; }; // Mark an account's root node as dirty. From ceb2aac9378fc1bc1069ee6046e063761fc88e98 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 18 Oct 2012 00:35:50 -0700 Subject: [PATCH 16/17] Clean up. --- js/remote.js | 43 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/js/remote.js b/js/remote.js index 9f624f135d..083eb01f2a 100644 --- a/js/remote.js +++ b/js/remote.js @@ -540,15 +540,17 @@ Remote.prototype.submit = function (transaction) { if (!transaction.transaction.Sequence) { // Look in the last closed ledger. - this.account_cache(transaction.transaction.Account, false) - .on('success_account_cache', function () { + this.account_seq_cache(transaction.transaction.Account, false) + .on('success_account_seq_cache', function () { // Try again. self.submit(transaction); }) .on('error', function (message) { + // XXX Maybe be smarter about this. Don't want to trust an untrusted server for this seq number. + // Look in the current ledger. - self.account_cache(transaction.transaction.Account, true) - .on('success_account_cache', function () { + self.account_seq_cache(transaction.transaction.Account, 'CURRENT') + .on('success_account_seq_cache', function () { // Try again. self.submit(transaction); }) @@ -570,10 +572,6 @@ Remote.prototype.submit = function (transaction) { submit_request.on('success', function (message) { transaction.emit('success', message); }); submit_request.on('error', function (message) { transaction.emit('error', message); }); - // XXX If transaction has a 'final' event listeners, register transaction to listen to final results. - // XXX Final messages only happen if a transaction makes it into a ledger. - // XXX A transaction may be "lost" or even resubmitted in this case. - // XXX For when ledger closes, can look up transaction meta data. submit_request.request(); } } @@ -646,7 +644,7 @@ Remote.prototype.account_seq = function (account, advance) { } // Return a request to refresh accounts[account].seq. -Remote.prototype.account_cache = function (account, current) { +Remote.prototype.account_seq_cache = function (account, current) { var self = this; var request = this.request_ledger_entry('account_root'); @@ -661,7 +659,7 @@ Remote.prototype.account_cache = function (account, current) { self.accounts[account].seq = seq; // If the caller also waits for 'success', they might run before this. - request.emit('success_account_cache'); + request.emit('success_account_seq_cache'); }); if (current) @@ -687,7 +685,14 @@ Remote.prototype.transaction = function () { // // Transactions // -// Transaction events: +// Construction: +// remote.transaction() // Build a transaction object. +// .offer_create(...) // Set major parameters. +// .flags() // Set optional parameters. +// .on() // Register for events. +// .submit(); // Send to network. +// +// Events: // 'success' : Transaction submitted without error. // 'error' : Error submitting transaction. // 'proposed: Advisory proposed status transaction. @@ -713,8 +718,8 @@ Remote.prototype.transaction = function () { // \- 'tep...' - Transaction partially succeeded. // // Notes: -// - All transactions including locally errors and malformed errors may be -// forwarded. +// - All transactions including those with local and malformed errors may be +// forwarded anyway. // - A malicous server can: // - give any proposed result. // - it may declare something correct as incorrect or something correct as incorrect. @@ -934,18 +939,6 @@ Transaction.prototype.flags = function (flags) { // // Transactions // -// Construction: -// remote.transaction() // Build a transaction object. -// .offer_create(...) // Set major parameters. -// .flags() // Set optional parameters. -// .on() // Register for events. -// .submit(); // Send to network. -// -// Events: -// 'success' // Transaction was successfully submitted: hash, proposed TER -// 'error' // Error submitting transaction. -// 'closed' // Result from closed ledger: TER -// // Allow config account defaults to be used. Transaction.prototype.account_default = function (account) { From 92409dd3fcf9f4f0ef1aed58706d7b3fe753be90 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Thu, 18 Oct 2012 09:18:46 -0700 Subject: [PATCH 17/17] Add 'SHAMap::getPath' function to get a verifiable path through a SHAMap. --- src/SHAMap.cpp | 32 ++++++++++++++++++++++++++++++++ src/SHAMap.h | 3 +++ src/SHAMapNodes.cpp | 25 ++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/SHAMap.cpp b/src/SHAMap.cpp index 7bb86ac300..85bbf3a61d 100644 --- a/src/SHAMap.cpp +++ b/src/SHAMap.cpp @@ -814,6 +814,38 @@ SHAMapTreeNode::pointer SHAMap::getNode(const SHAMapNode& nodeID) return node; } +bool SHAMap::getPath(const uint256& index, std::vector< std::vector >& nodes, SHANodeFormat format) +{ + // Return the path of nodes to the specified index in the specified format + // Return value: true = node present, false = node not present + + boost::recursive_mutex::scoped_lock sl(mLock); + SHAMapTreeNode* inNode = root.get(); + + while (!inNode->isLeaf()) + { + Serializer s; + inNode->addRaw(s, format); + nodes.push_back(s.peekData()); + + int branch = inNode->selectBranch(index); + if (inNode->isEmptyBranch(branch)) // paths leads to empty branch + return false; + inNode = getNodePointer(inNode->getChildNodeID(branch), inNode->getChildHash(branch)); + if (!inNode) + throw SHAMapMissingNode(mType, inNode->getChildNodeID(branch), inNode->getChildHash(branch), index); + } + + if (inNode->getTag() != index) // path leads to different leaf + return false; + + // path lead to the requested leaf + Serializer s; + inNode->addRaw(s, format); + nodes.push_back(s.peekData()); + return true; +} + void SHAMap::dump(bool hash) { #if 0 diff --git a/src/SHAMap.h b/src/SHAMap.h index 2518669b27..7426ab780b 100644 --- a/src/SHAMap.h +++ b/src/SHAMap.h @@ -125,6 +125,7 @@ enum SHANodeFormat { snfPREFIX = 1, // Form that hashes to its official hash snfWIRE = 2, // Compressed form used on the wire + snfHASH = 3, // just the hash }; enum SHAMapType @@ -404,6 +405,8 @@ public: void walkMap(std::vector& missingNodes, int maxMissing); + bool getPath(const uint256& index, std::vector< std::vector >& nodes, SHANodeFormat format); + bool deepCompare(SHAMap& other); virtual void dump(bool withHashes = false); }; diff --git a/src/SHAMapNodes.cpp b/src/SHAMapNodes.cpp index b14516813e..b301e9db82 100644 --- a/src/SHAMapNodes.cpp +++ b/src/SHAMapNodes.cpp @@ -259,7 +259,7 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector(mHashes), sizeof(mHashes)); +#ifdef DEBUG + Serializer s; + s.add32(sHP_InnerNode); + for(int i = 0; i < 16; ++i) + s.add256(mHashes[i]); + assert(nh == s.getSHA512Half()); +#endif + } } else if (mType == tnTRANSACTION_NM) { @@ -366,11 +381,15 @@ bool SHAMapTreeNode::updateHash() void SHAMapTreeNode::addRaw(Serializer& s, SHANodeFormat format) { - assert((format == snfPREFIX) || (format == snfWIRE)); + assert((format == snfPREFIX) || (format == snfWIRE) || (format == snfHASH)); if (mType == tnERROR) throw std::runtime_error("invalid I node type"); - if (mType == tnINNER) + if (format == snfHASH) + { + s.add256(getNodeHash()); + } + else if (mType == tnINNER) { assert(!isEmpty()); if (format == snfPREFIX)