Merge branch 'master' of github.com:jedmccaleb/NewCoin

This commit is contained in:
jed
2012-10-18 10:06:12 -07:00
26 changed files with 787 additions and 352 deletions

View File

@@ -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",

View File

@@ -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.

View File

@@ -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 ? "^" : ".");

View File

@@ -92,13 +92,15 @@ Ledger::Ledger(bool /* dummy */, Ledger& prevLedger) :
Ledger::Ledger(const std::vector<unsigned char>& 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);

View File

@@ -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; }

View File

@@ -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();
}

View File

@@ -4,6 +4,7 @@
#include <fstream>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/algorithm/string.hpp>
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<std::string, std::string> > LogPartition::getSeverities()
{
std::vector< std::pair<std::string, std::string> > 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)

View File

@@ -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<std::string, std::string> > 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);

View File

@@ -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<boost::interprocess::interprocess_upgradable_mutex> sl(mMonitorLock);

View File

@@ -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<std::string, std::string> > logTable = LogPartition::getSeverities();
for (std::vector< std::pair<std::string, std::string> >::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 },

View File

@@ -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);

View File

@@ -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<unsigned char> >& 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

View File

@@ -98,8 +98,7 @@ public:
std::vector<unsigned char> getData() const { return mData.getData(); }
const std::vector<unsigned char>& peekData() const { return mData.peekData(); }
Serializer& peekSerializer() { return mData; }
void addRaw(Serializer &s) { s.addRaw(mData); }
void addRaw(std::vector<unsigned char>& s) { s.insert(s.end(), mData.begin(), mData.end()); }
void addRaw(std::vector<unsigned char>& s) const { s.insert(s.end(), mData.begin(), mData.end()); }
void updateData(const std::vector<unsigned char>& 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<SHAMapMissingNode>& missingNodes, int maxMissing);
bool getPath(const uint256& index, std::vector< std::vector<unsigned char> >& nodes, SHANodeFormat format);
bool deepCompare(SHAMap& other);
virtual void dump(bool withHashes = false);
};

View File

@@ -259,7 +259,7 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector<unsigned
}
}
if (format == snfPREFIX)
else if (format == snfPREFIX)
{
if (rawNode.size() < 4)
{
@@ -316,6 +316,12 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector<unsigned
}
}
else
{
assert(false);
throw std::runtime_error("Unknown format");
}
updateHash();
}
@@ -333,7 +339,16 @@ bool SHAMapTreeNode::updateHash()
break;
}
if(!empty)
{
nh = Serializer::getPrefixHash(sHP_InnerNode, reinterpret_cast<unsigned char *>(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);
}

View File

@@ -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); }
};

View File

@@ -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<Serializer&>(s)); // we know 's' isn't going away
set(sit);
uint16 type = getFieldU16(sfLedgerEntryType);

View File

@@ -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; }

View File

@@ -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<SerializedTransaction>(ttKind);

View File

@@ -121,3 +121,4 @@ std::string transToken(TER terCode);
std::string transHuman(TER terCode);
#endif
// vim:ts=4

View File

@@ -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<unsigned char>&);
void init(const uint256& transactionID, uint32 ledger);

View File

@@ -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();
}
}
}

View File

@@ -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"
}
};

View File

@@ -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

99
test/send-test.js Normal file
View File

@@ -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

View File

@@ -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;

View File

@@ -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();
},
});