diff --git a/js/amount.js b/js/amount.js index 51e18a5630..099913479b 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'); @@ -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", diff --git a/js/remote.js b/js/remote.js index bed251dd7c..083eb01f2a 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); @@ -94,6 +104,19 @@ 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': A good indicate of ready to serve. +// 'subscribed' : This indicates stand-alone is available. +// + // --> trusted: truthy, if remote is trusted var Remote = function (trusted, websocket_ip, websocket_port, config, trace) { this.trusted = trusted; @@ -105,6 +128,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,198 +187,254 @@ 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) { + self._set_state('online'); + + // Note, we could get disconnected before tis go through. + self._server_subscribe(); // Automatically subscribe. + } + else { + 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(ws, 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; +// 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; - 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 = 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', message.ledger_closed, message.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. // <-> 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 () { @@ -415,7 +499,7 @@ Remote.prototype.request_ledger_entry = function (type) { // This type not cached. } - this.request_default(remote); + this.request_default(); } } }); @@ -455,19 +539,28 @@ 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_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. - cache_request.on('error', function (message) { - // Forward errors. - transaction.emit('error', message); - }); - - cache_request.request(); + // Look in the current ledger. + self.account_seq_cache(transaction.transaction.Account, 'CURRENT') + .on('success_account_seq_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'); @@ -479,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(); } } @@ -495,20 +584,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(); @@ -552,16 +644,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_seq_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]) @@ -570,10 +659,18 @@ Remote.prototype.account_cache = function (account) { 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'); }); - 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. @@ -588,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. @@ -599,21 +703,23 @@ 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 -// 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. @@ -645,7 +751,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, @@ -734,8 +840,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; @@ -745,22 +851,23 @@ 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) { 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. @@ -776,7 +883,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); @@ -832,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) { @@ -883,7 +978,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); @@ -892,11 +987,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. 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 ? "^" : "."); 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/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/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/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/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); diff --git a/src/SHAMap.cpp b/src/SHAMap.cpp index b556a18021..85bbf3a61d 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) @@ -817,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 a30c1f8808..7426ab780b 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; } @@ -126,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 @@ -405,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 2eaa0ed806..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) @@ -404,12 +423,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 +438,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 +451,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/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/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; } 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/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 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); 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(); + } } } 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/remote-test.js b/test/remote-test.js index b45f1a62ab..34eceb1aa7 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 + .once('ledger_closed', 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' : @@ -223,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 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 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; 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(); }, });