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

This commit is contained in:
jed
2013-03-05 11:00:14 -08:00
151 changed files with 5854 additions and 1896 deletions

View File

@@ -9,15 +9,121 @@
// var network = require("./network.js");
var EventEmitter = require('events').EventEmitter;
var Amount = require('./amount.js').Amount;
var UInt160 = require('./amount.js').UInt160;
var EventEmitter = require('events').EventEmitter;
var Amount = require('./amount').Amount;
var UInt160 = require('./uint160').UInt160;
var Account = function (network, account) {
this._network = network;
this._account = UInt160.json_rewrite(account);
var extend = require('extend');
return this;
var Account = function (remote, account) {
var self = this;
this._remote = remote;
this._account = UInt160.from_json(account);
this._account_id = this._account.to_json();
this._subs = 0;
// Ledger entry object
// Important: This must never be overwritten, only extend()-ed
this._entry = {};
this.on('newListener', function (type, listener) {
if (Account.subscribe_events.indexOf(type) !== -1) {
if (!self._subs && 'open' === self._remote._online_state) {
self._remote.request_subscribe()
.accounts(self._account_id)
.request();
}
self._subs += 1;
}
});
this.on('removeListener', function (type, listener) {
if (Account.subscribe_events.indexOf(type) !== -1) {
self._subs -= 1;
if (!self._subs && 'open' === self._remote._online_state) {
self._remote.request_unsubscribe()
.accounts(self._account_id)
.request();
}
}
});
this._remote.on('connect', function () {
if (self._subs) {
self._remote.request_subscribe()
.accounts(self._account_id)
.request();
}
});
this.on('transaction', function (msg) {
var changed = false;
msg.mmeta.each(function (an) {
if (an.entryType === 'AccountRoot' &&
an.fields.Account === self._account_id) {
extend(self._entry, an.fieldsNew, an.fieldsFinal);
changed = true;
}
});
if (changed) {
self.emit('entry', self._entry);
}
});
return this;
};
Account.prototype = new EventEmitter;
/**
* List of events that require a remote subscription to the account.
*/
Account.subscribe_events = ['transaction', 'entry'];
Account.prototype.to_json = function ()
{
return this._account.to_json();
};
/**
* Whether the AccountId is valid.
*
* Note: This does not tell you whether the account exists in the ledger.
*/
Account.prototype.is_valid = function ()
{
return this._account.is_valid();
};
/**
* Retrieve the current AccountRoot entry.
*
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
* "entry" event.
*
* @param {function (err, entry)} callback Called with the result
*/
Account.prototype.entry = function (callback)
{
var self = this;
self._remote.request_account_info(this._account_id)
.on('success', function (e) {
extend(self._entry, e.account_data);
self.emit('entry', self._entry);
if ("function" === typeof callback) {
callback(null, e);
}
})
.on('error', function (e) {
callback(e);
})
.request();
return this;
};
exports.Account = Account;

View File

@@ -111,7 +111,17 @@ Amount.prototype.add = function (v) {
result._is_negative = s.compareTo(BigInteger.ZERO) < 0;
result._value = result._is_negative ? s.negate() : s;
}
else {
else if (v.is_zero()) {
result = this;
}
else if (this.is_zero()) {
result = v.clone();
// YYY Why are these cloned? We never modify them.
result._currency = this._currency;
result._issuer = this._issuer;
}
else
{
var v1 = this._is_negative ? this._value.negate() : this._value;
var o1 = this._offset;
var v2 = v._is_negative ? v._value.negate() : v._value;
@@ -137,8 +147,8 @@ Amount.prototype.add = function (v) {
result._value = result._value.negate();
}
result._currency = this._currency.clone();
result._issuer = this._issuer.clone();
result._currency = this._currency;
result._issuer = this._issuer;
result.canonicalize();
}
@@ -266,6 +276,9 @@ Amount.prototype.copyTo = function (d, negate) {
this._currency.copyTo(d._currency);
this._issuer.copyTo(d._issuer);
// Prevent negative zero
if (d.is_zero()) d._is_negative = false;
return d;
};
@@ -273,21 +286,29 @@ Amount.prototype.currency = function () {
return this._currency;
};
// Check BigInteger NaN
// Checks currency, does not check issuer.
Amount.prototype.equals = function (d) {
return 'string' === typeof (d)
? this.equals(Amount.from_json(d))
: this === d
|| (d instanceof Amount
&& this._is_native === d._is_native
&& (this._is_native
? this._value.equals(d._value)
: this._currency.equals(d._currency)
? this._is_negative === d._is_negative
? this._value.equals(d._value)
: this._value.equals(BigInteger.ZERO) && d._value.equals(BigInteger.ZERO)
: false));
Amount.prototype.equals = function (d, ignore_issuer) {
if ("string" === typeof d) {
return this.equals(Amount.from_json(d));
}
if (this === d) return true;
if (d instanceof Amount) {
if (!this.is_valid() || !d.is_valid()) return false;
if (this._is_native !== d._is_native) return false;
if (!this._value.equals(d._value) || this._offset !== d._offset) {
return false;
}
if (this._is_negative !== d._is_negative) return false;
if (!this._is_native) {
if (!this._currency.equals(d._currency)) return false;
if (!ignore_issuer && !this._issuer.equals(d._issuer)) return false;
}
return true;
} else return false;
};
// Result in terms of this' currency and issuer.
@@ -298,7 +319,7 @@ Amount.prototype.divide = function (d) {
throw "divide by zero";
}
else if (this.is_zero()) {
result = this.clone();
result = this;
}
else if (!this.is_valid()) {
throw new Error("Invalid dividend");
@@ -307,13 +328,35 @@ Amount.prototype.divide = function (d) {
throw new Error("Invalid divisor");
}
else {
var _n = this;
if (_n.is_native()) {
_n = _n.clone();
while (_n._value.compareTo(consts.bi_man_min_value) < 0) {
_n._value = _n._value.multiply(consts.bi_10);
_n._offset -= 1;
}
}
var _d = d;
if (_d.is_native()) {
_d = _d.clone();
while (_d._value.compareTo(consts.bi_man_min_value) < 0) {
_d._value = _d._value.multiply(consts.bi_10);
_d._offset -= 1;
}
}
result = new Amount();
result._offset = this._offset - d._offset - 17;
result._value = this._value.multiply(consts.bi_1e17).divide(d._value).add(consts.bi_5);
result._is_native = this._is_native;
result._is_negative = this._is_negative !== d._is_negative;
result._currency = this._currency.clone();
result._issuer = this._issuer.clone();
result._offset = _n._offset - _d._offset - 17;
result._value = _n._value.multiply(consts.bi_1e17).divide(_d._value).add(consts.bi_5);
result._is_native = _n._is_native;
result._is_negative = _n._is_negative !== _d._is_negative;
result._currency = _n._currency;
result._issuer = _n._issuer;
result.canonicalize();
}
@@ -339,6 +382,13 @@ Amount.prototype.divide = function (d) {
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
*/
Amount.prototype.ratio_human = function (denominator) {
if ("number" === typeof denominator && parseInt(denominator) === denominator) {
// Special handling of integer arguments
denominator = Amount.from_json("" + denominator + ".0");
} else {
denominator = Amount.from_json(denominator);
}
var numerator = this;
denominator = Amount.from_json(denominator);
@@ -380,7 +430,12 @@ Amount.prototype.ratio_human = function (denominator) {
* @return {Amount} The product. Unit will be the same as the first factor.
*/
Amount.prototype.product_human = function (factor) {
factor = Amount.from_json(factor);
if ("number" === typeof factor && parseInt(factor) === factor) {
// Special handling of integer arguments
factor = Amount.from_json("" + factor + ".0");
} else {
factor = Amount.from_json(factor);
}
var product = this.multiply(factor);
@@ -413,6 +468,10 @@ Amount.prototype.is_negative = function () {
: false; // NaN is not negative
};
Amount.prototype.is_positive = function () {
return !this.is_zero() && !this.is_negative();
};
// Only checks the value. Not the currency and issuer.
Amount.prototype.is_valid = function () {
return this._value instanceof BigInteger;
@@ -433,15 +492,16 @@ Amount.prototype.issuer = function () {
};
// Result in terms of this' currency and issuer.
// XXX Diverges from cpp.
Amount.prototype.multiply = function (v) {
var result;
if (this.is_zero()) {
result = this.clone();
if (this.is_zero()) {
result = this;
}
else if (v.is_zero()) {
result = this.clone();
result._value = BigInteger.ZERO.clone();
result._value = BigInteger.ZERO;
}
else {
var v1 = this._value;
@@ -449,14 +509,18 @@ Amount.prototype.multiply = function (v) {
var v2 = v._value;
var o2 = v._offset;
while (v1.compareTo(consts.bi_man_min_value) < 0 ) {
v1 = v1.multiply(consts.bi_10);
o1 -= 1;
if (this.is_native()) {
while (v1.compareTo(consts.bi_man_min_value) < 0) {
v1 = v1.multiply(consts.bi_10);
o1 -= 1;
}
}
while (v2.compareTo(consts.bi_man_min_value) < 0 ) {
v2 = v2.multiply(consts.bi_10);
o2 -= 1;
if (v.is_native()) {
while (v2.compareTo(consts.bi_man_min_value) < 0) {
v2 = v2.multiply(consts.bi_10);
o2 -= 1;
}
}
result = new Amount();
@@ -464,8 +528,8 @@ Amount.prototype.multiply = function (v) {
result._value = v1.multiply(v2).divide(consts.bi_1e14).add(consts.bi_7);
result._is_native = this._is_native;
result._is_negative = this._is_negative !== v._is_negative;
result._currency = this._currency.clone();
result._issuer = this._issuer.clone();
result._currency = this._currency;
result._issuer = this._issuer;
result.canonicalize();
}
@@ -549,11 +613,15 @@ Amount.prototype.parse_issuer = function (issuer) {
Amount.prototype.parse_json = function (j) {
if ('string' === typeof j) {
// .../.../... notation is not a wire format. But allowed for easier testing.
var m = j.match(/^(.+)\/(...)\/(.+)$/);
var m = j.match(/^([^/]+)\/(...)(?:\/(.+))?$/);
if (m) {
this._currency = Currency.from_json(m[2]);
this._issuer = UInt160.from_json(m[3]);
if (m[3]) {
this._issuer = UInt160.from_json(m[3]);
} else {
this._issuer = UInt160.from_json('1');
}
this.parse_value(m[1]);
}
else {
@@ -562,6 +630,9 @@ Amount.prototype.parse_json = function (j) {
this._issuer = new UInt160();
}
}
else if ('number' === typeof j) {
this.parse_json(""+j);
}
else if ('object' === typeof j && j instanceof Amount) {
j.copyTo(this);
}
@@ -673,7 +744,7 @@ Amount.prototype.parse_value = function (j) {
}
}
else if (j instanceof BigInteger) {
this._value = j.clone();
this._value = j;
}
else {
this._value = NaN;
@@ -690,6 +761,7 @@ Amount.prototype.set_currency = function (c) {
{
c.copyTo(this._currency);
}
this._is_native = this._currency.is_native();
return this;
};
@@ -879,28 +951,33 @@ Amount.prototype.to_text_full = function (opts) {
};
// For debugging.
Amount.prototype.not_equals_why = function (d) {
return 'string' === typeof (d)
? this.not_equals_why(Amount.from_json(d))
: this === d
? false
: d instanceof Amount
? this._is_native === d._is_native
? this._is_native
? this._value.equals(d._value)
? false
: "XRP value differs."
: this._currency.equals(d._currency)
? this._is_negative === d._is_negative
? this._value.equals(d._value)
? false
: this._value.equals(BigInteger.ZERO) && d._value.equals(BigInteger.ZERO)
? false
: "Non-XRP value differs."
: "Non-XRP sign differs."
: "Non-XRP currency differs (" + JSON.stringify(this._currency) + "/" + JSON.stringify(d._currency) + ")"
: "Native mismatch"
: "Wrong constructor."
Amount.prototype.not_equals_why = function (d, ignore_issuer) {
if ("string" === typeof d) {
return this.not_equals_why(Amount.from_json(d));
}
if (this === d) return false;
if (d instanceof Amount) {
if (!this.is_valid() || !d.is_valid()) return "Invalid amount.";
if (this._is_native !== d._is_native) return "Native mismatch.";
var type = this._is_native ? "XRP" : "Non-XRP";
if (!this._value.equals(d._value) || this._offset !== d._offset) {
return type+" value differs.";
}
if (this._is_negative !== d._is_negative) return type+" sign differs.";
if (!this._is_native) {
if (!this._currency.equals(d._currency)) return "Non-XRP currency differs.";
if (!ignore_issuer && !this._issuer.equals(d._issuer)) {
return "Non-XRP issuer differs.";
}
}
return false;
} else return "Wrong constructor.";
};
exports.Amount = Amount;

View File

@@ -130,7 +130,11 @@ Base.decode_check = function (version, input, alphabet) {
if (computed[i] !== checksum[i])
return NaN;
return new BigInteger(buffer.slice(1, -4));
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
// intrepret the value as a negative number
buffer[0] = 0;
return new BigInteger(buffer.slice(0, -4), 256);
}
exports.Base = Base;

67
src/js/keypair.js Normal file
View File

@@ -0,0 +1,67 @@
var sjcl = require('../../build/sjcl');
var UInt256 = require('./uint256').UInt256;
var KeyPair = function ()
{
this._curve = sjcl.ecc.curves['c256'];
this._secret = null;
this._pubkey = null;
};
KeyPair.from_bn_secret = function (j)
{
if (j instanceof this) {
return j.clone();
} else {
return (new this()).parse_bn_secret(j);
}
};
KeyPair.prototype.parse_bn_secret = function (j)
{
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], j);
return this;
};
/**
* Returns public key as sjcl public key.
*
* @private
*/
KeyPair.prototype._pub = function ()
{
var curve = this._curve;
if (!this._pubkey && this._secret) {
var exponent = this._secret._exponent;
this._pubkey = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(exponent));
}
return this._pubkey;
};
/**
* Returns public key as hex.
*
* Key will be returned as a compressed pubkey - 33 bytes converted to hex.
*/
KeyPair.prototype.to_hex_pub = function ()
{
var pub = this._pub();
if (!pub) return null;
var point = pub._point, y_even = point.y.mod(2).equals(0);
return sjcl.codec.hex.fromBits(sjcl.bitArray.concat(
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
point.x.toBits(this._curve.r.bitLength())
)).toUpperCase();
};
KeyPair.prototype.sign = function (hash)
{
hash = UInt256.from_json(hash);
return this._secret.signDER(hash.to_bits(), 0);
};
exports.KeyPair = KeyPair;

108
src/js/meta.js Normal file
View File

@@ -0,0 +1,108 @@
var extend = require('extend');
var UInt160 = require('./uint160').UInt160;
var Amount = require('./amount').Amount;
/**
* Meta data processing facility.
*/
var Meta = function (raw_data)
{
this.nodes = [];
for (var i = 0, l = raw_data.AffectedNodes.length; i < l; i++) {
var an = raw_data.AffectedNodes[i],
result = {};
["CreatedNode", "ModifiedNode", "DeletedNode"].forEach(function (x) {
if (an[x]) result.diffType = x;
});
if (!result.diffType) return null;
an = an[result.diffType];
result.entryType = an.LedgerEntryType;
result.ledgerIndex = an.LedgerIndex;
result.fields = extend({}, an.PreviousFields, an.NewFields, an.FinalFields);
result.fieldsPrev = an.PreviousFields || {};
result.fieldsNew = an.NewFields || {};
result.fieldsFinal = an.FinalFields || {};
this.nodes.push(result);
}
};
/**
* Execute a function on each affected node.
*
* The callback is passed two parameters. The first is a node object which looks
* like this:
*
* {
* // Type of diff, e.g. CreatedNode, ModifiedNode
* diffType: 'CreatedNode'
*
* // Type of node affected, e.g. RippleState, AccountRoot
* entryType: 'RippleState',
*
* // Index of the ledger this change occurred in
* ledgerIndex: '01AB01AB...',
*
* // Contains all fields with later versions taking precedence
* //
* // This is a shorthand for doing things like checking which account
* // this affected without having to check the diffType.
* fields: {...},
*
* // Old fields (before the change)
* fieldsPrev: {...},
*
* // New fields (that have been added)
* fieldsNew: {...},
*
* // Changed fields
* fieldsFinal: {...}
* }
*
* The second parameter to the callback is the index of the node in the metadata
* (first entry is index 0).
*/
Meta.prototype.each = function (fn)
{
for (var i = 0, l = this.nodes.length; i < l; i++) {
fn(this.nodes[i], i);
}
};
var amountFieldsAffectingIssuer = [
"LowLimit", "HighLimit", "TakerPays", "TakerGets"
];
Meta.prototype.getAffectedAccounts = function ()
{
var accounts = [];
// This code should match the behavior of the C++ method:
// TransactionMetaSet::getAffectedAccounts
this.each(function (an) {
var fields = (an.diffType === "CreatedNode") ? an.fieldsNew : an.fieldsFinal;
for (var i in fields) {
var field = fields[i];
if ("string" === typeof field && UInt160.is_valid(field)) {
accounts.push(field);
} else if (amountFieldsAffectingIssuer.indexOf(i) !== -1) {
var amount = Amount.from_json(field);
var issuer = amount.issuer();
if (issuer.is_valid() && !issuer.is_zero()) {
accounts.push(issuer.to_json());
}
}
}
});
return accounts;
};
exports.Meta = Meta;

104
src/js/orderbook.js Normal file
View File

@@ -0,0 +1,104 @@
// Routines for working with an orderbook.
//
// Events:
// var network = require("./network.js");
var EventEmitter = require('events').EventEmitter;
var Amount = require('./amount').Amount;
var UInt160 = require('./uint160').UInt160;
var Currency = require('./currency').Currency;
var extend = require('extend');
var OrderBook = function (remote,
currency_out, issuer_out,
currency_in, issuer_in) {
var self = this;
this._remote = remote;
this._currency_out = currency_out;
this._issuer_out = issuer_out;
this._currency_in = currency_in;
this._issuer_in = issuer_in;
this._subs = 0;
// Ledger entry object
// Important: This must never be overwritten, only extend()-ed
this._entry = {};
this.on('newListener', function (type, listener) {
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
if (!self._subs && 'open' === self._remote._online_state) {
self._remote.request_subscribe()
.books([self.to_json()])
.request();
}
self._subs += 1;
}
});
this.on('removeListener', function (type, listener) {
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
self._subs -= 1;
if (!self._subs && 'open' === self._remote._online_state) {
self._remote.request_unsubscribe()
.books([self.to_json()])
.request();
}
}
});
this._remote.on('connect', function () {
if (self._subs) {
self._remote.request_subscribe()
.books([self.to_json()])
.request();
}
});
return this;
};
OrderBook.prototype = new EventEmitter;
/**
* List of events that require a remote subscription to the orderbook.
*/
OrderBook.subscribe_events = ['transaction'];
OrderBook.prototype.to_json = function ()
{
var json = {
"CurrencyOut": this._currency_out,
"CurrencyIn": this._currency_in
};
if (json["CurrencyOut"] !== "XRP") json["IssuerOut"] = this._issuer_out;
if (json["CurrencyIn"] !== "XRP") json["IssuerIn"] = this._issuer_in;
return json;
};
/**
* Whether the OrderBook is valid.
*
* Note: This only checks whether the parameters (currencies and issuer) are
* syntactically valid. It does not check anything against the ledger.
*/
OrderBook.prototype.is_valid = function ()
{
return (
Currency.is_valid(this._currency_in) &&
(this._currency_in !== "XRP" && UInt160.is_valid(this._issuer_in)) &&
Currency.is_valid(this._currency_out) &&
(this._currency_out !== "XRP" && UInt160.is_valid(this._issuer_out)) &&
!(this._currency_in === "XRP" && this._currency_out === "XRP")
);
};
exports.OrderBook = OrderBook;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -20,6 +20,9 @@ var Amount = require('./amount').Amount;
var Currency = require('./amount').Currency;
var UInt160 = require('./amount').UInt160;
var Transaction = require('./transaction').Transaction;
var Account = require('./account').Account;
var Meta = require('./meta').Meta;
var OrderBook = require('./orderbook').OrderBook;
var utils = require('./utils');
var config = require('./config');
@@ -88,6 +91,26 @@ Request.prototype.ledger_index = function (ledger_index) {
return this;
};
Request.prototype.ledger_select = function (ledger_spec) {
if (ledger_spec === 'closed') {
this.message.ledger_index = -1;
} else if (ledger_spec === 'current') {
this.message.ledger_index = -2;
} else if (ledger_spec === 'verified') {
this.message.ledger_index = -3;
} else if (String(ledger_spec).length > 12) { // XXX Better test needed
this.message.ledger_hash = ledger_spec;
} else {
this.message.ledger_index = ledger_spec;
}
return this;
};
Request.prototype.account_root = function (account) {
this.message.account_root = UInt160.json_rewrite(account);
@@ -138,6 +161,12 @@ Request.prototype.tx_json = function (j) {
return this;
};
Request.prototype.tx_blob = function (j) {
this.message.tx_blob = j;
return this;
};
Request.prototype.ripple_state = function (account, issuer, currency) {
this.message.ripple_state = {
'accounts' : [
@@ -173,6 +202,31 @@ Request.prototype.rt_accounts = function (accounts) {
return this.accounts(accounts, true);
};
Request.prototype.books = function (books) {
var procBooks = [];
for (var i = 0, l = books.length; i < l; i++) {
var book = books[i];
var json = {
"CurrencyOut": Currency.json_rewrite(book["CurrencyOut"]),
"CurrencyIn": Currency.json_rewrite(book["CurrencyIn"])
};
if (json["CurrencyOut"] !== "XRP") {
json["IssuerOut"] = UInt160.json_rewrite(book["IssuerOut"]);
}
if (json["CurrencyIn"] !== "XRP") {
json["IssuerIn"] = UInt160.json_rewrite(book["IssuerIn"]);
}
procBooks.push(json);
}
this.message.books = procBooks;
return this;
};
//
// Remote - access to a remote Ripple server via websocket.
//
@@ -196,6 +250,7 @@ var Remote = function (opts, trace) {
this.websocket_ssl = opts.websocket_ssl;
this.local_sequence = opts.local_sequence; // Locally track sequence numbers
this.local_fee = opts.local_fee; // Locally set fees
this.local_signing = opts.local_signing;
this.id = 0;
this.trace = opts.trace || trace;
this._server_fatal = false; // True, if we know server exited.
@@ -212,14 +267,21 @@ var Remote = function (opts, trace) {
this.retry = undefined;
this._load_base = 256;
this._load_fee = 256;
this._load_factor = 1.0;
this._fee_ref = undefined;
this._fee_base = undefined;
this._reserve_base = undefined;
this._reserve_inc = undefined;
this._server_status = undefined;
// Local signing implies local fees and sequences
if (this.local_signing) {
this.local_sequence = true;
this.local_fee = true;
}
// Cache information for accounts.
// DEPRECATED, will be removed
this.accounts = {
// Consider sequence numbers stable if you know you're not generating bad transactions.
// Otherwise, clear it to have it automatically refreshed from the network.
@@ -228,6 +290,9 @@ var Remote = function (opts, trace) {
};
// Hash map of Account objects by AccountId.
this._accounts = {};
// List of secrets that we know about.
this.secrets = {
// Secrets can be set by calling set_secret(account, secret).
@@ -329,11 +394,13 @@ Remote.prototype._set_state = function (state) {
switch (state) {
case 'online':
this._online_state = 'open';
this.emit('connect');
this.emit('connected');
break;
case 'offline':
this._online_state = 'closed';
this.emit('disconnect');
this.emit('disconnected');
break;
}
@@ -374,11 +441,13 @@ Remote.prototype.ledger_hash = function () {
// Stop from open state.
Remote.prototype._connect_stop = function () {
delete this.ws.onerror;
delete this.ws.onclose;
if (this.ws) {
delete this.ws.onerror;
delete this.ws.onclose;
this.ws.terminate();
delete this.ws;
this.ws.close();
delete this.ws;
}
this._set_state('offline');
};
@@ -437,6 +506,12 @@ Remote.prototype._connect_start = function () {
if (this.trace) console.log("remote: connect: %s", url);
// There should not be an active connection at this point, but if there is
// we will shut it down so we don't end up with a duplicate.
if (this.ws) {
this._connect_stop();
}
var WebSocket = require('ws');
var ws = this.ws = new WebSocket(url);
@@ -494,6 +569,7 @@ Remote.prototype._connect_start = function () {
// It is possible for messages to be dispatched after the connection is closed.
Remote.prototype._connect_message = function (ws, json) {
var self = this;
var message = JSON.parse(json);
var unexpected = false;
var request;
@@ -543,6 +619,23 @@ Remote.prototype._connect_message = function (ws, json) {
case 'account':
// XXX If not trusted, need proof.
if (this.trace) utils.logObject("remote: account: %s", message);
// Process metadata
message.mmeta = new Meta(message.meta);
// Pass the event on to any related Account objects
var affected = message.mmeta.getAffectedAccounts();
for (var i = 0, l = affected.length; i < l; i++) {
var account = self._accounts[affected[i]];
// Only trigger the event if the account object is actually
// subscribed - this prevents some weird phantom events from
// occurring.
if (account && account._subs) {
account.emit('transaction', message);
}
}
this.emit('account', message);
break;
@@ -561,6 +654,16 @@ Remote.prototype._connect_message = function (ws, json) {
Remote.online_states.indexOf(message.server_status) !== -1
? 'online'
: 'offline');
if ('load_base' in message
&& 'load_factor' in message
&& (message.load_base !== this._load_base || message.load_factor != this._load_factor))
{
this._load_base = message.load_base;
this._load_factor = message.load_factor;
this.emit('load', { 'load_base' : this._load_base, 'load_factor' : this.load_factor });
}
break;
// All other messages
@@ -763,15 +866,25 @@ Remote.prototype.request_unsubscribe = function (streams) {
return request;
};
// --> current: true, for the current ledger.
Remote.prototype.request_transaction_entry = function (hash, current) {
// .ledger_choose()
// .ledger_hash()
// .ledger_index()
Remote.prototype.request_transaction_entry = function (hash) {
//utils.assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol.
return (new Request(this, 'transaction_entry'))
.ledger_choose(current)
.tx_hash(hash);
};
// DEPRECATED: use request_transaction_entry
Remote.prototype.request_tx = function (hash) {
var request = new Request(this, 'tx');
request.message.transaction = hash;
return request;
};
Remote.prototype.request_account_info = function (accountID) {
var request = new Request(this, 'account_info');
@@ -863,58 +976,12 @@ Remote.prototype.request_sign = function (secret, tx_json) {
};
// Submit a transaction.
Remote.prototype.submit = function (transaction) {
Remote.prototype.request_submit = function () {
var self = this;
if (transaction._secret && !this.trusted)
{
transaction.emit('error', {
'result' : 'tejServerUntrusted',
'result_message' : "Attempt to give a secret to an untrusted server."
});
}
else {
if (self.local_sequence && !transaction.tx_json.Sequence) {
transaction.tx_json.Sequence = this.account_seq(transaction.tx_json.Account, 'ADVANCE');
// console.log("Sequence: %s", transaction.tx_json.Sequence);
}
var request = new Request(this, 'submit');
if (self.local_sequence && !transaction.tx_json.Sequence) {
// Look in the last closed ledger.
this.account_seq_cache(transaction.tx_json.Account, false)
.on('success_account_seq_cache', function () {
// Try again.
self.submit(transaction);
})
.on('error_account_seq_cache', function (message) {
// XXX Maybe be smarter about this. Don't want to trust an untrusted server for this seq number.
// Look in the current ledger.
self.account_seq_cache(transaction.tx_json.Account, 'CURRENT')
.on('success_account_seq_cache', function () {
// Try again.
self.submit(transaction);
})
.on('error_account_seq_cache', function (message) {
// Forward errors.
transaction.emit('error', message);
})
.request();
})
.request();
}
else {
// Convert the transaction into a request and submit it.
(new Request(this, 'submit'))
.build_path(transaction._build_path)
.tx_json(transaction.tx_json)
.secret(transaction._secret)
.on('success', function (message) { transaction.emit('success', message); }) // Forward successes and errors.
.on('error', function (message) { transaction.emit('error', message); })
.request();
}
}
return request;
};
//
@@ -928,7 +995,7 @@ Remote.prototype._server_subscribe = function () {
var self = this;
var feeds = [ 'ledger', 'server' ];
if (this._transaction_subs)
feeds.push('transactions');
@@ -950,10 +1017,10 @@ Remote.prototype._server_subscribe = function () {
// FIXME Use this to estimate fee.
self._load_base = message.load_base || 256;
self._load_fee = message.load_fee || 256;
self._load_factor = message.load_factor || 1.0;
self._fee_ref = message.fee_ref;
self._fee_base = message.fee_base;
self._reserve_base = message.reverse_base;
self._reserve_base = message.reserve_base;
self._reserve_inc = message.reserve_inc;
self._server_status = message.server_status;
@@ -1017,6 +1084,23 @@ Remote.prototype.request_owner_count = function (account, current) {
});
};
Remote.prototype.account = function (accountId) {
var account = new Account(this, accountId);
if (!account.is_valid()) return account;
return this._accounts[account.to_json()] = account;
};
Remote.prototype.book = function (currency_out, issuer_out,
currency_in, issuer_in) {
var book = new OrderBook(this,
currency_out, issuer_out,
currency_in, issuer_in);
return book;
}
// Return the next account sequence if possible.
// <-- undefined or Sequence
Remote.prototype.account_seq = function (account, advance) {
@@ -1028,7 +1112,8 @@ Remote.prototype.account_seq = function (account, advance) {
{
seq = account_info.seq;
if (advance) account_info.seq += 1;
if (advance === "ADVANCE") account_info.seq += 1;
if (advance === "REWIND") account_info.seq -= 1;
// console.log("cached: %s current=%d next=%d", account, seq, account_info.seq);
}

View File

@@ -5,85 +5,65 @@
var sjcl = require('../../build/sjcl');
var utils = require('./utils');
var jsbn = require('./jsbn');
var extend = require('extend');
var BigInteger = jsbn.BigInteger;
var Base = require('./base').Base,
UInt256 = require('./uint256').UInt256;
UInt = require('./uint').UInt,
UInt256 = require('./uint256').UInt256,
KeyPair = require('./keypair').KeyPair;
var Seed = function () {
var Seed = extend(function () {
// Internal form: NaN or BigInteger
this._value = NaN;
};
this._curve = sjcl.ecc.curves['c256'];
this._value = NaN;
}, UInt);
Seed.json_rewrite = function (j) {
return Seed.from_json(j).to_json();
};
// Return a new Seed from j.
Seed.from_json = function (j) {
return (j instanceof Seed)
? j.clone()
: (new Seed()).parse_json(j);
};
Seed.is_valid = function (j) {
return Seed.from_json(j).is_valid();
};
Seed.prototype.clone = function () {
return this.copyTo(new Seed());
};
// Returns copy.
Seed.prototype.copyTo = function (d) {
d._value = this._value;
return d;
};
Seed.prototype.equals = function (d) {
return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value);
};
Seed.prototype.is_valid = function () {
return this._value instanceof BigInteger;
};
Seed.width = 16;
Seed.prototype = extend({}, UInt.prototype);
Seed.prototype.constructor = Seed;
// value = NaN on error.
// One day this will support rfc1751 too.
Seed.prototype.parse_json = function (j) {
if ('string' !== typeof j) {
this._value = NaN;
}
else if (j[0] === "s") {
this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
}
else if (16 === j.length) {
this._value = new BigInteger(utils.stringToArray(j), 128);
}
else {
this._value = NaN;
if ('string' === typeof j) {
if (!j.length) {
this._value = NaN;
// XXX Should actually always try and continue if it failed.
} else if (j[0] === "s") {
this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
} else if (j.length === 32) {
this._value = this.parse_hex(j);
// XXX Should also try 1751
} else {
this.parse_passphrase(j);
}
} else {
this._value = NaN;
}
return this;
};
// Convert from internal form.
Seed.prototype.parse_passphrase = function (j) {
if ("string" !== typeof j) {
throw new Error("Passphrase must be a string");
}
var hash = sjcl.hash.sha512.hash(sjcl.codec.utf8String.toBits(j));
var bits = sjcl.bitArray.bitSlice(hash, 0, 128);
this.parse_bits(bits);
return this;
};
Seed.prototype.to_json = function () {
if (!(this._value instanceof BigInteger))
return NaN;
var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; });
var target = 20;
// XXX Make sure only trim off leading zeros.
var array = bytes.length < target
? bytes.length
? [].concat(utils.arraySet(target - bytes.length, 0), bytes)
: utils.arraySet(target, 0)
: bytes.slice(target - bytes.length);
var output = Base.encode_check(Base.VER_FAMILY_SEED, array);
var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
return output;
};
@@ -103,27 +83,34 @@ function SHA256_RIPEMD160(bits) {
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
}
Seed.prototype.generate_private = function (account_id) {
// XXX If account_id is given, should loop over keys until we find the right one
Seed.prototype.get_key = function (account_id) {
if (!this.is_valid()) {
throw new Error("Cannot generate keys from invalid seed!");
}
// XXX Should loop over keys until we find the right one
var curve = this._curve;
var seq = 0;
var private_gen, public_gen, i = 0;
do {
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i)));
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.to_bytes(), i)));
i++;
} while (!sjcl.ecc.curves.c256.r.greaterEquals(private_gen));
} while (!curve.r.greaterEquals(private_gen));
public_gen = sjcl.ecc.curves.c256.G.mult(private_gen);
public_gen = curve.G.mult(private_gen);
var sec;
i = 0;
do {
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
i++;
} while (!sjcl.ecc.curves.c256.r.greaterEquals(sec));
} while (!curve.r.greaterEquals(sec));
return UInt256.from_bn(sec);
sec = sec.add(private_gen).mod(curve.r);
return KeyPair.from_bn_secret(sec);
};
exports.Seed = Seed;

View File

@@ -92,7 +92,7 @@ SerializedObject.prototype.serialize_field = function (spec, obj)
Type = spec.shift();
if ("undefined" !== typeof obj[name]) {
console.log(name, Type.id, field_id);
//console.log(name, Type.id, field_id);
this.append(SerializedObject.get_field_header(Type.id, field_id));
try {

View File

@@ -12,7 +12,8 @@ var extend = require('extend'),
var amount = require('./amount'),
UInt160 = amount.UInt160,
Amount = amount.Amount;
Amount = amount.Amount,
Currency= amount.Currency;
// Shortcuts
var hex = sjcl.codec.hex,
@@ -47,7 +48,7 @@ SerializedType.prototype.serialize_varint = function (so, val) {
} else throw new Error("Variable integer overflow.");
};
exports.Int8 = new SerializedType({
var STInt8 = exports.Int8 = new SerializedType({
serialize: function (so, val) {
so.append([val & 0xff]);
},
@@ -56,7 +57,7 @@ exports.Int8 = new SerializedType({
}
});
exports.Int16 = new SerializedType({
var STInt16 = exports.Int16 = new SerializedType({
serialize: function (so, val) {
so.append([
val >>> 8 & 0xff,
@@ -65,10 +66,11 @@ exports.Int16 = new SerializedType({
},
parse: function (so) {
// XXX
throw new Error("Parsing Int16 not implemented");
}
});
exports.Int32 = new SerializedType({
var STInt32 = exports.Int32 = new SerializedType({
serialize: function (so, val) {
so.append([
val >>> 24 & 0xff,
@@ -79,46 +81,82 @@ exports.Int32 = new SerializedType({
},
parse: function (so) {
// XXX
throw new Error("Parsing Int32 not implemented");
}
});
exports.Int64 = new SerializedType({
var STInt64 = exports.Int64 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Int64 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Int64 not implemented");
}
});
exports.Hash128 = new SerializedType({
var STHash128 = exports.Hash128 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Hash128 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Hash128 not implemented");
}
});
exports.Hash256 = new SerializedType({
var STHash256 = exports.Hash256 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Hash256 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Hash256 not implemented");
}
});
exports.Hash160 = new SerializedType({
var STHash160 = exports.Hash160 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Hash160 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Hash160 not implemented");
}
});
exports.Amount = new SerializedType({
// Internal
var STCurrency = new SerializedType({
serialize: function (so, val) {
var currency = val.to_json();
if ("string" === typeof currency && currency.length === 3) {
var currencyCode = currency.toUpperCase(),
currencyData = utils.arraySet(20, 0);
if (!/^[A-Z]{3}$/.test(currencyCode)) {
throw new Error('Invalid currency code');
}
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
so.append(currencyData);
} else {
throw new Error('Tried to serialize invalid/unimplemented currency type.');
}
},
parse: function (so) {
// XXX
throw new Error("Parsing Currency not implemented");
}
});
var STAmount = exports.Amount = new SerializedType({
serialize: function (so, val) {
var amount = Amount.from_json(val);
if (!amount.is_valid()) {
@@ -126,6 +164,7 @@ exports.Amount = new SerializedType({
}
// Amount (64-bit integer)
var valueBytes = utils.arraySet(8, 0);
if (amount.is_native()) {
var valueHex = amount._value.toString(16);
@@ -137,103 +176,138 @@ exports.Amount = new SerializedType({
valueHex = "0" + valueHex;
}
var valueBytes = bytes.fromBits(hex.toBits(valueHex));
valueBytes = bytes.fromBits(hex.toBits(valueHex));
// Clear most significant two bits - these bits should already be 0 if
// Amount enforces the range correctly, but we'll clear them anyway just
// so this code can make certain guarantees about the encoded value.
valueBytes[0] &= 0x3f;
if (!amount.is_negative()) valueBytes[0] |= 0x40;
so.append(valueBytes);
} else {
// XXX
throw new Error("Non-native amounts not implemented!");
var hi = 0, lo = 0;
// First bit: non-native
hi |= 1 << 31;
if (!amount.is_zero()) {
// Second bit: non-negative?
if (!amount.is_negative()) hi |= 1 << 30;
// Next eight bits: offset/exponent
hi |= ((97 + amount._offset) & 0xff) << 22;
// Remaining 52 bits: mantissa
hi |= amount._value.shiftRight(32).intValue() & 0x3fffff;
lo = amount._value.intValue() & 0xffffffff;
}
valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
}
so.append(valueBytes);
if (!amount.is_native()) {
// Currency (160-bit hash)
var currency = amount.currency().to_json();
if ("string" === typeof currency && currency.length === 3) {
var currencyCode = currency.toUpperCase(),
currencyData = utils.arraySet(20, 0);
if (!/^[A-Z]{3}$/.test(currencyCode)) {
throw new Error('Invalid currency code');
}
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
var currencyBits = bytes.toBits(currencyData),
currencyHash = sjcl.hash.ripemd160.hash(currencyBits);
so.append(bytes.fromBits(currencyHash));
} else {
throw new Error('Tried to serialize invalid/unimplemented currency type.');
}
var currency = amount.currency();
STCurrency.serialize(so, currency);
// Issuer (160-bit hash)
// XXX
so.append(amount.issuer().to_bytes());
}
},
parse: function (so) {
// XXX
throw new Error("Parsing Amount not implemented");
}
});
exports.VariableLength = new SerializedType({
var STVL = exports.VariableLength = new SerializedType({
serialize: function (so, val) {
if ("string" === typeof val) this.serialize_hex(so, val);
else throw new Error("Unknown datatype.");
},
parse: function (so) {
// XXX
throw new Error("Parsing VL not implemented");
}
});
exports.Account = new SerializedType({
var STAccount = exports.Account = new SerializedType({
serialize: function (so, val) {
var account = UInt160.from_json(val);
this.serialize_hex(so, account.to_hex());
},
parse: function (so) {
// XXX
throw new Error("Parsing Account not implemented");
}
});
exports.PathSet = new SerializedType({
var STPathSet = exports.PathSet = new SerializedType({
serialize: function (so, val) {
// XXX
for (var i = 0, l = val.length; i < l; i++) {
for (var j = 0, l2 = val[i].length; j < l2; j++) {
var entry = val[i][j];
var type = 0;
if (entry.account) type |= 0x01;
if (entry.currency) type |= 0x10;
if (entry.issuer) type |= 0x20;
STInt8.serialize(so, type);
if (entry.account) {
so.append(UInt160.from_json(entry.account).to_bytes());
}
if (entry.currency) {
var currency = Currency.from_json(entry.currency);
STCurrency.serialize(so, currency);
}
if (entry.issuer) {
so.append(UInt160.from_json(entry.issuer).to_bytes());
}
}
if (j < l2) STInt8.serialize(so, 0xff);
}
STInt8.serialize(so, 0x00);
},
parse: function (so) {
// XXX
throw new Error("Parsing PathSet not implemented");
}
});
exports.Vector256 = new SerializedType({
var STVector256 = exports.Vector256 = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Vector256 not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Vector256 not implemented");
}
});
exports.Object = new SerializedType({
var STObject = exports.Object = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Object not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Object not implemented");
}
});
exports.Array = new SerializedType({
var STArray = exports.Array = new SerializedType({
serialize: function (so, val) {
// XXX
throw new Error("Serializing Array not implemented");
},
parse: function (so) {
// XXX
throw new Error("Parsing Array not implemented");
}
});

View File

@@ -5,12 +5,18 @@ sjcl.ecc.ecdsa.secretKey.prototype.signDER = function(hash, paranoia) {
sjcl.ecc.ecdsa.secretKey.prototype.encodeDER = function(rs) {
var w = sjcl.bitArray,
R = this._curve.r,
l = R.bitLength(),
r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)).toBits(),
s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)).toBits();
l = R.bitLength();
var rb = sjcl.codec.bytes.fromBits(r),
sb = sjcl.codec.bytes.fromBits(s);
var rb = sjcl.codec.bytes.fromBits(w.bitSlice(rs,0,l)),
sb = sjcl.codec.bytes.fromBits(w.bitSlice(rs,l,2*l));
// Drop empty leading bytes
while (!rb[0] && rb.length) rb.shift();
while (!sb[0] && sb.length) sb.shift();
// If high bit is set, prepend an extra zero byte (DER signed integer)
if (rb[0] & 0x80) rb.unshift(0);
if (sb[0] & 0x80) sb.unshift(0);
var buffer = [].concat(
0x30,

View File

@@ -9,7 +9,7 @@ sjcl.bn.prototype.divRem = function (that) {
this.initWith(0);
return this;
} else if (thisa.equals(thata)) {
this.initWith(sign);
this.initWith(1);
return this;
}

View File

@@ -0,0 +1,30 @@
sjcl.ecc.ecdsa.secretKey.prototype = {
sign: function(hash, paranoia) {
var R = this._curve.r,
l = R.bitLength(),
k = sjcl.bn.random(R.sub(1), paranoia).add(1),
r = this._curve.G.mult(k).x.mod(R),
s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)).mul(k.inverseMod(R)).mod(R);
return sjcl.bitArray.concat(r.toBits(l), s.toBits(l));
}
};
sjcl.ecc.ecdsa.publicKey.prototype = {
verify: function(hash, rs) {
var w = sjcl.bitArray,
R = this._curve.r,
l = R.bitLength(),
r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)),
s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)),
sInv = s.modInverse(R),
hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R),
hA = r.mul(sInv).mod(R),
r2 = this._curve.G.mult2(hG, hA, this._point).x;
if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) || !r2.equals(r)) {
throw (new sjcl.exception.corrupt("signature didn't check out"));
}
return true;
}
};

View File

@@ -74,6 +74,7 @@ var Transaction = function (remote) {
this.hash = undefined;
this.submit_index = undefined; // ledger_current_index was this when transaction was submited.
this.state = undefined; // Under construction.
this.finalized = false;
this.on('success', function (message) {
if (message.engine_result) {
@@ -167,6 +168,26 @@ Transaction.prototype.set_state = function (state) {
}
};
/**
* Attempts to complete the transaction for submission.
*
* This function seeks to fill out certain fields, such as Fee and
* SigningPubKey, which can be determined by the library based on network
* information and other fields.
*/
Transaction.prototype.complete = function () {
var tx_json = this.tx_json;
if (this.remote.local_fee && undefined === tx_json.Fee) {
tx_json.Fee = Transaction.fees['default'].to_json();
}
if (this.remote.local_signing && undefined === tx_json.SigningPubKey) {
var seed = Seed.from_json(this._secret);
var key = seed.get_key(this.tx_json.Account);
tx_json.SigningPubKey = key.to_hex_pub();
}
};
Transaction.prototype.serialize = function () {
return SerializedObject.from_json(this.tx_json);
};
@@ -181,11 +202,10 @@ Transaction.prototype.signing_hash = function () {
Transaction.prototype.sign = function () {
var seed = Seed.from_json(this._secret),
priv = seed.generate_private(this.tx_json.Account),
hash = this.signing_hash();
var key = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], priv.to_bn()),
sig = key.signDER(hash.to_bits(), 0),
var key = seed.get_key(this.tx_json.Account),
sig = key.sign(hash, 0),
hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
this.tx_json.TxnSignature = hex;
@@ -199,6 +219,7 @@ Transaction.prototype.sign = function () {
// // 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
@@ -216,14 +237,12 @@ Transaction.prototype.submit = function (callback) {
'error' : 'tejInvalidAccount',
'error_message' : 'Bad account.'
});
return;
return this;
}
// YYY Might check paths for invalid accounts.
if (this.remote.local_fee && undefined === tx_json.Fee) {
tx_json.Fee = Transaction.fees['default'].to_json();
}
this.complete();
if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) {
// There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them.
@@ -240,15 +259,19 @@ Transaction.prototype.submit = function (callback) {
self.remote.request_transaction_entry(self.hash)
.ledger_hash(ledger_hash)
.on('success', function (message) {
if (self.finalized) return;
self.set_state(message.metadata.TransactionResult);
self.remote.removeListener('ledger_closed', on_ledger_closed);
self.emit('final', message);
self.finalized = true;
if (self.callback)
self.callback(message.metadata.TransactionResult, message);
stop = true;
})
.on('error', function (message) {
if (self.finalized) return;
if ('remoteError' === message.error
&& 'transactionNotFound' === message.remote.error) {
if (self.submit_index + SUBMIT_LOST < ledger_index) {
@@ -258,7 +281,9 @@ Transaction.prototype.submit = function (callback) {
if (self.callback)
self.callback('tejLost', message);
stop = true;
self.remote.removeListener('ledger_closed', on_ledger_closed);
self.emit('final', message);
self.finalized = true;
}
else if (self.submit_index + SUBMIT_MISSING < ledger_index) {
self.set_state('client_missing'); // We don't know what happened to transaction, still might find.
@@ -271,11 +296,6 @@ Transaction.prototype.submit = function (callback) {
// XXX Could log other unexpectedness.
})
.request();
if (stop) {
self.remote.removeListener('ledger_closed', on_ledger_closed);
self.emit('final', message);
}
};
this.remote.on('ledger_closed', on_ledger_closed);
@@ -289,7 +309,100 @@ Transaction.prototype.submit = function (callback) {
this.set_state('client_submitted');
this.remote.submit(this);
if (self.remote.local_sequence && !self.tx_json.Sequence) {
self.tx_json.Sequence = this.remote.account_seq(self.tx_json.Account, 'ADVANCE');
// console.log("Sequence: %s", self.tx_json.Sequence);
if (!self.tx_json.Sequence) {
// Look in the last closed ledger.
this.remote.account_seq_cache(self.tx_json.Account, false)
.on('success_account_seq_cache', function () {
// Try again.
self.submit();
})
.on('error_account_seq_cache', function (message) {
// XXX Maybe be smarter about this. Don't want to trust an untrusted server for this seq number.
// Look in the current ledger.
self.remote.account_seq_cache(self.tx_json.Account, 'CURRENT')
.on('success_account_seq_cache', function () {
// Try again.
self.submit();
})
.on('error_account_seq_cache', function (message) {
// Forward errors.
self.emit('error', message);
})
.request();
})
.request();
return this;
}
// If the transaction fails we want to either undo incrementing the sequence
// or submit a noop transaction to consume the sequence remotely.
this.on('success', function (res) {
if (!res || "string" !== typeof res.engine_result) return;
switch (res.engine_result.slice(0, 3)) {
// Synchronous local error
case 'tej':
self.remote.account_seq(self.tx_json.Account, 'REWIND');
break;
// XXX: What do we do in case of ter?
case 'tel':
case 'tem':
case 'tef':
// XXX Once we have a transaction submission manager class, we can
// check if there are any other transactions pending. If there are,
// we should submit a dummy transaction to ensure those
// transactions are still valid.
//var noop = self.remote.transaction().account_set(self.tx_json.Account);
//noop.submit();
// XXX Hotfix. This only works if no other transactions are pending.
self.remote.account_seq(self.tx_json.Account, 'REWIND');
break;
}
});
}
// Prepare request
var request = this.remote.request_submit();
// Forward successes and errors.
request.on('success', function (message) {
self.emit('success', message);
});
request.on('error', function (message) {
self.emit('error', message);
});
if (!this._secret && !this.tx_json.Signature) {
this.emit('error', {
'result' : 'tejSecretUnknown',
'result_message' : "Could not sign transactions because we."
});
return this;
} else if (this.remote.local_signing) {
this.sign();
request.tx_blob(this.serialize().to_hex());
} else {
if (!this.remote.trusted) {
this.emit('error', {
'result' : 'tejServerUntrusted',
'result_message' : "Attempt to give a secret to an untrusted server."
});
return this;
}
request.secret(this._secret);
request.build_path(this._build_path);
request.tx_json(this.tx_json);
}
request.request();
return this;
}
@@ -319,8 +432,8 @@ Transaction.prototype.destination_tag = function (tag) {
Transaction._path_rewrite = function (path) {
var path_new = [];
for (var index in path) {
var node = path[index];
for (var i = 0, l = path.length; i < l; i++) {
var node = path[i];
var node_new = {};
if ('account' in node)
@@ -339,7 +452,7 @@ Transaction._path_rewrite = function (path) {
}
Transaction.prototype.path_add = function (path) {
this.tx_json.Paths = this.tx_json.Paths || []
this.tx_json.Paths = this.tx_json.Paths || [];
this.tx_json.Paths.push(Transaction._path_rewrite(path));
return this;
@@ -348,8 +461,8 @@ Transaction.prototype.path_add = function (path) {
// --> paths: undefined or array of path
// A path is an array of objects containing some combination of: account, currency, issuer
Transaction.prototype.paths = function (paths) {
for (var index in paths) {
this.path_add(paths[index]);
for (var i = 0, l = paths.length; i < l; i++) {
this.path_add(paths[i]);
}
return this;

View File

@@ -20,8 +20,8 @@ var UInt = function () {
this._value = NaN;
};
UInt.json_rewrite = function (j) {
return this.from_json(j).to_json();
UInt.json_rewrite = function (j, opts) {
return this.from_json(j).to_json(opts);
};
// Return a new UInt from j.
@@ -92,6 +92,10 @@ UInt.prototype.is_valid = function () {
return this._value instanceof BigInteger;
};
UInt.prototype.is_zero = function () {
return this._value.equals(BigInteger.ZERO);
};
// value = NaN on error.
UInt.prototype.parse_generic = function (j) {
// Canonicalize and validate
@@ -102,14 +106,14 @@ UInt.prototype.parse_generic = function (j) {
case undefined:
case "0":
case this.constructor.STR_ZERO:
case this.constructor.ADDRESS_ZERO:
case this.constructor.ACCOUNT_ZERO:
case this.constructor.HEX_ZERO:
this._value = nbi();
break;
case "1":
case this.constructor.STR_ONE:
case this.constructor.ADDRESS_ONE:
case this.constructor.ACCOUNT_ONE:
case this.constructor.HEX_ONE:
this._value = new BigInteger([1]);

View File

@@ -24,8 +24,8 @@ UInt160.width = 20;
UInt160.prototype = extend({}, UInt.prototype);
UInt160.prototype.constructor = UInt160;
var ADDRESS_ZERO = UInt160.ADDRESS_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp";
var ADDRESS_ONE = UInt160.ADDRESS_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji";
var ACCOUNT_ZERO = UInt160.ACCOUNT_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp";
var ACCOUNT_ONE = UInt160.ACCOUNT_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji";
var HEX_ZERO = UInt160.HEX_ZERO = "0000000000000000000000000000000000000000";
var HEX_ONE = UInt160.HEX_ONE = "0000000000000000000000000000000000000001";
var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
@@ -59,8 +59,8 @@ UInt160.prototype.to_json = function (opts) {
var output = Base.encode_check(Base.VER_ACCOUNT_ID, this.to_bytes());
if (config.gateways && output in config.gateways && !opts.no_gateway)
output = config.gateways[output];
if (opts.gateways && output in opts.gateways)
output = opts.gateways[output];
return output;
};

View File

@@ -24,9 +24,6 @@ UInt256.width = 32;
UInt256.prototype = extend({}, UInt.prototype);
UInt256.prototype.constructor = UInt256;
// XXX Generate these constants (or remove them)
var ADDRESS_ZERO = UInt256.ADDRESS_ZERO = "XXX";
var ADDRESS_ONE = UInt256.ADDRESS_ONE = "XXX";
var HEX_ZERO = UInt256.HEX_ZERO = "00000000000000000000000000000000" +
"00000000000000000000000000000000";
var HEX_ONE = UInt256.HEX_ONE = "00000000000000000000000000000000" +