diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index e8f4a1a6..88da4027 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -15,6 +15,7 @@ var sjcl = utils.sjcl; var UInt128 = require('./uint128').UInt128; var UInt160 = require('./uint160').UInt160; var UInt256 = require('./uint256').UInt256; +var Base = require('./base').Base; var amount = require('./amount'); var Amount = amount.Amount; @@ -381,6 +382,7 @@ var STAmount = exports.Amount = new SerializedType({ var currency = STCurrency.parse(so); var issuer_bytes = so.read(20); var issuer = UInt160.from_bytes(issuer_bytes); + issuer.set_version(Base.VER_ACCOUNT_ID); var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97; var mantissa_bytes = value_bytes.slice(1); mantissa_bytes[0] &= 0x3f; @@ -441,6 +443,7 @@ var STAccount = exports.Account = new SerializedType({ } var result = UInt160.from_bytes(so.read(len)); + result.set_version(Base.VER_ACCOUNT_ID); //console.log('PARSED 160:', result.to_json()); if (false && !result.is_valid()) { @@ -482,7 +485,7 @@ var STPathSet = exports.PathSet = new SerializedType({ if (entry.currency) { var currency = Currency.from_json(entry.currency); - STCurrency.serialize(so, currency); + STCurrency.serialize(so, currency, entry.non_native); } if (entry.issuer) { @@ -529,14 +532,20 @@ var STPathSet = exports.PathSet = new SerializedType({ /*var bta = so.read(20); console.log('BTA:', bta);*/ entry.account = STHash160.parse(so); + entry.account.set_version(Base.VER_ACCOUNT_ID); } if (tag_byte & this.typeCurrency) { //console.log('entry.currency'); - entry.currency = STCurrency.parse(so) + entry.currency = STCurrency.parse(so); + if (entry.currency.to_json() === "XRP" && + !entry.currency.is_native()) { + entry.non_native = true; + } } if (tag_byte & this.typeIssuer) { //console.log('entry.issuer'); entry.issuer = STHash160.parse(so); //should know to use Base58? + entry.issuer.set_version(Base.VER_ACCOUNT_ID); //console.log('DONE WITH ISSUER!'); } @@ -625,14 +634,14 @@ function parse(so) { var type = TYPES_MAP[type_bits]; - assert(type, 'Unknown type: ' + type_bits); + assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16)); var field_bits = tag_byte & 0x0f; var field_name = (field_bits === 0) ? field_name = FIELDS_MAP[type_bits][so.read(1)[0]] : field_name = FIELDS_MAP[type_bits][field_bits]; - assert(field_name, 'Unknown field: ' + tag_byte); + assert(field_name, 'Unknown field - header byte is 0x' + tag_byte.toString(16)); return [ field_name, type.parse(so) ]; //key, value }; diff --git a/src/js/ripple/transactionmanager.js b/src/js/ripple/transactionmanager.js index 41ded6ad..e3eb7637 100644 --- a/src/js/ripple/transactionmanager.js +++ b/src/js/ripple/transactionmanager.js @@ -28,8 +28,8 @@ function TransactionManager(account) { // transaction sequence number this._load_sequence(); - function cache_transaction(transaction) { - var transaction = TransactionManager.normalize_transaction(transaction); + function cache_transaction(res) { + var transaction = TransactionManager.normalize_transaction(res); var sequence = transaction.tx_json.Sequence; var hash = transaction.hash; @@ -40,7 +40,7 @@ function TransactionManager(account) { self.remote._trace('transactionmanager: transaction_received: %s', transaction.tx_json); if (pending) { - pending.emit('success', transaction); + pending.emit('success', res); } else { self._cache[hash] = transaction; } diff --git a/src/js/ripple/uint.js b/src/js/ripple/uint.js index c9ccb6c2..486b0fc9 100644 --- a/src/js/ripple/uint.js +++ b/src/js/ripple/uint.js @@ -129,9 +129,6 @@ UInt.prototype.parse_generic = function (j) { if ('string' !== typeof j) { this._value = NaN; } - else if (j[0] === "r") { - this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); - } else if (this.constructor.width === j.length) { this._value = new BigInteger(utils.stringToArray(j), 256); } diff --git a/src/js/ripple/uint160.js b/src/js/ripple/uint160.js index 22fe0c4a..8b405959 100644 --- a/src/js/ripple/uint160.js +++ b/src/js/ripple/uint160.js @@ -15,6 +15,7 @@ var Base = require('./base').Base; var UInt160 = extend(function () { // Internal form: NaN or BigInteger this._value = NaN; + this._version_byte = void(0); }, UInt); UInt160.width = 20; @@ -28,6 +29,16 @@ var HEX_ONE = UInt160.HEX_ONE = '0000000000000000000000000000000000000 var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO); var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE); +UInt160.prototype.set_version = function (j) { + this._version_byte = j; + + return this; +}; + +UInt160.prototype.get_version = function () { + return this._version_byte; +}; + // value = NaN on error. UInt160.prototype.parse_json = function (j) { // Canonicalize and validate @@ -36,13 +47,30 @@ UInt160.prototype.parse_json = function (j) { } if (typeof j === 'number' && !isNaN(j)) { - this._value = new BigInteger(String(j)); + // Allow raw numbers - DEPRECATED + // This is used mostly by the test suite and is supported + // as a legacy feature only. DO NOT RELY ON THIS BEHAVIOR. + this._value = new BigInteger(String(j)); + this._version_byte = Base.VER_ACCOUNT_ID; } else if (typeof j !== 'string') { this._value = NaN; } else if (j[0] === 'r') { - this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + this._version_byte = Base.VER_ACCOUNT_ID; } else { - this._value = NaN; + this.parse_hex(j); + } + + return this; +}; + +UInt160.prototype.parse_generic = function (j) { + UInt.prototype.parse_generic.call(this, j); + + if (isNaN(this._value)) { + if ("string" === typeof j && j[0] === 'r') { + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + } } return this; @@ -50,17 +78,22 @@ UInt160.prototype.parse_json = function (j) { // XXX Json form should allow 0 and 1, C++ doesn't currently allow it. UInt160.prototype.to_json = function (opts) { - var opts = opts || {}; - var output = NaN; + opts = opts || {}; if (this._value instanceof BigInteger) { - output = Base.encode_check(Base.VER_ACCOUNT_ID, this.to_bytes()); - if (opts.gateways && output in opts.gateways) { - output = opts.gateways[output]; + // If this value has a type, return a Base58 encoded string. + if ("number" === typeof this._version_byte) { + var output = Base.encode_check(this._version_byte, this.to_bytes()); + if (opts.gateways && output in opts.gateways) { + output = opts.gateways[output]; + } + + return output; + } else { + return this.to_hex(); } } - - return output; + return NaN; }; exports.UInt160 = UInt160; diff --git a/test/amount-test.js b/test/amount-test.js index 58df7ff6..51c40282 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -36,7 +36,7 @@ describe('Amount', function() { assert.deepEqual(new BigInteger(), UInt160.from_generic('0')._value); }); it('Parse 0 export', function () { - assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_generic('0').to_json()); + assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_generic('0').set_version(0).to_json()); }); it('Parse 1', function () { assert.deepEqual(new BigInteger([1]), UInt160.from_generic('1')._value); diff --git a/test/serializedobject-test.js b/test/serializedobject-test.js index 09cf3a26..21c8a2ed 100644 --- a/test/serializedobject-test.js +++ b/test/serializedobject-test.js @@ -2,7 +2,7 @@ var utils = require('./testutils'); var assert = require('assert'); var SerializedObject = utils.load_module('serializedobject').SerializedObject; -describe('Serialied object', function() { +describe('Serialized object', function() { describe('Serialized object', function() { it('From json and back', function() { var input_json = { @@ -18,7 +18,7 @@ describe('Serialied object', function() { issuer: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV' }, { - currency:'XRP' + currency: 'XRP' } ]], SendMax: { diff --git a/test/serializedtypes-test.js b/test/serializedtypes-test.js index 48af1ea3..4f0551b9 100644 --- a/test/serializedtypes-test.js +++ b/test/serializedtypes-test.js @@ -378,19 +378,37 @@ describe('Serialized types', function() { describe('Hash160', function() { it('Serialize 0', function () { + var hex = '0000000000000000000000000000000000000000'; + var base58 = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'; var so = new SerializedObject(); - types.Hash160.serialize(so, 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'); - assert.strictEqual(so.to_hex(), '0000000000000000000000000000000000000000'); + types.Hash160.serialize(so, base58); + assert.strictEqual(so.to_hex(), hex); + + so = new SerializedObject(); + types.Hash160.serialize(so, hex); + assert.strictEqual(so.to_hex(), hex); }); it('Serialize 1', function () { + var hex = '0000000000000000000000000000000000000001'; + var base58 = 'rrrrrrrrrrrrrrrrrrrrBZbvji'; var so = new SerializedObject(); - types.Hash160.serialize(so, 'rrrrrrrrrrrrrrrrrrrrBZbvji'); - assert.strictEqual(so.to_hex(), '0000000000000000000000000000000000000001'); + types.Hash160.serialize(so, base58); + assert.strictEqual(so.to_hex(), hex); + + so = new SerializedObject(); + types.Hash160.serialize(so, hex); + assert.strictEqual(so.to_hex(), hex); }); it('Serialize FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', function () { + var hex = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; + var base58 = 'rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi'; var so = new SerializedObject(); - types.Hash160.serialize(so, 'rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi'); - assert.strictEqual(so.to_hex(), 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); + types.Hash160.serialize(so, base58); + assert.strictEqual(so.to_hex(), hex); + + so = new SerializedObject(); + types.Hash160.serialize(so, hex); + assert.strictEqual(so.to_hex(), hex); }); it('Parse 0', function () { var val = '0000000000000000000000000000000000000000'; @@ -410,6 +428,14 @@ describe('Serialized types', function() { var num = types.Hash160.parse(so); assert.strictEqual(num.to_hex(), val); }); + it('Parse 0 as JSON', function () { + // Hash160 should be returned as hex in JSON, unlike + // addresses. + var val = '0000000000000000000000000000000000000000'; + var so = new SerializedObject(val); + var num = types.Hash160.parse(so); + assert.strictEqual(num.to_json(), val); + }); }); describe('Hash256', function() { @@ -539,6 +565,60 @@ describe('Serialized types', function() { }); }); + describe('Account', function() { + it('Serialize 0', function () { + var hex = '0000000000000000000000000000000000000000'; + var base58 = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'; + var so = new SerializedObject(); + types.Account.serialize(so, base58); + assert.strictEqual(so.to_hex(), "14"+hex); + + so = new SerializedObject(); + types.Account.serialize(so, hex); + assert.strictEqual(so.to_hex(), "14"+hex); + }); + it('Serialize 1', function () { + var hex = '0000000000000000000000000000000000000001'; + var base58 = 'rrrrrrrrrrrrrrrrrrrrBZbvji'; + var so = new SerializedObject(); + types.Account.serialize(so, base58); + assert.strictEqual(so.to_hex(), "14"+hex); + + so = new SerializedObject(); + types.Account.serialize(so, hex); + assert.strictEqual(so.to_hex(), "14"+hex); + }); + it('Serialize FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', function () { + var hex = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; + var base58 = 'rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi'; + var so = new SerializedObject(); + types.Account.serialize(so, base58); + assert.strictEqual(so.to_hex(), "14"+hex); + + so = new SerializedObject(); + types.Account.serialize(so, hex); + assert.strictEqual(so.to_hex(), "14"+hex); + }); + it('Parse 0', function () { + var val = '140000000000000000000000000000000000000000'; + var so = new SerializedObject(val); + var num = types.Account.parse(so); + assert.strictEqual(num.to_json(), 'rrrrrrrrrrrrrrrrrrrrrhoLvTp'); + }); + it('Parse 1', function () { + var val = '140000000000000000000000000000000000000001'; + var so = new SerializedObject(val); + var num = types.Account.parse(so); + assert.strictEqual(num.to_json(), 'rrrrrrrrrrrrrrrrrrrrBZbvji'); + }); + it('Parse HASH160_MAX', function () { + var val = '14FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; + var so = new SerializedObject(val); + var num = types.Account.parse(so); + assert.strictEqual(num.to_json(), 'rQLbzfJH5BT1FS9apRLKV3G8dWEA5njaQi'); + }); + }); + describe('PathSet', function() { it('Serialize single empty path [[]]', function () { var so = new SerializedObject(); @@ -564,24 +644,114 @@ describe('Serialized types', function() { }]]); assert.strictEqual(so.to_hex(), '31000000000000000000000000000000000000007B00000000000000000000000055534400000000000000000000000000000000000000000000000315FF31000000000000000000000000000000000000007B000000000000000000000000425443000000000000000000000000000000000000000000000003153100000000000000000000000000000000000003DB0000000000000000000000004555520000000000000000000000000000000000000000000000014100'); //TODO: Check this independently }); + it('Serialize path through XRP', function () { + var hex = '31000000000000000000000000000000000000007B00000000000000000000000055534400000000000000000000000000000000000000000000000315FF1000000000000000000000000000000000000000003100000000000000000000000000000000000003DB0000000000000000000000004555520000000000000000000000000000000000000000000000014100'; + var json = [ + [ { + account: "rrrrrrrrrrrrrrrrrrrrNxV3Xza", + currency: 'USD', + issuer: "rrrrrrrrrrrrrrrrrrrpYnYCNYf" + }], + [{ + currency: "XRP" + }, { + account: "rrrrrrrrrrrrrrrrrrrpvQsW3V3", + currency: 'EUR', + issuer: "rrrrrrrrrrrrrrrrrrrdHRtqg2" + }] + ]; + + var so = new SerializedObject(); + types.PathSet.serialize(so, json); + assert.strictEqual(so.to_hex(), hex); + + so = new SerializedObject(hex); + var parsed_path = SerializedObject.jsonify_structure(types.PathSet.parse(so)); + assert.deepEqual(parsed_path, json); + }); + it('Serialize path through XRP IOUs', function () { + // Appears in the history + // TX #0CBB429C456ED999CC691DFCC8E62E8C8C7E9522C2BEA967FED0D7E2A9B28D13 + // Note that XRP IOUs are no longer allowed, so this functionality is + // for historic transactions only. + + var hex = '31585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D694C8531CDEECBE84F33670000000000000000000000004254430000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367310A20B3C85F482532A9578DBB3950B85CA06594D100000000000000000000000042544300000000000A20B3C85F482532A9578DBB3950B85CA06594D13000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D694C8531CDEECBE84F33670000000000000000000000004254430000000000E4FE687C90257D3D2D694C8531CDEECBE84F33673115036E2D3F5437A83E5AC3CAEE34FF2C21DEB618000000000000000000000000425443000000000015036E2D3F5437A83E5AC3CAEE34FF2C21DEB6183000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C13157180C769B66D942EE69E6DCC940CA48D82337AD000000000000000000000000425443000000000057180C769B66D942EE69E6DCC940CA48D82337AD1000000000000000000000000058525000000000003000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D100'; + var json = [ + [{ + "account": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", + "currency": "BTC", + "issuer": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K" + }, { + "account": "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo", + "currency": "BTC", + "issuer": "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo" + }, { + "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", + "currency": "BTC", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + }, { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + }], + [{ + "account": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", + "currency": "BTC", + "issuer": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K" + }, { + "account": "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo", + "currency": "BTC", + "issuer": "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo" + }, { + "account": "rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi", + "currency": "BTC", + "issuer": "rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi" + }, { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + }], + [{ + "account": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", + "currency": "BTC", + "issuer": "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K" + }, { + "account": "r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn", + "currency": "BTC", + "issuer": "r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn" + }, { + "currency": "XRP", + "non_native": true + }, { + "currency": "USD", + "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" + }] + ]; + + var so = new SerializedObject(); + types.PathSet.serialize(so, json); + assert.strictEqual(so.to_hex(), hex); + + so = new SerializedObject(hex); + var parsed_path = SerializedObject.jsonify_structure(types.PathSet.parse(so)); + assert.deepEqual(parsed_path, json); + }); it('Parse single empty path [[]]', function () { var so = new SerializedObject('00'); - var parsed_path = types.PathSet.parse(so) + var parsed_path = SerializedObject.jsonify_structure(types.PathSet.parse(so)); assert.deepEqual(parsed_path, [[]]); }); it('Parse [[e],[e,e]]', function () { var so = new SerializedObject('31000000000000000000000000000000000000007B00000000000000000000000055534400000000000000000000000000000000000000000000000315FF31000000000000000000000000000000000000007B000000000000000000000000425443000000000000000000000000000000000000000000000003153100000000000000000000000000000000000003DB0000000000000000000000004555520000000000000000000000000000000000000000000000014100'); var parsed_path = types.PathSet.parse(so); - var comp =[ [ { account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', - currency: 'USD', - issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf' } ], - [ { account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', - currency: 'BTC', - issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf' }, - { account: 'rrrrrrrrrrrrrrrrrrrpvQsW3V3', - currency: 'EUR', - issuer: 'rrrrrrrrrrrrrrrrrrrdHRtqg2' } ] ]; + var comp = [ [ { account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', + currency: 'USD', + issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf' } ], + [ { account: 'rrrrrrrrrrrrrrrrrrrrNxV3Xza', + currency: 'BTC', + issuer: 'rrrrrrrrrrrrrrrrrrrpYnYCNYf' }, + { account: 'rrrrrrrrrrrrrrrrrrrpvQsW3V3', + currency: 'EUR', + issuer: 'rrrrrrrrrrrrrrrrrrrdHRtqg2' } ] ]; assert.deepEqual(SerializedObject.jsonify_structure(parsed_path, ""), comp); }); });