Update everything

This commit is contained in:
wltsmrz
2013-08-14 17:20:51 -07:00
parent 0f5fdbc772
commit b3822cc4c5
7 changed files with 507 additions and 388 deletions

View File

@@ -110,7 +110,7 @@ Account.prototype.is_valid = function () {
return this._account.is_valid();
};
Account.prototype.get_account_info = function(callback) {
Account.prototype.get_info = function(callback) {
var callback = typeof callback === 'function' ? callback : function(){};
this._remote.request_account_info(this._account_id, callback);
};
@@ -127,7 +127,7 @@ Account.prototype.entry = function (callback) {
var self = this;
var callback = typeof callback === 'function' ? callback : function(){};
this.get_account_info(function account_info(err, info) {
this.get_info(function account_info(err, info) {
if (err) {
callback(err);
} else {
@@ -143,7 +143,7 @@ Account.prototype.entry = function (callback) {
Account.prototype.get_next_sequence = function(callback) {
var callback = typeof callback === 'function' ? callback : function(){};
this.get_account_info(function account_info(err, info) {
this.get_info(function account_info(err, info) {
if (err) {
callback(err);
} else {

View File

@@ -28,6 +28,7 @@ var Account = require('./account').Account;
var Meta = require('./meta').Meta;
var OrderBook = require('./orderbook').OrderBook;
var PathFind = require('./pathfind').PathFind;
var RippleError = require('./rippleerror').RippleError;
var utils = require('./utils');
var config = require('./config');
@@ -253,12 +254,11 @@ Remote.create_remote = function(options, callback) {
Remote.prototype.add_server = function (opts) {
var self = this;
var url = ((opts.secure || opts.websocket_ssl) ? 'wss://' : 'ws://') +
(opts.host || opts.websocket_ip) + ':' +
(opts.port || opts.websocket_port)
;
var server = new Server(this, { url: url });
var server = new Server(this, {
host : opts.host || opts.websocket_ip,
port : opts.port || opts.websocket_port,
secure : opts.secure || opts.websocket_ssl
});
function server_message(data) {
self._handle_message(data, server);
@@ -338,25 +338,23 @@ Remote.prototype._trace = function() {
* Connect to the Ripple network.
*/
Remote.prototype.connect = function (online) {
// Downwards compatibility
if (!this._servers.length) {
throw new Error('No servers available.');
}
switch (typeof online) {
case 'undefined':
break;
case 'function':
this.once('connect', online);
break;
default:
// Downwards compatibility
if (!Boolean(online)) {
return this.disconnect();
}
break;
}
if (!this._servers.length) {
throw new Error('No servers available.');
} else {
var self = this;
;(function nextServer(i) {
@@ -370,7 +368,6 @@ Remote.prototype.connect = function (online) {
}, self._connection_offset);
}
})(0);
}
return this;
};
@@ -379,6 +376,10 @@ Remote.prototype.connect = function (online) {
* Disconnect from the Ripple network.
*/
Remote.prototype.disconnect = function (online) {
if (!this._servers.length) {
throw new Error('No servers available, not disconnecting');
}
this._servers.forEach(function(server) {
server.disconnect();
});
@@ -398,10 +399,7 @@ Remote.prototype._handle_message = function (message, server) {
if (unexpected) {
// Unexpected response from remote.
this.emit('error', {
error: 'remoteUnexpected',
error_message: 'Unexpected response from remote.'
});
this.emit('error', new RippleError('remoteUnexpected', 'Unexpected response from remote'));
return;
}
@@ -544,8 +542,10 @@ Remote.prototype.request = function (request) {
request.emit('error', new Error('No servers available'));
} else if (!this._connected) {
this.once('connect', this.request.bind(this, request));
} else if (request.server === null) {
this.emit('error', new Error('Server does not exist'));
} else {
var server = this._get_server();
var server = request.server || this._get_server();
if (server) {
server.request(request);
} else {
@@ -739,9 +739,9 @@ Remote.prototype.request_transaction_entry = function (tx_hash, ledger_hash, cal
case 'string':
request.ledger_hash(ledger_hash);
break;
case 'function':
default:
request.ledger_index('validated');
callback = ledger_hash;
break;
}
request.callback(callback);
@@ -752,20 +752,16 @@ Remote.prototype.request_transaction_entry = function (tx_hash, ledger_hash, cal
// DEPRECATED: use request_transaction_entry
Remote.prototype.request_tx = function (hash, callback) {
var request = new Request(this, 'tx');
request.message.transaction = hash;
request.callback(callback);
return request;
};
Remote.prototype.request_account_info = function (accountID, callback) {
var request = new Request(this, 'account_info');
request.message.ident = UInt160.json_rewrite(accountID); // DEPRECATED
request.message.account = UInt160.json_rewrite(accountID);
request.callback(callback);
return request;
};
@@ -875,32 +871,23 @@ Remote.prototype.request_book_offers = function (gets, pays, taker, callback) {
Remote.prototype.request_wallet_accounts = function (seed, callback) {
utils.assert(this.trusted); // Don't send secrets.
var request = new Request(this, 'wallet_accounts');
request.message.seed = seed;
return request.callback(callback);
};
Remote.prototype.request_sign = function (secret, tx_json, callback) {
utils.assert(this.trusted); // Don't send secrets.
var request = new Request(this, 'sign');
request.message.secret = secret;
request.message.tx_json = tx_json;
request.callback(callback);
return request;
};
// Submit a transaction.
Remote.prototype.request_submit = function (callback) {
var request = new Request(this, 'submit');
request.callback(callback);
return request;
return new Request(this, 'submit').callback(callback);
};
//
@@ -926,7 +913,7 @@ Remote.prototype._server_prepare_subscribe = function (callback) {
var request = this.request_subscribe(feeds);
request.on('success', function (message) {
request.once('success', function (message) {
self._stand_alone = !!message.stand_alone;
self._testnet = !!message.testnet;
@@ -949,7 +936,7 @@ Remote.prototype._server_prepare_subscribe = function (callback) {
// XXX When we have multiple server support, most of this should be tracked
// by the Server objects and then aggregated/interpreted by Remote.
self._load_base = message.load_base || 256;
self._load_factor = message.load_factor || 1.0;
self._load_factor = message.load_factor || 256;
self._fee_ref = message.fee_ref;
self._fee_base = message.fee_base;
self._reserve_base = message.reserve_base;
@@ -979,52 +966,42 @@ Remote.prototype.ledger_accept = function (callback) {
} else {
this.emit('error', { error : 'notStandAlone' });
}
return this;
};
// Return a request to refresh the account balance.
Remote.prototype.request_account_balance = function (account, current, callback) {
var request = this.request_ledger_entry('account_root');
request.account_root(account);
request.ledger_choose(current);
request.once('success', function (message) {
request.emit('account_balance', Amount.from_json(message.node.Balance));
});
request.callback(callback, 'account_balance');
return request;
};
// Return a request to return the account flags.
Remote.prototype.request_account_flags = function (account, current, callback) {
var request = this.request_ledger_entry('account_root');
request.account_root(account);
request.ledger_choose(current);
request.on('success', function (message) {
request.emit('account_flags', message.node.Flags);
});
request.callback(callback, 'account_flags');
return request;
};
// Return a request to emit the owner count.
Remote.prototype.request_owner_count = function (account, current, callback) {
var request = this.request_ledger_entry('account_root');
request.account_root(account);
request.ledger_choose(current);
request.on('success', function (message) {
request.emit('owner_count', message.node.OwnerCount);
});
request.callback(callback, 'owner_count');
return request;
};
@@ -1082,7 +1059,7 @@ Remote.prototype.account_seq = function (account, advance) {
if (account_info && account_info.seq) {
seq = account_info.seq;
account_info.seq += { ADVANCE: 1, REWIND: -1 }[advance] || 0;
account_info.seq += { ADVANCE: 1, REWIND: -1 }[advance.toUpperCase()] || 0;
}
return seq;
@@ -1103,7 +1080,7 @@ Remote.prototype.account_seq_cache = function (account, current, callback) {
var self = this;
if (!this.accounts.hasOwnProperty(account)) {
self.accounts[account] = { };
this.accounts[account] = { };
}
var account_info = this.accounts[account];
@@ -1198,6 +1175,20 @@ Remote.prototype.request_ripple_balance = function (account, issuer, currency, c
return request;
};
function prepare_currencies(ci) {
var ci_new = { };
if (ci.hasOwnProperty('issuer')) {
ci_new.issuer = UInt160.json_rewrite(ci.issuer);
}
if (ci.hasOwnProperty('currency')) {
ci_new.currency = Currency.json_rewrite(ci.currency);
}
return ci_new;
}
Remote.prototype.request_ripple_path_find = function (src_account, dst_account, dst_amount, src_currencies, callback) {
var request = new Request(this, 'ripple_path_find');
@@ -1206,19 +1197,7 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account,
request.message.destination_amount = Amount.json_rewrite(dst_amount);
if (src_currencies) {
request.message.source_currencies = src_currencies.map(function (ci) {
var ci_new = { };
if (ci.hasOwnProperty('issuer')) {
ci_new.issuer = UInt160.json_rewrite(ci.issuer);
}
if (ci.hasOwnProperty('currency')) {
ci_new.currency = Currency.json_rewrite(ci.currency);
}
return ci_new;
});
request.message.source_currencies = src_currencies.map(prepare_currencies);
}
request.callback(callback);
@@ -1227,7 +1206,6 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account,
};
Remote.prototype.request_path_find_create = function (src_account, dst_account, dst_amount, src_currencies, callback) {
var self = this;
var request = new Request(this, 'path_find');
request.message.subcommand = 'create';
@@ -1236,19 +1214,7 @@ Remote.prototype.request_path_find_create = function (src_account, dst_account,
request.message.destination_amount = Amount.json_rewrite(dst_amount);
if (src_currencies) {
request.message.source_currencies = src_currencies.map(function (ci) {
var ci_new = {};
if (ci.hasOwnProperty('issuer')) {
ci_new.issuer = UInt160.json_rewrite(ci.issuer);
}
if (ci.hasOwnProperty('currency')) {
ci_new.currency = Currency.json_rewrite(ci.currency);
}
return ci_new;
});
request.message.source_currencies = src_currencies.map(prepare_currencies);
}
request.callback(callback);
@@ -1263,9 +1229,7 @@ Remote.prototype.request_path_find_close = function () {
};
Remote.prototype.request_unl_list = function (callback) {
var request = new Request(this, 'unl_list');
request.callback(callback);
return request;
return new Request(this, 'unl_list').callback(callback);
};
Remote.prototype.request_unl_add = function (addr, comment, callback) {
@@ -1291,9 +1255,7 @@ Remote.prototype.request_unl_delete = function (node, callback) {
};
Remote.prototype.request_peers = function (callback) {
var request = new Request(this, 'peers');
request.callback(callback);
return request;
return new Request(this, 'peers').callback(callback);
};
Remote.prototype.request_connect = function (ip, port, callback) {
@@ -1372,6 +1334,36 @@ Remote.prototype.reserve = function (owner_count) {
return reserve_base.add(reserve_inc.product_human(owner_count));
};
Remote.prototype.ping = function(host, callback) {
var request = new Request(this, 'ping');
switch (typeof host) {
case 'function':
callback = host;
break;
case 'string':
var server = null;
for (var i=0, s; s=this._servers[i]; i++) {
if (s._host === host) {
server = s;
break;
}
}
request.set_server(server);
break;
}
var then = Date.now();
request.once('success', function() {
request.emit('pong', Date.now() - then);
});
request.callback(callback, 'pong');
return request;
};
exports.Remote = Remote;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -6,6 +6,7 @@ var Transaction = require('./transaction').Transaction;
var Account = require('./account').Account;
var Meta = require('./meta').Meta;
var OrderBook = require('./orderbook').OrderBook;
var RippleError = require('./rippleerror').RippleError;
// Request events emitted:
// 'success' : Request successful.
@@ -15,6 +16,7 @@ var OrderBook = require('./orderbook').OrderBook;
// 'remoteDisconnected'
function Request(remote, command) {
EventEmitter.call(this);
this.remote = remote;
this.requested = false;
this.message = {
@@ -36,8 +38,19 @@ Request.prototype.request = function (remote) {
Request.prototype.callback = function(callback, successEvent, errorEvent) {
if (callback && typeof callback === 'function') {
this.once(successEvent || 'success', callback.bind(this, null));
this.once(errorEvent || 'error' , callback.bind(this));
function request_success(message) {
callback.call(this, null, message);
}
function request_error(error) {
if (!(error instanceof RippleError)) {
error = new RippleError(error);
}
callback.call(this, error);
}
this.once(successEvent || 'success', request_success);
this.once(errorEvent || 'error' , request_error);
this.request();
}
@@ -45,12 +58,16 @@ Request.prototype.callback = function(callback, successEvent, errorEvent) {
};
Request.prototype.timeout = function(duration, callback) {
if (!this.requested) {
this.once('request', this.timeout.bind(this, duration, callback));
return;
};
var self = this;
if (!this.requested) {
function requested() {
self.timeout(duration, callback);
}
this.once('request', requested);
return;
}
var emit = this.emit;
var timed_out = false;
@@ -61,19 +78,23 @@ Request.prototype.timeout = function(duration, callback) {
}, duration);
this.emit = function() {
if (timed_out) return;
else clearTimeout(timeout);
if (!timed_out) {
clearTimeout(timeout);
emit.apply(self, arguments);
}
};
return this;
};
Request.prototype.set_server = function(server) {
this.server = server;
};
Request.prototype.build_path = function (build) {
if (build) {
this.message.build_path = true;
}
return this;
};
@@ -83,16 +104,14 @@ Request.prototype.ledger_choose = function (current) {
} else {
this.message.ledger_hash = this.remote._ledger_hash;
}
return this;
};
// Set the ledger for a request.
// - ledger_entry
// - transaction_entry
Request.prototype.ledger_hash = function (h) {
this.message.ledger_hash = h;
Request.prototype.ledger_hash = function (hash) {
this.message.ledger_hash = hash;
return this;
};
@@ -100,7 +119,6 @@ Request.prototype.ledger_hash = function (h) {
// - ledger_entry
Request.prototype.ledger_index = function (ledger_index) {
this.message.ledger_index = ledger_index;
return this;
};
@@ -114,10 +132,10 @@ Request.prototype.ledger_select = function (ledger_spec) {
default:
// XXX Better test needed
if (String(ledger_spec).length > 12) {
this.message.ledger_hash = ledger_spec;
} else {
if (Number(ledger_spec)) {
this.message.ledger_index = ledger_spec;
} else {
this.message.ledger_hash = ledger_spec;
}
break;
}
@@ -127,13 +145,11 @@ Request.prototype.ledger_select = function (ledger_spec) {
Request.prototype.account_root = function (account) {
this.message.account_root = UInt160.json_rewrite(account);
return this;
};
Request.prototype.index = function (hash) {
this.message.index = hash;
return this;
};
@@ -145,52 +161,45 @@ Request.prototype.offer_id = function (account, seq) {
account: UInt160.json_rewrite(account),
seq: seq
};
return this;
};
// --> index : ledger entry index.
Request.prototype.offer_index = function (index) {
this.message.offer = index;
return this;
};
Request.prototype.secret = function (s) {
if (s) {
this.message.secret = s;
Request.prototype.secret = function (secret) {
if (secret) {
this.message.secret = secret;
}
return this;
};
Request.prototype.tx_hash = function (h) {
this.message.tx_hash = h;
Request.prototype.tx_hash = function (hash) {
this.message.tx_hash = hash;
return this;
};
Request.prototype.tx_json = function (j) {
this.message.tx_json = j;
Request.prototype.tx_json = function (json) {
this.message.tx_json = json;
return this;
};
Request.prototype.tx_blob = function (j) {
this.message.tx_blob = j;
Request.prototype.tx_blob = function (json) {
this.message.tx_blob = json;
return this;
};
Request.prototype.ripple_state = function (account, issuer, currency) {
this.message.ripple_state = {
'accounts' : [
currency : currency,
accounts : [
UInt160.json_rewrite(account),
UInt160.json_rewrite(issuer)
],
'currency' : currency
]
};
return this;
};
@@ -200,14 +209,14 @@ Request.prototype.accounts = function (accounts, realtime) {
}
// Process accounts parameters
var procAccounts = accounts.map(function(account) {
var processedAccounts = accounts.map(function(account) {
return UInt160.json_rewrite(account);
});
if (realtime) {
this.message.rt_accounts = procAccounts;
this.message.rt_accounts = processedAccounts;
} else {
this.message.accounts = procAccounts;
this.message.accounts = processedAccounts;
}
return this;
@@ -218,14 +227,16 @@ Request.prototype.rt_accounts = function (accounts) {
};
Request.prototype.books = function (books, snapshot) {
var procBooks = [];
var processedBooks = [ ];
for (var i = 0, l = books.length; i < l; i++) {
var book = books[i];
var json = { };
function processSide(side) {
if (!book[side]) throw new Error('Missing '+side);
if (!book[side]) {
throw new Error('Missing ' + side);
}
var obj = json[side] = {
currency: Currency.json_rewrite(book[side].currency)
@@ -239,13 +250,18 @@ Request.prototype.books = function (books, snapshot) {
processSide('taker_gets');
processSide('taker_pays');
if (snapshot) json.snapshot = true;
if (book.both) json.both = true;
procBooks.push(json);
if (snapshot) {
json.snapshot = true;
}
this.message.books = procBooks;
if (book.both) {
json.both = true;
}
processedBooks.push(json);
}
this.message.books = processedBooks;
return this;
};

View File

@@ -6,8 +6,8 @@ function RippleError(code, message) {
extend(this, code);
} else {
this.result = code;
this.result_message = message;
this.message = message;
this.result_message = message;
}
this.message = this.result_message || 'Error';

View File

@@ -182,9 +182,11 @@ Transaction.prototype.get_fee = function() {
Transaction.prototype.complete = function () {
var tx_json = this.tx_json;
if (typeof tx_json.Fee === 'undefined' && this.remote.local_fee) {
if (typeof tx_json.Fee === 'undefined') {
if (this.remote.local_fee || !this.remote.trusted) {
this.tx_json.Fee = this.remote.fee_tx(this.fee_units()).to_json();
}
}
if (typeof tx_json.SigningPubKey === 'undefined' && (!this.remote || this.remote.local_signing)) {
var seed = Seed.from_json(this._secret);
@@ -200,10 +202,7 @@ Transaction.prototype.serialize = function () {
};
Transaction.prototype.signing_hash = function () {
var prefix = config.testnet
? Transaction.HASH_SIGN_TESTNET
: Transaction.HASH_SIGN;
var prefix = Transaction[config.testnet ? 'HASH_SIGN_TESTNET' : 'HASH_SIGN'];
return SerializedObject.from_json(this.tx_json).signing_hash(prefix);
};
@@ -223,13 +222,6 @@ Transaction.prototype.sign = function () {
this.tx_json.TxnSignature = hex;
};
Transaction.prototype._hasTransactionListeners = function() {
return this.listeners('final').length
|| this.listeners('lost').length
|| this.listeners('pending').length
};
//
// Set options for Transactions
//
@@ -239,7 +231,6 @@ Transaction.prototype._hasTransactionListeners = function() {
// "blindly" because the sender has no idea of the actual cost except that is must be less than send max.
Transaction.prototype.build_path = function (build) {
this._build_path = build;
return this;
}
@@ -249,28 +240,27 @@ Transaction.prototype.destination_tag = function (tag) {
if (tag !== void(0)) {
this.tx_json.DestinationTag = tag;
}
return this;
}
Transaction._path_rewrite = function (path) {
var path_new = [];
var props = [
'account'
, 'issuer'
, 'currency'
]
for (var i=0, l=path.length; i<l; i++) {
var node = path[i];
var path_new = path.map(function(node) {
var node_new = { };
if ('account' in node)
node_new.account = UInt160.json_rewrite(node.account);
if ('issuer' in node)
node_new.issuer = UInt160.json_rewrite(node.issuer);
if ('currency' in node)
node_new.currency = Currency.json_rewrite(node.currency);
path_new.push(node_new);
for (var prop in node) {
if (~props.indexOf(prop)) {
node_new[prop] = UInt160.json_rewrite(node[prop]);
}
}
return node_new;
});
return path_new;
}
@@ -278,7 +268,6 @@ Transaction._path_rewrite = function (path) {
Transaction.prototype.path_add = function (path) {
this.tx_json.Paths = this.tx_json.Paths || [];
this.tx_json.Paths.push(Transaction._path_rewrite(path));
return this;
}
@@ -288,22 +277,20 @@ Transaction.prototype.paths = function (paths) {
for (var i=0, l=paths.length; i<l; i++) {
this.path_add(paths[i]);
}
return this;
}
};
// If the secret is in the config object, it does not need to be provided.
Transaction.prototype.secret = function (secret) {
this._secret = secret;
}
};
Transaction.prototype.send_max = function (send_max) {
if (send_max) {
this.tx_json.SendMax = Amount.json_rewrite(send_max);
}
return this;
}
};
// tag should be undefined or a 32 bit integer.
// YYY Add range checking for tag.
@@ -311,9 +298,8 @@ Transaction.prototype.source_tag = function (tag) {
if (tag) {
this.tx_json.SourceTag = tag;
}
return this;
}
};
// --> rate: In billionths.
Transaction.prototype.transfer_rate = function (rate) {
@@ -324,7 +310,7 @@ Transaction.prototype.transfer_rate = function (rate) {
}
return this;
}
};
// Add flags to a transaction.
// --> flags: undefined, _flag_, or [ _flags_ ]
@@ -339,12 +325,9 @@ Transaction.prototype.set_flags = function (flags) {
var flag_set = Array.isArray(flags) ? flags : [ flags ];
for (var index in flag_set) {
if (!flag_set.hasOwnProperty(index)) continue;
var flag = flag_set[index];
if (flag in transaction_flags) {
for (var i=0, l=flag_set.length; i<l; i++) {
var flag = flag_set[i];
if (transaction_flags.hasOwnProperty(flag)) {
this.tx_json.Flags += transaction_flags[flag];
} else {
// XXX Immediately report an error or mark it.
@@ -353,7 +336,7 @@ Transaction.prototype.set_flags = function (flags) {
}
return this;
}
};
//
// Transactions
@@ -375,7 +358,6 @@ Transaction.prototype.account_set = function (src) {
this._secret = this._account_secret(src);
this.tx_json.TransactionType = 'AccountSet';
this.tx_json.Account = UInt160.json_rewrite(src);
return this;
};
@@ -385,7 +367,6 @@ Transaction.prototype.claim = function (src, generator, public_key, signature) {
this.tx_json.Generator = generator;
this.tx_json.PublicKey = public_key;
this.tx_json.Signature = signature;
return this;
};
@@ -394,7 +375,6 @@ Transaction.prototype.offer_cancel = function (src, sequence) {
this.tx_json.TransactionType = 'OfferCancel';
this.tx_json.Account = UInt160.json_rewrite(src);
this.tx_json.OfferSequence = Number(sequence);
return this;
};
@@ -430,7 +410,6 @@ Transaction.prototype.password_fund = function (src, dst) {
this._secret = this._account_secret(src);
this.tx_json.TransactionType = 'PasswordFund';
this.tx_json.Destination = UInt160.json_rewrite(dst);
return this;
}
@@ -441,7 +420,6 @@ Transaction.prototype.password_set = function (src, authorized_key, generator, p
this.tx_json.Generator = generator;
this.tx_json.PublicKey = public_key;
this.tx_json.Signature = signature;
return this;
}
@@ -463,6 +441,14 @@ Transaction.prototype.password_set = function (src, authorized_key, generator, p
// .set_flags()
// .source_tag()
Transaction.prototype.payment = function (src, dst, deliver_amount) {
if (!UInt160.is_valid(src)) {
throw new Error('Payment source address invalid');
}
if (!UInt160.is_valid(dst)) {
throw new Error('Payment destination address invalid');
}
this._secret = this._account_secret(src);
this.tx_json.TransactionType = 'Payment';
this.tx_json.Account = UInt160.json_rewrite(src);
@@ -478,14 +464,17 @@ Transaction.prototype.ripple_line_set = function (src, limit, quality_in, qualit
this.tx_json.Account = UInt160.json_rewrite(src);
// Allow limit of 0 through.
if (limit !== void(0))
if (limit !== void(0)) {
this.tx_json.LimitAmount = Amount.json_rewrite(limit);
}
if (quality_in)
if (quality_in) {
this.tx_json.QualityIn = quality_in;
}
if (quality_out)
if (quality_out) {
this.tx_json.QualityOut = quality_out;
}
// XXX Throw an error if nothing is set.
@@ -499,7 +488,6 @@ Transaction.prototype.wallet_add = function (src, amount, authorized_key, public
this.tx_json.RegularKey = authorized_key;
this.tx_json.PublicKey = public_key;
this.tx_json.Signature = signature;
return this;
};
@@ -519,20 +507,6 @@ Transaction.prototype.fee_units = function () {
};
// Submit a transaction to the network.
// XXX Don't allow a submit without knowing ledger_index.
// XXX Have a network canSubmit(), post events for following.
// XXX Also give broader status for tracking through network disconnects.
// callback = function (status, info) {
// // status is final status. Only works under a ledger_accepting conditions.
// switch status:
// case 'tesSUCCESS': all is well.
// case 'tejSecretUnknown': unable to sign transaction - secret unknown
// case 'tejServerUntrusted': sending secret to untrusted server.
// case 'tejInvalidAccount': locally detected error.
// case 'tejLost': locally gave up looking
// default: some other TER
// }
Transaction.prototype.submit = function (callback) {
var self = this;
@@ -540,9 +514,9 @@ Transaction.prototype.submit = function (callback) {
function submission_error(error, message) {
if (!(error instanceof RippleError)) {
error = new RippleError(error);
error = new RippleError(error, message);
}
self.callback(error, message);
self.callback(error);
}
function submission_success(message) {

View File

@@ -1,6 +1,7 @@
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var RippleError = require('./rippleerror').RippleError;
var Queue = require('./transactionqueue').TransactionQueue;
/**
* @constructor TransactionManager
@@ -16,84 +17,128 @@ function TransactionManager(account) {
this.remote = account._remote;
this._timeout = void(0);
this._resubmitting = false;
this._pending = new Queue;
this._next_sequence = void(0);
this._config = { max_fee: self.remote.max_fee };
this._cache = { };
//XX Fee units
this._max_fee = Number(this.remote.max_fee) || Infinity;
function remote_disconnected() {
function remote_reconnected() {
self._resubmit();
};
self.remote.once('connect', remote_reconnected);
};
this.remote.on('disconnect', remote_disconnected);
function sequence_loaded(err, sequence) {
self._next_sequence = sequence;
self.emit('sequence_loaded', sequence);
};
this.account.get_next_sequence(sequence_loaded);
function cache_transaction(message) {
var transaction = {
ledger_hash: message.ledger_hash,
ledger_index: message.ledger_index,
metadata: message.meta,
tx_json: message.transaction
}
account.get_next_sequence(sequence_loaded);
transaction.tx_json.ledger_index = transaction.ledger_index;
transaction.tx_json.inLedger = transaction.ledger_index;
self._cache[message.transaction.Sequence] = transaction;
}
this.account.on('transaction', cache_transaction);
};
util.inherits(TransactionManager, EventEmitter);
/**
* @param {Object} tx
*/
// request_tx presents transactions in
// a format slightly different from
// request_transaction_entry
function rewrite_transaction(tx) {
try {
var result = {
ledger_index: tx.ledger_index,
metadata: tx.meta,
tx_json: {
Account: tx.Account,
Amount: tx.Amount,
Destination: tx.Destination,
Fee: tx.Fee,
Flags: tx.Flags,
Sequence: tx.Sequence,
SigningPubKey: tx.SigningPubKey,
TransactionType: tx.TransactionType,
hash: tx.hash
}
}
} catch(exception) {
}
return result || { };
};
//called whenever we get a transaction for an account whose transactions we're managing.
//TransactionManager.prototype.update = function(tx) {
// this._next_sequence_number = Math.max(tx.meta.account_next_seq, this._next_sequence_number);
// var queued;
//
// if (queued = this.queue.get('hash', tx.hash)) {
// this.queue.remove('hash', queued.hash);
// } else if (queued = this.queue.get('sequence', tx.seq)) {
// if (queued.nullified) {
// //Figures out new sequence number, signs, and sends to network
// this.submit(queued);
// }
// }
//}
TransactionManager.prototype._resubmit = function() {
var self = this;
/**
* User has given up, replace transaction with a null transaction
*
* @param {Object} tx
*/
function resubmit(pending, index) {
if (pending.finalized) {
// Transaction has been finalized, nothing to do
return;
}
//TransactionManager.prototype._nullify = function(tx) {
// this.queue.remove('hash', tx.hash);
//}
var sequence = pending.tx_json.Sequence;
var cached = self._cache[sequence];
/**
*/
pending.emit('resubmit');
//TransactionManager.prototype._timeout = function() {
// var transactions = this.queue.get();
// for (var i=0; i<transactions.length; i++) {
// this.remote.submit(transactions[i].blob);
// }
// //restart the timer
// this._timeout = setTimeout(function(){}, 10000);
//}
if (cached) {
// Transaction was received while waiting for
// resubmission
pending.emit('success', cached);
delete self._cache[sequence];
} else if (pending.hash) {
// Transaction was successfully submitted, and
// its hash discovered, but not validated
/**
* @param {Number} oldFee
* @param {Number} newFee
*/
function pending_check(err, res) {
if (self._is_not_found(err)) {
self._request(pending);
} else {
pending.emit('success', rewrite_transaction(res));
}
}
//TransactionManager.prototype._fee_change = function(oldFee, newFee) {
// if (newFee > this._config.maxFee) {
// if (this._timeout) clearTimeout(this._timeout);
// return;
// }
//
// // resubmit everything raising fee if needed
// for (var i=0; i<transactions.length; i++) {
// var tx = transactions[i];
//
// if (tx.fee < newFee) {
// tx.fee = newFee;
// //tx.blob = signTransaction(tx, tx.seq, newFee);
// //this.queue.add(tx);
// }
//
// this.remote.submit(tx.blob);
// }
//}
self.remote.request_tx(pending.hash, pending_check);
} else {
self._request(pending);
}
}
this._wait_ledgers(3, function() {
self._pending.forEach(resubmit);
});
}
TransactionManager.prototype._wait_ledgers = function(ledgers, callback) {
var self = this;
var closes = 0;
function ledger_closed() {
if (++closes === ledgers) {
callback();
self.remote.removeListener('ledger_closed', ledger_closed);
}
}
this.remote.on('ledger_closed', ledger_closed);
}
TransactionManager.prototype._request = function(tx) {
var self = this;
@@ -109,11 +154,12 @@ TransactionManager.prototype._request = function(tx) {
return;
}
tx.submit_index = remote._ledger_current_index;
function finalize(message) {
if (!tx.finalized) {
tx.finalized = true;
tx.emit('final', message);
self._pending.removeSequence(tx.tx_json.Sequence);
}
}
tx.once('error', finalize);
@@ -136,11 +182,9 @@ TransactionManager.prototype._request = function(tx) {
submit_request.tx_json(tx.tx_json);
}
// Handle insufficient fee
function submission_success(message) {
if (!message.engine_result) {
submission_error(message);
} else {
tx.submit_index = remote._ledger_current_index;
function transaction_proposed(message) {
tx.hash = message.tx_json.hash;
tx.set_state('client_proposed');
tx.emit('proposed', {
@@ -159,6 +203,20 @@ TransactionManager.prototype._request = function(tx) {
rejected: tx.isRejected(message.engine_result_code),
});
}
function transaction_failed(message) {
if (!tx.hash) tx.hash = message.tx_json.hash;
function transaction_requested(err, res) {
if (self._is_not_found(err)) {
self._resubmit();
} else {
tx.emit('error', new RippleError(message));
self._pending.removeSequence(tx.tx_json.Sequence);
}
}
self.remote.request_tx(tx.hash, transaction_requested);
}
function submission_error(err) {
@@ -166,17 +224,34 @@ TransactionManager.prototype._request = function(tx) {
tx.emit('error', new RippleError(err));
}
function submission_success(message) {
var engine_result = message.engine_result || '';
tx.hash = message.tx_json.hash;
switch (engine_result.slice(0, 3)) {
case 'tef':
//tefPAST_SEQ
transaction_failed(message);
break;
case 'tes':
transaction_proposed(message);
break;
default:
submission_error(message);
}
}
submit_request.once('success', submission_success);
submit_request.once('error', submission_error);
// submit_request.timeout(1000 * 2, function() {
// tx.emit('error', new Error('Timeout'));
// });
//submit_request.emit = function() {}
submit_request.request();
submit_request.timeout(1000 * 10, function() {
if (self.remote._connected) {
self._resubmit();
}
});
tx.set_state('client_submitted');
tx.emit('submitted');
@@ -184,12 +259,11 @@ TransactionManager.prototype._request = function(tx) {
};
TransactionManager.prototype._is_not_found = function(error) {
return !!error && typeof error === 'object'
&& typeof error.remote === 'object'
var not_found_re = /^(txnNotFound|transactionNotFound)$/;
return error && typeof error === 'object'
&& error.error === 'remoteError'
&& (error.remote.error === 'transactionNotFound'
|| error.remote.error === 'ledgerNotFound')
;
&& typeof error.remote === 'object'
&& not_found_re.test(error.remote.error);
};
TransactionManager.prototype._detect_ledger_entry = function(tx) {
@@ -197,29 +271,21 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) {
var remote = this.remote;
var checked_ledgers = { };
function ledger_closed(message) {
if (tx.finalized) {
// Transaction has already erred or
// been detected in the ledger
return;
}
function entry_callback(err, message) {
if (typeof message !== 'object') return;
var ledger_hash = message.ledger_hash;
var ledger_index = message.ledger_index;
if (checked_ledgers.hasOwnProperty(ledger_hash)) {
// Ledger with this hash has already been
// checked to contain transaction
if (tx.finalized || checked_ledgers[ledger_hash]) {
// Transaction submission has already erred or
// this ledger has already been checked for
// transaction
return;
}
checked_ledgers[ledger_hash] = true;
var request_transaction = remote.request_transaction_entry(tx.hash, ledger_hash);
request_transaction.callback(function(err, message) {
if (tx.finalized) return;
if (self._is_not_found(err)) {
var dif = ledger_index - tx.submit_index;
if (dif >= 8) {
@@ -229,7 +295,7 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) {
} else if (dif >= 4) {
// Missing
tx.set_state('client_missing');
tx.emit('pending', message);
tx.emit('missing', message);
} else {
// Pending
tx.emit('pending', message);
@@ -242,7 +308,12 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) {
}
tx.emit('success', message);
}
});
}
function ledger_closed(message) {
if (!tx.finalized && !checked_ledgers[message.ledger_hash]) {
remote.request_transaction_entry(tx.hash, message.ledger_hash, entry_callback);
}
}
function transaction_proposed() {
@@ -253,10 +324,12 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) {
function transaction_finalized() {
// Stop checking the ledger
remote.removeListener('ledger_closed', ledger_closed);
tx.removeListener('proposed', transaction_proposed);
}
tx.once('proposed', transaction_proposed);
tx.once('final', transaction_finalized);
tx.once('resubmit', transaction_finalized);
};
/**
@@ -265,19 +338,24 @@ TransactionManager.prototype._detect_ledger_entry = function(tx) {
TransactionManager.prototype.submit = function(tx) {
// If sequence number is not yet known, defer until it is.
var self = this;
if (!this._next_sequence) {
this.once('sequence_loaded', this.submit.bind(this, tx));
function resubmit_transaction() {
self.submit(tx);
}
this.once('sequence_loaded', resubmit_transaction);
return;
}
var seq = this._next_sequence++;
var fee = tx.fee_units();
if (fee <= this._config.max_fee) {
tx.tx_json.Sequence = seq;
tx.tx_json.Fee = fee;
tx.tx_json.Sequence = this._next_sequence++;
tx.complete();
this._pending.push(tx);
var fee = tx.tx_json.Fee;
if (fee === void(0) || fee <= this._max_fee) {
this._request(tx);
}
};

View File

@@ -0,0 +1,59 @@
function TransactionQueue() {
this._queue = [ ];
}
TransactionQueue.prototype.length = function() {
return this._queue.length;
};
TransactionQueue.prototype.push = function(o) {
return this._queue.push(o);
};
TransactionQueue.prototype.hasHash = function(hash) {
return this.indexOf('hash', hash) !== -1;
};
TransactionQueue.prototype.hasSequence = function(sequence) {
return this.indexOf('sequence', sequence) !== -1;
};
TransactionQueue.prototype.indexOf = function(prop, val) {
var index = -1;
for (var i=0, tx; tx=this._queue[i]; i++) {
if (tx[prop] === val) {
index = i;
break;
}
}
return index;
};
TransactionQueue.prototype.removeSequence = function(sequence) {
var result = [ ];
for (var i=0, tx; tx=this._queue[i]; i++) {
if (!tx.tx_json) continue;
if (tx.tx_json.Sequence !== sequence)
result.push(tx);
}
this._queue = result;
};
TransactionQueue.prototype.removeHash = function(hash) {
var result = [ ];
for (var i=0, tx; tx=this._queue[i]; i++) {
if (!tx.tx_json) continue;
if (tx.hash !== hash)
result.push(tx);
}
this._queue = result;
};
TransactionQueue.prototype.forEach = function(fn) {
for (var i=0, tx; tx=this._queue[i]; i++) {
fn(tx, i);
}
};
exports.TransactionQueue = TransactionQueue;