Compare commits

...

45 Commits

Author SHA1 Message Date
Alan Cohen
3df64091dd Bump version to 0.13.0-rc15 2015-10-19 08:51:44 -07:00
Chris Clark
de5d9335d1 Merge pull request #598 from clark800/remove-currency
Remove Currency class
2015-10-16 11:51:20 -07:00
Chris Clark
88a65f08d8 Merge pull request #596 from darkdarkdragon/develop-RLJS-520
getBalances fixes:
2015-10-16 10:54:16 -07:00
Chris Clark
837f7e6e9b Remove Currency class 2015-10-16 10:48:09 -07:00
Ivan Tivonenko
4faa857330 getBalances fixes:
obey limit, do not return XRP if currency or issuer specified
2015-10-16 20:46:52 +03:00
Alan Cohen
9c7b0cb889 Merge '0.13.0-rc11.1' into develop
Bump version to 0.13.0-rc11.1
  Add proxy support to schema
2015-10-15 18:28:27 -07:00
Alan Cohen
a11abcc016 Bump version to 0.13.0-rc11.1 2015-10-15 18:22:20 -07:00
Alan Cohen
dd693fdc5f Add proxy support to schema 2015-10-15 18:14:22 -07:00
Chris Clark
5ac2576fcf Merge pull request #597 from clark800/remove-ieee754
Remove demurrage
2015-10-15 12:18:21 -07:00
Chris Clark
512817a2db Remove demurrage 2015-10-15 11:56:39 -07:00
Chris Clark
1f8c8d88fa Merge pull request #595 from clark800/ripple-hashes
Use ripple-hashes
2015-10-15 10:51:06 -07:00
Chris Clark
044ed53935 Merge pull request #582 from darkdarkdragon/develop-flow-annotate-4
add more flow annotations to RippleApi
2015-10-15 10:32:32 -07:00
Ivan Tivonenko
d47bb2749a add more flow annotations to RippleApi 2015-10-15 03:51:45 +03:00
Chris Clark
0dc000839b Use ripple-hashes 2015-10-14 15:48:40 -07:00
Chris Clark
462e440d5b Merge pull request #594 from clark800/hex-sign
Update ripple-keypairs
2015-10-14 14:12:23 -07:00
Chris Clark
1891fe0afd Update ripple-keypairs 2015-10-14 13:24:13 -07:00
Geert Weening
8cec60c4b0 Merge pull request #592 from clark800/remote-serial
Delete serialization code
2015-10-14 11:28:11 -07:00
Chris Clark
7419244b39 Delete serialization code 2015-10-13 18:24:21 -07:00
Chris Clark
eb9a48d2d6 Merge pull request #589 from wltsmrz/multisign-update
Fix Transaction.complete() for multisigned transactions
2015-10-13 16:18:01 -07:00
Chris Clark
e44d36b4af Merge pull request #591 from clark800/ledger-hashes
Decouple ledger.js and serialization code
2015-10-13 16:15:40 -07:00
Chris Clark
9a5d05f198 Decouple ledger.js and serialization code 2015-10-13 14:51:04 -07:00
Chris Clark
d7a20a5d53 Merge pull request #588 from darkdarkdragon/develop-broadcast-fix
fix Request.broadcast logic
2015-10-13 14:06:31 -07:00
Ivan Tivonenko
b56680e24e fix Request.broadcast logic
Add default timeout to Request.broadcas
Add default timeout to Remote.createPathFind
Handle 'slowDown' error in many places
2015-10-13 22:40:02 +03:00
Chris Clark
886e80ff6d Merge pull request #590 from FrRichard/patch-4
Update REFERENCE.md
2015-10-13 10:51:57 -07:00
FrRichard
142187b024 Update REFERENCE.md
Changed the way you are setting the request callback.
Previous documentation didn't work, ie, the callback was never called (probably old way to set the callback).
2015-10-10 19:52:55 +02:00
wltsmrz
72f3237aba Fix Transaction.complete() for multisigned transactions 2015-10-09 10:50:54 -07:00
Chris Clark
a2406ac163 Merge pull request #587 from clark800/serialize-quality
Use ripple-binary-codec
2015-10-08 15:51:15 -07:00
Chris Clark
91a64137fe BREAKING CHANGE: Use ripple-binary-codec 2015-10-08 15:48:23 -07:00
Geert Weening
57ecbc58f8 bump version to 0.13.0-rc14 2015-10-07 11:42:08 -07:00
Geert Weening
ea4d1007b8 Merge branch 'release' into develop 2015-10-07 11:17:36 -07:00
Geert Weening
16bc7b986b Bump version to 0.13.0-rc13 2015-10-07 11:14:39 -07:00
Geert Weening
115f95fa96 update release notes 2015-10-07 11:14:01 -07:00
Chris Clark
b77b76ebb5 Merge pull request #586 from darkdarkdragon/develop-log-fix
get back lost argument in debug logger
2015-10-07 10:20:01 -07:00
Ivan Tivonenko
f516298a84 get back lost argument in debug logger 2015-10-07 11:43:17 +03:00
Chris Clark
edb31a0c9c Merge pull request #584 from darkdarkdragon/develop-RLJS-521
Add Remote.closeCurrentPathFind function, so current pathfind can be …
2015-10-06 18:40:25 -07:00
Ivan Tivonenko
e99010f363 Add Remote.closeCurrentPathFind function, so current pathfind can be properly closed,
so new can be created without adding to queue
2015-10-07 04:10:13 +03:00
Chris Clark
fa865f8409 Merge pull request #580 from FrRichard/patch-1
Update REFERENCE.md
2015-10-06 17:43:47 -07:00
FrRichard
40b613b7a2 Update REFERENCE.md
Update REFERENCE.md

Update REFERENCE.md
2015-10-07 02:29:17 +02:00
Chris Clark
a79b010572 Merge pull request #576 from shekenahglory/develop
allow offers request on orderbook subscribe when disconnected
2015-10-06 14:09:25 -07:00
Matthew Fettig
7404795dc6 fix bugs in orderbook subscription 2015-10-06 13:59:21 -07:00
Chris Clark
47a9fb5803 Merge pull request #583 from clark800/fix-merge-error
Fix merge error
2015-10-06 12:54:41 -07:00
Chris Clark
701d4c5722 Fix merge error 2015-10-06 12:47:26 -07:00
Chris Clark
d8d6f945ec Merge pull request #581 from clark800/binary-codec
Decouple UInt from non-serialization code
2015-10-05 16:39:17 -07:00
Geert Weening
a6821bb8ab Fix bower version regex 2015-10-05 15:58:49 -07:00
Chris Clark
2f163c3b6e Decouple UInt from non-serialization code 2015-10-05 15:27:43 -07:00
95 changed files with 2054 additions and 6365 deletions

View File

@@ -7,7 +7,9 @@
- [Method documentation](https://rawgit.com/ripple/ripple-lib/develop/docs/api.html)
**Changes**
+ [Add Remote.closeCurrentPathFind function, so current pathfind can be properly closed](https://github.com/ripple/ripple-lib/commit/e99010f363fc7cbe7fd547d3ca5b32ea083c44e6)
+ [Implement Balance Sheet API](https://github.com/ripple/ripple-lib/pull/579)
+ [Fix bugs in orderbook subscription](https://github.com/ripple/ripple-lib/commit/7404795dc64a85216148de7bc3ca7da7b33f4490)
+ [Fix crash due to rippled slowDown error](https://github.com/ripple/ripple-lib/commit/84838b2e9f6969b593b8462a62a6b8f516ada937)
+ [Fix: Emit error events and return error on pathfind](https://github.com/ripple/ripple-lib/commit/1ccbaf677631a1944eb05d90f7afc5f3690a03dd)
+ [Deprecate core and remove snake case method copying](https://github.com/ripple/ripple-lib/commit/fb8dc44ec1d49bb05cd0cdbe6dd4ab211195868a)

View File

@@ -14,6 +14,8 @@ __(More examples coming soon!)__
+ [Transaction requests](REFERENCE.md#transaction-requests)
3. [`Transaction` constructors](REFERENCE.md#transaction-constructors)
+ [Transaction events](REFERENCE.md#transaction-events)
4. [Subscriptions](REFERENCE.md#subscriptions)
+ [Orderbook subscription](REFERENCE.md#orderbook-subscription)
###Also see:
@@ -240,11 +242,10 @@ var options = {
limit: < limit >
};
var request = remote.requestBookOffers(options);
request.request(function(err, offers) {
//handle offers
var request = remote.requestBookOffers(options, function(err, offers) {
// handle offers
});
```
##Transaction requests
@@ -352,3 +353,24 @@ transaction.submit(function(err, res) {
#Amount objects
Coming Soon
#Subscriptions
##Orderbook subscription
Subscribes to an orderbook (including autobridged books). Send the orderbook on subscribe then notifies updates.
Available events: ['transaction', 'model', 'trade', 'offer_added', 'offer_removed', 'offer_changed', 'offer_funds_changed']
```js
parameters = {
currency_pays: <string>,
issuer_pays: <string>,
currency_gets: <string>,
issuer_gets: <string>
}
```
Basic subscription:
```js
var Orderbook = Remote.book(parameters);
Orderbook.on('model', handler);
```

86
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.13.0-rc12",
"version": "0.13.0-rc15",
"npm-shrinkwrap-version": "5.4.0",
"node-version": "v0.12.7",
"dependencies": {
@@ -132,9 +132,73 @@
}
}
},
"ripple-binary-codec": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.6.tgz",
"dependencies": {
"bn.js": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.2.0.tgz"
},
"create-hash": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
"dependencies": {
"cipher-base": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.1.tgz"
},
"ripemd160": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz"
},
"sha.js": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz"
}
}
},
"decimal.js": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-4.0.3.tgz"
},
"inherits": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"ripple-hashes": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.0.1.tgz",
"dependencies": {
"create-hash": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
"dependencies": {
"cipher-base": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.1.tgz"
},
"inherits": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"ripemd160": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz"
},
"sha.js": {
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz"
}
}
}
}
},
"ripple-keypairs": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.9.0.tgz",
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
"dependencies": {
"brorand": {
"version": "1.0.5",
@@ -149,22 +213,6 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"ripple-address-codec": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-1.6.0.tgz",
"dependencies": {
"x-address-codec": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.6.0.tgz",
"dependencies": {
"base-x": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz"
}
}
}
}
}
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.13.0-rc12",
"version": "0.13.0-rc15",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -27,7 +27,9 @@
"lodash": "^3.1.0",
"lru-cache": "~2.5.0",
"ripple-address-codec": "^2.0.1",
"ripple-keypairs": "^0.9.0",
"ripple-binary-codec": "^0.0.6",
"ripple-hashes": "^0.0.1",
"ripple-keypairs": "^0.10.0",
"ripple-lib-transactionparser": "^0.5.1",
"ripple-lib-value": "0.1.0",
"sjcl-codec": "0.1.0",

View File

@@ -26,7 +26,7 @@ gulp bower
exit_on_error
cd dist/bower
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc([0-9])+)?')
echo "version: $version"
git add ripple.js ripple-debug.js ripple-min.js bower.json
exit_on_error
@@ -40,4 +40,4 @@ exit_on_error
git push origin master
git push --tags origin master
cd ..
cd ../..

View File

@@ -26,7 +26,7 @@ gulp bower
exit_on_error
cd dist/bower
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc([0-9])+)?')
echo "version: $version"
git add ripple.js ripple-debug.js ripple-min.js bower.json
exit_on_error
@@ -40,4 +40,4 @@ exit_on_error
git push origin master
git push --tags origin master
cd ..
cd ../..

View File

@@ -4,21 +4,11 @@
const _ = require('lodash');
const assert = require('assert');
const validator = require('is-my-json-valid');
const core = require('./utils').core;
const ValidationError = require('./errors').ValidationError;
const {isValidAddress} = require('ripple-address-codec');
let SCHEMAS = {};
function isValidAddress(address: string): boolean {
return typeof address === 'string' && address.length > 0 &&
address[0] === 'r' &&
core.UInt160.is_valid(address);
}
function isValidLedgerHash(ledgerHash) {
return core.UInt256.is_valid(ledgerHash);
}
function loadSchemas() {
// listed explicitly for webpack (instead of scanning schemas directory)
const schemas = [
@@ -102,8 +92,7 @@ function formatSchemaErrors(errors) {
}
function schemaValidate(schemaName: string, object: any): void {
const formats = {address: isValidAddress,
ledgerHash: isValidLedgerHash};
const formats = {address: isValidAddress};
const options = {schemas: SCHEMAS, formats: formats,
verbose: true, greedy: true};
const schema = SCHEMAS[schemaName];

View File

@@ -11,6 +11,9 @@
"format": "uri",
"pattern": "^wss?://"
}
},
"proxy": {
"format": "uri"
}
},
"additionalProperties": false

55
src/api/common/types.js Normal file
View File

@@ -0,0 +1,55 @@
/* @flow */
'use strict';
export type RippledAmountIOU = {
currency: string,
value: string,
issuer?: string
}
export type RippledAmount = string | RippledAmountIOU
export type Amount = {
value: string,
currency: string,
counterparty?: string
}
// Amount where counterparty and value are optional
export type LaxLaxAmount = {
currency: string,
value?: string,
counterparty?: string
}
// A currency-counterparty pair, or just currency if it's XRP
export type Issue = {
currency: string,
counterparty?: string
}
export type Adjustment = {
address: string,
amount: Amount,
tag?: number
}
export type MaxAdjustment = {
address: string,
maxAmount: Amount,
tag?: number
}
export type MinAdjustment = {
address: string,
minAmount: Amount,
tag?: number
}
export type Memo = {
type?: string,
format?: string,
data?: string
}

View File

@@ -7,7 +7,7 @@ const errors = require('./errors');
const es6promisify = require('es6-promisify');
const keypairs = require('ripple-keypairs');
type Amount = {currency: string, issuer: string, value: string}
import type {Amount, RippledAmount} from './types.js';
function dropsToXrp(drops: string): string {
return (new BigNumber(drops)).dividedBy(1000000.0).toString();
@@ -17,13 +17,14 @@ function xrpToDrops(xrp: string): string {
return (new BigNumber(xrp)).times(1000000.0).floor().toString();
}
function toRippledAmount(amount: Amount): string|Amount {
function toRippledAmount(amount: Amount): RippledAmount {
if (amount.currency === 'XRP') {
return xrpToDrops(amount.value);
}
return {
currency: amount.currency,
issuer: amount.counterparty ? amount.counterparty : amount.issuer,
issuer: amount.counterparty ? amount.counterparty :
(amount.issuer ? amount.issuer : undefined),
value: amount.value
};
}

View File

@@ -1,3 +1,4 @@
/* @flow */
'use strict';
const _ = require('lodash');
@@ -5,8 +6,23 @@ const utils = require('./utils');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
import type {Amount} from '../common/types.js';
function formatBalanceSheet(balanceSheet) {
type BalanceSheetOptions = {
excludeAddresses?: Array<string>,
ledgerVersion?: number
}
type GetBalanceSheet = {
balances?: Array<Amount>,
assets?: Array<Amount>,
obligations?: Array<{
currency: string,
value: string
}>
}
function formatBalanceSheet(balanceSheet): GetBalanceSheet {
const result = {};
if (!_.isUndefined(balanceSheet.balances)) {
@@ -33,7 +49,9 @@ function formatBalanceSheet(balanceSheet) {
return result;
}
function getBalanceSheetAsync(address, options, callback) {
function getBalanceSheetAsync(address: string, options: BalanceSheetOptions,
callback
) {
validate.address(address);
validate.getBalanceSheetOptions(options);
@@ -61,7 +79,8 @@ function getBalanceSheetAsync(address, options, callback) {
});
}
function getBalanceSheet(address: string, options = {}) {
function getBalanceSheet(address: string, options: BalanceSheetOptions = {}
): Promise<GetBalanceSheet> {
return utils.promisify(getBalanceSheetAsync).call(this, address, options);
}

View File

@@ -7,11 +7,20 @@ const getTrustlines = require('./trustlines');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
import type {Remote} from '../../core/remote';
import type {GetLedgerSequenceCallback} from '../../core/remote';
import type {Remote, GetLedgerSequenceCallback} from '../../core/remote';
import type {TrustlinesOptions, Trustline} from './trustlines-types.js';
function getTrustlineBalanceAmount(trustline) {
type Balance = {
value: string,
currency: string,
counterparty?: string
}
type GetBalances = Array<Balance>
function getTrustlineBalanceAmount(trustline: Trustline) {
return {
currency: trustline.specification.currency,
counterparty: trustline.specification.counterparty,
@@ -19,16 +28,27 @@ function getTrustlineBalanceAmount(trustline) {
};
}
function formatBalances(balances) {
const xrpBalance = {
currency: 'XRP',
value: balances.xrp
};
return [xrpBalance].concat(
balances.trustlines.map(getTrustlineBalanceAmount));
function formatBalances(options, balances) {
const result = balances.trustlines.map(getTrustlineBalanceAmount);
if (!(options.counterparty ||
(options.currency && options.currency !== 'XRP')
)) {
const xrpBalance = {
currency: 'XRP',
value: balances.xrp
};
result.unshift(xrpBalance);
}
if (options.limit && result.length > options.limit) {
const toRemove = result.length - options.limit;
result.splice(-toRemove, toRemove);
}
return result;
}
function getTrustlinesAsync(account, options, callback) {
function getTrustlinesAsync(account: string, options: TrustlinesOptions,
callback
) {
getTrustlines.call(this, account, options)
.then(data => callback(null, data))
.catch(callback);
@@ -44,7 +64,9 @@ function getLedgerVersionHelper(remote: Remote, optionValue?: number,
}
}
function getBalancesAsync(account, options, callback) {
function getBalancesAsync(account: string, options: TrustlinesOptions,
callback
) {
validate.address(account);
validate.getBalancesOptions(options);
@@ -54,10 +76,11 @@ function getBalancesAsync(account, options, callback) {
_.partial(utils.getXRPBalance, this.remote, account)
),
trustlines: _.partial(getTrustlinesAsync.bind(this), account, options)
}, composeAsync(formatBalances, convertErrors(callback)));
}, composeAsync(_.partial(formatBalances, options), convertErrors(callback)));
}
function getBalances(account: string, options = {}) {
function getBalances(account: string, options: TrustlinesOptions = {}
): Promise<GetBalances> {
return utils.promisify(getBalancesAsync).call(this, account, options);
}

View File

@@ -5,8 +5,17 @@ const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const parseLedger = require('./parse/ledger');
import type {GetLedger} from './types.js';
function getLedgerAsync(options, callback) {
type LedgerOptions = {
ledgerVersion?: number,
includeAllData?: boolean,
includeTransactions?: boolean,
includeState?: boolean
}
function getLedgerAsync(options: LedgerOptions, callback) {
validate.getLedgerOptions(options);
const request = {
@@ -21,7 +30,7 @@ function getLedgerAsync(options, callback) {
convertErrors(callback)));
}
function getLedger(options = {}) {
function getLedger(options: LedgerOptions = {}): Promise<GetLedger> {
return utils.promisify(getLedgerAsync).call(this, options);
}

View File

@@ -7,11 +7,40 @@ const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const parseOrderbookOrder = require('./parse/orderbook-order');
import type {Remote} from '../../core/remote';
import type {OrdersOptions, OrderSpecification} from './types.js';
import type {Amount, Issue} from '../common/types.js';
type Orderbook = {
base: Issue,
counter: Issue
}
type OrderbookItem = {
specification: OrderSpecification,
properties: {
maker: string,
sequence: number,
makerExchangeRate: string
},
state?: {
fundedAmount: Amount,
priceOfFundedAmount: Amount
}
}
type OrderbookOrders = Array<OrderbookItem>
type GetOrderbook = {
bids: OrderbookOrders,
asks: OrderbookOrders
}
// account is to specify a "perspective", which affects which unfunded offers
// are returned
function getBookOffers(remote, account, ledgerVersion, limit,
takerGets, takerPays, callback
function getBookOffers(remote: Remote, account: string,
ledgerVersion?: number, limit?: number, takerGets: Issue,
takerPays: Issue, callback
) {
remote.requestBookOffers(utils.renameCounterpartyToIssuerInOrder({
taker_gets: takerGets,
@@ -22,15 +51,15 @@ function getBookOffers(remote, account, ledgerVersion, limit,
}), composeAsync(data => data.offers, convertErrors(callback)));
}
function isSameIssue(a, b) {
function isSameIssue(a: Amount, b: Amount) {
return a.currency === b.currency && a.counterparty === b.counterparty;
}
function directionFilter(direction, order) {
function directionFilter(direction: string, order: OrderbookItem) {
return order.specification.direction === direction;
}
function flipOrder(order) {
function flipOrder(order: OrderbookItem) {
const specification = order.specification;
const flippedSpecification = {
quantity: specification.totalPrice,
@@ -41,12 +70,12 @@ function flipOrder(order) {
return _.merge({}, order, {specification: newSpecification});
}
function alignOrder(base, order) {
function alignOrder(base: Amount, order: OrderbookItem) {
const quantity = order.specification.quantity;
return isSameIssue(quantity, base) ? order : flipOrder(order);
}
function formatBidsAndAsks(orderbook, offers) {
function formatBidsAndAsks(orderbook: Orderbook, offers) {
// the "base" currency is the currency that you are buying or selling
// the "counter" is the currency that the "base" is priced in
// a "bid"/"ask" is an order to buy/sell the base, respectively
@@ -64,7 +93,9 @@ function formatBidsAndAsks(orderbook, offers) {
return {bids, asks};
}
function getOrderbookAsync(account, orderbook, options, callback) {
function getOrderbookAsync(account: string, orderbook: Orderbook,
options: OrdersOptions, callback
) {
validate.address(account);
validate.orderbook(orderbook);
validate.getOrderbookOptions(options);
@@ -78,7 +109,9 @@ function getOrderbookAsync(account, orderbook, options, callback) {
callback));
}
function getOrderbook(account: string, orderbook: Object, options = {}) {
function getOrderbook(account: string, orderbook: Orderbook,
options: OrdersOptions = {}
): Promise<GetOrderbook> {
return utils.promisify(getOrderbookAsync).call(this,
account, orderbook, options);
}

View File

@@ -7,9 +7,13 @@ const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const parseAccountOrder = require('./parse/account-order');
import type {Remote} from '../../core/remote';
import type {OrdersOptions, Order} from './types.js';
function requestAccountOffers(remote, address, ledgerVersion, marker, limit,
callback
type GetOrders = Array<Order>
function requestAccountOffers(remote: Remote, address: string,
ledgerVersion: number, marker: string, limit: number, callback
) {
remote.requestAccountOffers({
account: address,
@@ -23,7 +27,7 @@ function requestAccountOffers(remote, address, ledgerVersion, marker, limit,
}), convertErrors(callback)));
}
function getOrdersAsync(account, options, callback) {
function getOrdersAsync(account: string, options: OrdersOptions, callback) {
validate.address(account);
validate.getOrdersOptions(options);
@@ -34,7 +38,8 @@ function getOrdersAsync(account, options, callback) {
(order) => order.properties.sequence), callback));
}
function getOrders(account: string, options = {}) {
function getOrders(account: string, options: OrdersOptions = {}
): Promise<GetOrders> {
return utils.promisify(async.seq(
utils.getLedgerOptionsWithLedgerVersion,
getOrdersAsync)).call(this, account, options);

View File

@@ -1,13 +1,10 @@
/* @flow */
'use strict';
const utils = require('./utils');
import type {Amount, RippledAmount} from '../../common/types.js';
type Amount = string | {currency: string, issuer: string, value: string}
type XRPAmount = {currency: string, value: string}
type IOUAmount = {currency: string, value: string, counterparty: string}
type Output = XRPAmount | IOUAmount
function parseAmount(amount: Amount): Output {
function parseAmount(amount: RippledAmount): Amount {
if (typeof amount === 'string') {
return {
currency: 'XRP',

View File

@@ -3,6 +3,7 @@
const _ = require('lodash');
const removeUndefined = require('./utils').removeUndefined;
const parseTransaction = require('./transaction');
import type {GetLedger} from '../types.js';
function parseTransactions(transactions) {
if (_.isEmpty(transactions)) {
@@ -27,7 +28,7 @@ function parseState(state) {
return {rawState: JSON.stringify(state)};
}
function parseLedger(ledger: Object): Object {
function parseLedger(ledger: Object): GetLedger {
return removeUndefined(_.assign({
accepted: ledger.accepted,
closed: ledger.closed,

View File

@@ -2,13 +2,15 @@
'use strict';
const _ = require('lodash');
const parseAmount = require('./amount');
import type {Amount, RippledAmount} from '../../common/types.js';
import type {GetPaths, RippledPathsResponse} from '../pathfind-types.js';
function parsePaths(paths) {
return paths.map(steps => steps.map(step =>
_.omit(step, ['type', 'type_hex'])));
}
function removeAnyCounterpartyEncoding(address: string, amount: Object) {
function removeAnyCounterpartyEncoding(address: string, amount: Amount) {
return amount.counterparty === address ?
_.omit(amount, 'counterparty') : amount;
}
@@ -21,7 +23,7 @@ function createAdjustment(address: string, adjustmentWithoutAddress: Object) {
}
function parseAlternative(sourceAddress: string, destinationAddress: string,
destinationAmount: Object, alternative: Object
destinationAmount: RippledAmount, alternative: Object
) {
// we use "maxAmount"/"minAmount" here so that the result can be passed
// directly to preparePayment
@@ -38,7 +40,7 @@ function parseAlternative(sourceAddress: string, destinationAddress: string,
};
}
function parsePathfind(pathfindResult: Object): Object {
function parsePathfind(pathfindResult: RippledPathsResponse): GetPaths {
const sourceAddress = pathfindResult.source_account;
const destinationAddress = pathfindResult.destination_account;
const destinationAmount = pathfindResult.destination_amount;

View File

@@ -67,15 +67,19 @@ function parseOutcome(tx: Object): ?Object {
};
}
function hexToString(hex) {
return hex ? new Buffer(hex, 'hex').toString('utf-8') : undefined;
}
function parseMemos(tx: Object): ?Array<Object> {
if (!Array.isArray(tx.Memos) || tx.Memos.length === 0) {
return undefined;
}
return tx.Memos.map((m) => {
return removeUndefined({
type: m.Memo.parsed_memo_type,
format: m.Memo.parsed_memo_format,
data: m.Memo.parsed_memo_data
type: m.Memo.parsed_memo_type || hexToString(m.Memo.MemoType),
format: m.Memo.parsed_memo_format || hexToString(m.Memo.MemoFormat),
data: m.Memo.parsed_memo_data || hexToString(m.Memo.MemoData)
});
});
}

View File

@@ -0,0 +1,54 @@
/* @flow */
'use strict';
import type {Amount, LaxLaxAmount, RippledAmount, Adjustment, MaxAdjustment,
MinAdjustment} from '../common/types.js';
type Path = {
source: Adjustment | MaxAdjustment,
destination: Adjustment | MinAdjustment,
paths: string
}
export type GetPaths = Array<Path>
export type PathFind = {
source: {
address: string,
amount?: Amount,
currencies?: Array<{currency: string, counterparty?:string}>
},
destination: {
address: string,
amount: LaxLaxAmount
}
}
export type PathFindParams = {
src_account: string,
dst_amount: RippledAmount,
dst_account: string,
src_amount?: RippledAmount,
src_currencies?: Array<string>
}
export type RippledPathsResponse = {
alternatives: Array<{
paths_computed: Array<Array<{
type: number,
type_hex: string,
account?: string,
issuer?: string,
currency?: string
}>>,
source_amount: RippledAmount
}>,
type: string,
destination_account: string,
destination_amount: RippledAmount,
destination_currencies?: Array<string>,
source_account?: string,
source_currencies?: Array<{currency: string}>,
full_reply?: boolean
}

View File

@@ -11,27 +11,20 @@ const ValidationError = utils.common.errors.ValidationError;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const toRippledAmount = utils.common.toRippledAmount;
import type {Remote} from '../../core/remote';
import type {RippledAmount} from '../common/types.js';
import type {GetPaths, PathFind, PathFindParams,
RippledPathsResponse} from './pathfind-types.js';
type PathFindParams = {
src_currencies?: Array<string>, src_account: string,
dst_amount: string | Object, dst_account?: string,
src_amount?: string | Object
}
function addParams(params: PathFindParams, result: {}) {
return _.assign({}, result, {
function addParams(params: PathFindParams, result: RippledPathsResponse) {
return _.defaults(_.assign({}, result, {
source_account: params.src_account,
source_currencies: params.src_currencies,
destination_amount: params.dst_amount
});
source_currencies: params.src_currencies
}), {destination_amount: params.dst_amount});
}
type PathFind = {
source: {address: string, currencies: Array<string>},
destination: {address: string, amount: string}
}
function requestPathFind(remote, pathfind: PathFind, callback) {
function requestPathFind(remote: Remote, pathfind: PathFind, callback) {
const destinationAmount = _.assign({value: -1}, pathfind.destination.amount);
const params: PathFindParams = {
src_account: pathfind.source.address,
@@ -65,7 +58,8 @@ function requestPathFind(remote, pathfind: PathFind, callback) {
composeAsync(_.partial(addParams, params), convertErrors(callback)));
}
function addDirectXrpPath(paths, xrpBalance) {
function addDirectXrpPath(paths: RippledPathsResponse, xrpBalance: string
): RippledPathsResponse {
// Add XRP "path" only if the source acct has enough XRP to make the payment
const destinationAmount = paths.destination_amount;
if ((new BigNumber(xrpBalance)).greaterThanOrEqualTo(destinationAmount)) {
@@ -77,13 +71,15 @@ function addDirectXrpPath(paths, xrpBalance) {
return paths;
}
function isRippledIOUAmount(amount) {
function isRippledIOUAmount(amount: RippledAmount) {
// rippled XRP amounts are specified as decimal strings
return (typeof amount === 'object') &&
amount.currency && (amount.currency !== 'XRP');
}
function conditionallyAddDirectXRPPath(remote, address, paths, callback) {
function conditionallyAddDirectXRPPath(remote: Remote, address: string,
paths: RippledPathsResponse, callback
) {
if (isRippledIOUAmount(paths.destination_amount)
|| !_.includes(paths.destination_currencies, 'XRP')) {
callback(null, paths);
@@ -93,7 +89,7 @@ function conditionallyAddDirectXRPPath(remote, address, paths, callback) {
}
}
function formatResponse(pathfind, paths) {
function formatResponse(pathfind: PathFind, paths: RippledPathsResponse) {
if (paths.alternatives && paths.alternatives.length > 0) {
return parsePathfind(paths);
}
@@ -118,7 +114,7 @@ function formatResponse(pathfind, paths) {
}
}
function getPathsAsync(pathfind, callback) {
function getPathsAsync(pathfind: PathFind, callback) {
validate.pathfind(pathfind);
const address = pathfind.source.address;
@@ -128,7 +124,7 @@ function getPathsAsync(pathfind, callback) {
], composeAsync(_.partial(formatResponse, pathfind), callback));
}
function getPaths(pathfind: Object) {
function getPaths(pathfind: PathFind): Promise<GetPaths> {
return utils.promisify(getPathsAsync).call(this, pathfind);
}

View File

@@ -8,6 +8,31 @@ const composeAsync = utils.common.composeAsync;
const AccountFlags = utils.common.constants.AccountFlags;
const convertErrors = utils.common.convertErrors;
type SettingsOptions = {
ledgerVersion?: number
}
type GetSettings = {
passwordSpent?: boolean,
requireDestinationTag?: boolean,
requireAuthorization?: boolean,
disallowIncomingXRP?: boolean,
disableMasterKey?: boolean,
enableTransactionIDTracking?: boolean,
noFreeze?: boolean,
globalFreeze?: boolean,
defaultRipple?: boolean,
emailHash?: ?string,
walletLocator?: ?string,
walletSize?: ?number,
messageKey?: string,
domain?: string,
transferRate?: ?number,
signers?: string,
regularKey?: string
}
function parseFlags(value) {
const settings = {};
for (const flagName in AccountFlags) {
@@ -25,7 +50,7 @@ function formatSettings(response) {
return _.assign({}, parsedFlags, parsedFields);
}
function getSettingsAsync(account, options, callback) {
function getSettingsAsync(account: string, options: SettingsOptions, callback) {
validate.address(account);
validate.getSettingsOptions(options);
@@ -38,7 +63,8 @@ function getSettingsAsync(account, options, callback) {
composeAsync(formatSettings, convertErrors(callback)));
}
function getSettings(account: string, options = {}) {
function getSettings(account: string, options: SettingsOptions = {}
): Promise<GetSettings> {
return utils.promisify(getSettingsAsync).call(this, account, options);
}

View File

@@ -1,14 +1,16 @@
/* @flow */
'use strict';
import type {Amount, Memo} from '../common/types.js';
type Outcome = {
result: string,
timestamp?: string,
ledgerVersion: number,
indexInLedger: number,
fee: string,
balanceChanges: Object,
orderbookChanges: Object,
ledgerVersion: number,
indexInLedger: number
timestamp?: string
}
type Adjustment = {
@@ -56,18 +58,6 @@ type OrderCancellation = {
orderSequence: number
}
type Memo = {
type?: string,
format?: string,
data?: string
}
type Amount = {
value: string,
currency: string,
counterparty?: string
}
type Payment = {
source: Adjustment,
destination: Adjustment,
@@ -88,7 +78,7 @@ type PaymentTransaction = {
sequence: number
}
type Order = {
export type Order = {
direction: string,
quantity: Amount,
totalPrice: Amount,
@@ -138,10 +128,10 @@ export type TransactionOptions = {
maxLedgerVersion?: number
}
export type GetTransactionResponse = PaymentTransaction | OrderTransaction |
export type TransactionType = PaymentTransaction | OrderTransaction |
OrderCancellationTransaction | TrustlineTransaction | SettingsTransaction
export type GetTransactionResponseCallback =
(err?: ?Error, data?: GetTransactionResponse) => void
(err?: ?Error, data?: TransactionType) => void
export type CallbackType = (err?: ?Error, data?: Object) => void

View File

@@ -11,7 +11,7 @@ const RippleError = require('../../core/rippleerror').RippleError;
import type {Remote} from '../../core/remote';
import type {CallbackType, GetTransactionResponse,
import type {CallbackType, TransactionType,
GetTransactionResponseCallback, TransactionOptions}
from './transaction-types';
@@ -106,7 +106,7 @@ function getTransactionAsync(identifier: string, options: TransactionOptions,
function getTransaction(identifier: string,
options: TransactionOptions = {}
): Promise<GetTransactionResponse> {
): Promise<TransactionType> {
return utils.promisify(getTransactionAsync).call(this, identifier, options);
}

View File

@@ -9,6 +9,29 @@ const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
import type {Remote} from '../../core/remote';
import type {TransactionType} from './transaction-types';
type TransactionsOptions = {
start?: string,
limit?: number,
minLedgerVersion?: number,
maxLedgerVersion?: number,
earliestFirst?: boolean,
excludeFailures?: boolean,
initiated?: boolean,
counterparty?: string,
types?: Array<string>,
binary?: boolean,
startTx?: TransactionType
}
type GetTransactionsResponse = Array<TransactionType>
type CallbackType = (err?: ?Error, data?: GetTransactionsResponse) => void
function parseAccountTxTransaction(tx) {
// rippled uses a different response format for 'account_tx' than 'tx'
tx.tx.meta = tx.meta;
@@ -16,7 +39,7 @@ function parseAccountTxTransaction(tx) {
return parseTransaction(tx.tx);
}
function counterpartyFilter(filters, tx) {
function counterpartyFilter(filters, tx: TransactionType) {
if (!filters.counterparty) {
return true;
}
@@ -31,7 +54,9 @@ function counterpartyFilter(filters, tx) {
return false;
}
function transactionFilter(address, filters, tx) {
function transactionFilter(address: string, filters: TransactionsOptions,
tx: TransactionType
) {
if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') {
return false;
}
@@ -50,13 +75,15 @@ function transactionFilter(address, filters, tx) {
return true;
}
function orderFilter(options, tx) {
function orderFilter(options: TransactionsOptions, tx: TransactionType) {
return !options.startTx || (options.earliestFirst ?
utils.compareTransactions(tx, options.startTx) > 0 :
utils.compareTransactions(tx, options.startTx) < 0);
}
function formatPartialResponse(address, options, data) {
function formatPartialResponse(address: string,
options: TransactionsOptions, data
) {
return {
marker: data.marker,
results: data.transactions
@@ -67,7 +94,9 @@ function formatPartialResponse(address, options, data) {
};
}
function getAccountTx(remote, address, options, marker, limit, callback) {
function getAccountTx(remote: Remote, address: string,
options: TransactionsOptions, marker: string, limit: number, callback
) {
const params = {
account: address,
// -1 is equivalent to earliest available validated ledger
@@ -85,7 +114,9 @@ function getAccountTx(remote, address, options, marker, limit, callback) {
convertErrors(callback)));
}
function checkForLedgerGaps(remote, options, transactions) {
function checkForLedgerGaps(remote: Remote, options: TransactionsOptions,
transactions: GetTransactionsResponse
) {
let {minLedgerVersion, maxLedgerVersion} = options;
// if we reached the limit on number of transactions, then we can shrink
@@ -105,7 +136,9 @@ function checkForLedgerGaps(remote, options, transactions) {
}
}
function formatResponse(remote, options, transactions) {
function formatResponse(remote: Remote, options: TransactionsOptions,
transactions: GetTransactionsResponse
) {
const compare = options.earliestFirst ? utils.compareTransactions :
_.rearg(utils.compareTransactions, 1, 0);
const sortedTransactions = transactions.sort(compare);
@@ -113,13 +146,17 @@ function formatResponse(remote, options, transactions) {
return sortedTransactions;
}
function getTransactionsInternal(remote, address, options, callback) {
function getTransactionsInternal(remote: Remote, address: string,
options: TransactionsOptions, callback
) {
const getter = _.partial(getAccountTx, remote, address, options);
const format = _.partial(formatResponse, remote, options);
utils.getRecursive(getter, options.limit, composeAsync(format, callback));
}
function getTransactionsAsync(account, options, callback) {
function getTransactionsAsync(account: string,
options: TransactionsOptions, callback: CallbackType
) {
validate.address(account);
validate.getTransactionsOptions(options);
@@ -138,7 +175,8 @@ function getTransactionsAsync(account, options, callback) {
}
}
function getTransactions(account: string, options = {}) {
function getTransactions(account: string, options: TransactionsOptions = {}
): Promise<GetTransactionsResponse> {
return utils.promisify(getTransactionsAsync).call(this, account, options);
}

View File

@@ -0,0 +1,33 @@
/* @flow */
'use strict';
export type TrustLineSpecification = {
currency: string,
counterparty: string,
limit: string,
qualityIn?: number,
qualityOut?: number,
ripplingDisabled?: boolean,
authorized?: boolean,
frozen?: boolean
}
export type Trustline = {
specification: TrustLineSpecification,
counterparty: {
limit: string,
ripplingDisabled?: boolean,
frozen?: boolean,
authorized?: boolean
},
state: {
balance: string
}
}
export type TrustlinesOptions = {
counterparty?: string,
currency?: string,
limit?: number,
ledgerVersion?: number
}

View File

@@ -8,11 +8,17 @@ const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const parseAccountTrustline = require('./parse/account-trustline');
function currencyFilter(currency, trustline) {
import type {Remote} from '../../core/remote';
import type {TrustlinesOptions, Trustline} from './trustlines-types.js';
type GetTrustlinesResponse = Array<Trustline>
function currencyFilter(currency: string, trustline: Trustline) {
return currency === null || trustline.specification.currency === currency;
}
function formatResponse(options, data) {
function formatResponse(options: TrustlinesOptions, data) {
return {
marker: data.marker,
results: data.lines.map(parseAccountTrustline)
@@ -20,8 +26,8 @@ function formatResponse(options, data) {
};
}
function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
callback
function getAccountLines(remote: Remote, address: string, ledgerVersion: number,
options: TrustlinesOptions, marker: string, limit: number, callback
) {
const requestOptions = {
account: address,
@@ -36,8 +42,7 @@ function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
convertErrors(callback)));
}
function getTrustlinesAsync(account: string, options: {currency: string,
counterparty: string, limit: number, ledgerVersion: number},
function getTrustlinesAsync(account: string, options: TrustlinesOptions,
callback: () => void
): void {
validate.address(account);
@@ -48,7 +53,8 @@ function getTrustlinesAsync(account: string, options: {currency: string,
utils.getRecursive(getter, options.limit, callback);
}
function getTrustlines(account: string, options = {}) {
function getTrustlines(account: string, options: TrustlinesOptions = {}
): Promise<GetTrustlinesResponse> {
return utils.promisify(async.seq(
utils.getLedgerOptionsWithLedgerVersion,
getTrustlinesAsync)).call(this, account, options);

50
src/api/ledger/types.js Normal file
View File

@@ -0,0 +1,50 @@
/* @flow */
'use strict';
import type {Amount} from '../common/types.js';
export type OrdersOptions = {
limit?: number,
ledgerVersion?: number
}
export type OrderSpecification = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
// If enabled, the offer will not consume offers that exactly match it, and
// instead becomes an Offer node in the ledger. It will still consume offers
// that cross it.
passive?: boolean
}
export type Order = {
specification: OrderSpecification,
properties: {
maker: string,
sequence: number,
makerExchangeRate: string
}
}
export type GetLedger = {
accepted: boolean,
closed: boolean,
stateHash: string,
closeTime: number,
closeTimeResolution: number,
closeFlags: number,
ledgerHash: string,
ledgerVersion: number,
parentLedgerHash: string,
parentCloseTime: number,
totalDrops: string,
transactionHash: string,
transactions?: Array<Object>,
rawTransactions?: string,
transactionHashes?: Array<string>,
rawState?: string,
stateHashes?: Array<string>
}

View File

@@ -6,9 +6,18 @@ const common = require('../common');
const dropsToXrp = common.dropsToXrp;
const composeAsync = common.composeAsync;
import type {Remote} from '../../core/remote';
import type {TransactionType} from './transaction-types';
import type {Issue} from '../common/types.js';
type Callback = (err: any, data: any) => void
type RecursiveData = {
marker: string,
results: Array<any>
}
type RecursiveCallback = (err: any, data: RecursiveData) => void
function clamp(value: number, min: number, max: number): number {
assert(min <= max, 'Illegal clamp bounds');
return Math.min(Math.max(value, min), max);
@@ -21,7 +30,8 @@ function getXRPBalance(remote: Remote, address: string, ledgerVersion?: number,
composeAsync((data) => dropsToXrp(data.account_data.Balance), callback));
}
type Getter = (marker: ?string, limit: number, callback: Callback) => void
type Getter = (marker: ?string, limit: number,
callback: RecursiveCallback) => void
// If the marker is omitted from a response, you have reached the end
// getter(marker, limit, callback), callback(error, {marker, results})
@@ -48,21 +58,20 @@ function getRecursive(getter: Getter, limit?: number, callback: Callback) {
getRecursiveRecur(getter, undefined, limit || Infinity, callback);
}
type Amount = {counterparty?: string, issuer?: string, value: string}
function renameCounterpartyToIssuer(amount?: Amount): ?{issuer?: string} {
function renameCounterpartyToIssuer(amount?: Issue): ?{issuer?: string} {
if (amount === undefined) {
return undefined;
}
const issuer = amount.counterparty === undefined ?
amount.issuer : amount.counterparty;
(amount.issuer !== undefined ? amount.issuer : undefined) :
amount.counterparty;
const withIssuer = _.assign({}, amount, {issuer: issuer});
return _.omit(withIssuer, 'counterparty');
}
type Order = {taker_gets: Amount, taker_pays: Amount}
type RequestBookOffersArgs = {taker_gets: Issue, taker_pays: Issue}
function renameCounterpartyToIssuerInOrder(order: Order) {
function renameCounterpartyToIssuerInOrder(order: RequestBookOffersArgs) {
const taker_gets = renameCounterpartyToIssuer(order.taker_gets);
const taker_pays = renameCounterpartyToIssuer(order.taker_pays);
const changes = {taker_gets: taker_gets, taker_pays: taker_pays};
@@ -84,9 +93,8 @@ function signum(num) {
* @returns {Number} [-1, 0, 1]
*/
type Outcome = {outcome: {ledgerVersion: number, indexInLedger: number}};
function compareTransactions(first: Outcome, second: Outcome): number {
function compareTransactions(first: TransactionType, second: TransactionType
): number {
if (!first.outcome || !second.outcome) {
return 0;
}

View File

@@ -2,6 +2,7 @@
'use strict';
const _ = require('lodash');
const common = require('../common');
const hashes = require('ripple-hashes');
function convertLedgerHeader(header) {
return {
@@ -25,7 +26,7 @@ function convertLedgerHeader(header) {
function hashLedgerHeader(ledgerHeader) {
const header = convertLedgerHeader(ledgerHeader);
return common.core.Ledger.calculateLedgerHash(header);
return hashes.computeLedgerHash(header);
}
function computeTransactionHash(ledger) {
@@ -39,8 +40,7 @@ function computeTransactionHash(ledger) {
tx.meta ? {metaData: tx.meta} : {});
return renameMeta;
});
const ledgerObject = common.core.Ledger.from_json({transactions: txs});
const transactionHash = ledgerObject.calc_tx_hash().to_hex();
const transactionHash = hashes.computeTransactionTreeHash(txs);
if (ledger.transactionHash !== undefined
&& ledger.transactionHash !== transactionHash) {
throw new common.errors.ValidationError('transactionHash in header'
@@ -54,8 +54,7 @@ function computeStateHash(ledger) {
return ledger.stateHash;
}
const state = JSON.parse(ledger.rawState);
const ledgerObject = common.core.Ledger.from_json({accountState: state});
const stateHash = ledgerObject.calc_account_hash().to_hex();
const stateHash = hashes.computeStateTreeHash(state);
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
throw new common.errors.ValidationError('stateHash in header'
+ ' does not match computed hash of state');
@@ -64,11 +63,11 @@ function computeStateHash(ledger) {
}
function computeLedgerHash(ledger: Object): string {
const hashes = {
const subhashes = {
transactionHash: computeTransactionHash(ledger),
stateHash: computeStateHash(ledger)
};
return hashLedgerHeader(_.assign({}, ledger, hashes));
return hashLedgerHeader(_.assign({}, ledger, subhashes));
}
module.exports = computeLedgerHash;

View File

@@ -3,6 +3,8 @@
const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
import type {Instructions, Prepare} from './types.js';
import type {Order} from '../ledger/transaction-types.js';
const OfferCreateFlags = {
passive: {set: 'Passive'},
@@ -10,7 +12,7 @@ const OfferCreateFlags = {
fillOrKill: {set: 'FillOrKill'}
};
function createOrderTransaction(account, order) {
function createOrderTransaction(account: string, order: Order): Transaction {
validate.address(account);
validate.order(order);
@@ -30,12 +32,16 @@ function createOrderTransaction(account, order) {
return transaction;
}
function prepareOrderAsync(account, order, instructions, callback) {
function prepareOrderAsync(account: string, order: Order,
instructions: Instructions, callback
) {
const transaction = createOrderTransaction(account, order);
utils.prepareTransaction(transaction, this.remote, instructions, callback);
}
function prepareOrder(account: string, order: Object, instructions = {}) {
function prepareOrder(account: string, order: Order,
instructions: Instructions = {}
): Promise<Prepare> {
return utils.promisify(prepareOrderAsync.bind(this))(
account, order, instructions);
}

View File

@@ -3,8 +3,11 @@
const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
import type {Instructions, Prepare} from './types.js';
function createOrderCancellationTransaction(account, sequence) {
function createOrderCancellationTransaction(account: string,
sequence: number
): Transaction {
validate.address(account);
validate.sequence(sequence);
@@ -13,16 +16,16 @@ function createOrderCancellationTransaction(account, sequence) {
return transaction;
}
function prepareOrderCancellationAsync(account, sequence, instructions,
callback
function prepareOrderCancellationAsync(account: string, sequence: number,
instructions: Instructions, callback
) {
const transaction = createOrderCancellationTransaction(account, sequence);
utils.prepareTransaction(transaction, this.remote, instructions, callback);
}
function prepareOrderCancellation(account: string, sequence: number,
instructions = {}
) {
instructions: Instructions = {}
): Promise<Prepare> {
return utils.promisify(prepareOrderCancellationAsync.bind(this))(
account, sequence, instructions);
}

View File

@@ -6,19 +6,44 @@ const validate = utils.common.validate;
const toRippledAmount = utils.common.toRippledAmount;
const Transaction = utils.common.core.Transaction;
const ValidationError = utils.common.errors.ValidationError;
import type {Instructions, Prepare} from './types.js';
import type {Amount, Adjustment, MaxAdjustment,
MinAdjustment, Memo} from '../common/types.js';
function isXRPToXRPPayment(payment) {
type Payment = {
source: Adjustment | MaxAdjustment,
destination: Adjustment | MinAdjustment,
paths?: string,
memos?: Array<Memo>,
// A 256-bit hash that can be used to identify a particular payment
invoiceID?: string,
// A boolean that, if set to true, indicates that this payment should go
// through even if the whole amount cannot be delivered because of a lack of
// liquidity or funds in the source_account account
allowPartialPayment?: boolean,
// A boolean that can be set to true if paths are specified and the sender
// would like the Ripple Network to disregard any direct paths from
// the source_account to the destination_account. This may be used to take
// advantage of an arbitrage opportunity or by gateways wishing to issue
// balances from a hot wallet to a user who has mistakenly set a trustline
// directly to the hot wallet
noDirectRipple?: boolean,
limitQuality?: boolean
}
function isXRPToXRPPayment(payment: Payment): boolean {
const sourceCurrency = _.get(payment, 'source.maxAmount.currency');
const destinationCurrency = _.get(payment, 'destination.amount.currency');
return sourceCurrency === 'XRP' && destinationCurrency === 'XRP';
}
function isIOUWithoutCounterparty(amount) {
function isIOUWithoutCounterparty(amount: Amount): boolean {
return amount && amount.currency !== 'XRP'
&& amount.counterparty === undefined;
}
function applyAnyCounterpartyEncoding(payment) {
function applyAnyCounterpartyEncoding(payment: Payment): void {
// Convert blank counterparty to sender or receiver's address
// (Ripple convention for 'any counterparty')
// https://ripple.com/build/transactions/
@@ -33,14 +58,15 @@ function applyAnyCounterpartyEncoding(payment) {
});
}
function createMaximalAmount(amount) {
function createMaximalAmount(amount: Amount): Amount {
const maxXRPValue = '100000000000';
const maxIOUValue = '9999999999999999e80';
const maxValue = amount.currency === 'XRP' ? maxXRPValue : maxIOUValue;
return _.assign(amount, {value: maxValue});
}
function createPaymentTransaction(account, paymentArgument) {
function createPaymentTransaction(account: string, paymentArgument: Payment
): Transaction {
const payment = _.cloneDeep(paymentArgument);
applyAnyCounterpartyEncoding(payment);
validate.address(account);
@@ -115,12 +141,16 @@ function createPaymentTransaction(account, paymentArgument) {
return transaction;
}
function preparePaymentAsync(account, payment, instructions, callback) {
function preparePaymentAsync(account: string, payment: Payment,
instructions: Instructions, callback
) {
const transaction = createPaymentTransaction(account, payment);
utils.prepareTransaction(transaction, this.remote, instructions, callback);
}
function preparePayment(account: string, payment: Object, instructions = {}) {
function preparePayment(account: string, payment: Payment,
instructions: Instructions = {}
): Promise<Prepare> {
return utils.promisify(preparePaymentAsync.bind(this))(
account, payment, instructions);
}

View File

@@ -0,0 +1,62 @@
/* @flow */
'use strict';
type SettingPasswordSpent = {
passwordSpent?: boolean,
}
type SettingRequireDestinationTag = {
requireDestinationTag?: boolean,
}
type SettingRequireAuthorization = {
requireAuthorization?: boolean,
}
type SettingDisallowIncomingXRP = {
disallowIncomingXRP?: boolean,
}
type SettingDisableMasterKey = {
disableMasterKey?: boolean,
}
type SettingEnableTransactionIDTracking = {
enableTransactionIDTracking?: boolean,
}
type SettingNoFreeze = {
noFreeze?: boolean,
}
type SettingGlobalFreeze = {
globalFreeze?: boolean,
}
type SettingDefaultRipple = {
defaultRipple?: boolean,
}
type SettingEmailHash = {
emailHash?: ?string,
}
type SettingWalletLocator = {
walletLocator?: ?string,
}
type SettingWalletSize = {
walletSize?: ?number,
}
type SettingMessageKey = {
messageKey?: string,
}
type SettingDomain = {
domain?: string,
}
type SettingTransferRate = {
transferRate?: ?number,
}
type SettingSigners = {
signers?: string,
}
type SettingRegularKey = {
regularKey?: string
}
export type Settings = SettingRegularKey | SettingSigners |
SettingTransferRate | SettingDomain | SettingMessageKey | SettingWalletSize |
SettingWalletLocator | SettingEmailHash | SettingDefaultRipple |
SettingGlobalFreeze | SettingNoFreeze | SettingEnableTransactionIDTracking |
SettingDisableMasterKey | SettingDisallowIncomingXRP |
SettingRequireAuthorization | SettingRequireDestinationTag |
SettingPasswordSpent

View File

@@ -7,11 +7,14 @@ const validate = utils.common.validate;
const AccountFlagIndices = utils.common.constants.AccountFlagIndices;
const AccountFields = utils.common.constants.AccountFields;
const Transaction = utils.common.core.Transaction;
import type {Instructions, Prepare} from './types.js';
import type {Settings} from './settings-types.js';
// Emptry string passed to setting will clear it
const CLEAR_SETTING = null;
function setTransactionFlags(transaction, values) {
function setTransactionFlags(transaction: Transaction, values: Settings) {
const keys = Object.keys(values);
assert(keys.length === 1, 'ERROR: can only set one setting per transaction');
const flagName = keys[0];
@@ -26,7 +29,7 @@ function setTransactionFlags(transaction, values) {
}
}
function setTransactionFields(transaction, input) {
function setTransactionFields(transaction: Transaction, input: Settings) {
const fieldSchema = AccountFields;
for (const fieldName in fieldSchema) {
const field = fieldSchema[fieldName];
@@ -63,11 +66,12 @@ function setTransactionFields(transaction, input) {
* are returned
*/
function convertTransferRate(transferRate) {
function convertTransferRate(transferRate: number | string): number | string {
return (new BigNumber(transferRate)).shift(9).toNumber();
}
function createSettingsTransaction(account, settings) {
function createSettingsTransaction(account: string, settings: Settings
): Transaction {
validate.address(account);
validate.settings(settings);
@@ -90,12 +94,16 @@ function createSettingsTransaction(account, settings) {
return transaction;
}
function prepareSettingsAsync(account, settings, instructions, callback) {
function prepareSettingsAsync(account: string, settings: Settings,
instructions: Instructions, callback
) {
const transaction = createSettingsTransaction(account, settings);
utils.prepareTransaction(transaction, this.remote, instructions, callback);
}
function prepareSettings(account: string, settings: Object, instructions = {}) {
function prepareSettings(account: string, settings: Object,
instructions: Instructions = {}
): Promise<Prepare> {
return utils.promisify(prepareSettingsAsync.bind(this))(
account, settings, instructions);
}

View File

@@ -2,7 +2,8 @@
'use strict';
const utils = require('./utils');
const keypairs = require('ripple-keypairs');
const core = utils.common.core;
const binary = require('ripple-binary-codec');
const sha512 = require('hash.js').sha512;
const validate = utils.common.validate;
/**
@@ -17,20 +18,22 @@ const validate = utils.common.validate;
*/
const HASH_TX_ID = 0x54584E00; // 'TXN'
function serialize(txJSON) {
return core.SerializedObject.from_json(txJSON);
// For a hash function, rippled uses SHA-512 and then truncates the result
// to the first 256 bytes. This algorithm, informally called SHA-512Half,
// provides an output that has comparable security to SHA-256, but runs
// faster on 64-bit processors.
function sha512half(buffer) {
return sha512().update(buffer).digest('hex').toUpperCase().slice(0, 64);
}
function hashSerialization(serialized, prefix) {
return serialized.hash(prefix || HASH_TX_ID).to_hex();
}
function signingData(txJSON) {
return core.Transaction.from_json(txJSON).signingData().buffer;
const hexPrefix = prefix.toString(16).toUpperCase();
return sha512half(new Buffer(hexPrefix + serialized, 'hex'));
}
function computeSignature(txJSON, privateKey) {
return keypairs.sign(signingData(txJSON), privateKey);
const signingData = binary.encodeForSigning(txJSON);
return keypairs.sign(signingData, privateKey);
}
function sign(txJSON: string, secret: string
@@ -44,9 +47,9 @@ function sign(txJSON: string, secret: string
tx.SigningPubKey = keypair.publicKey;
}
tx.TxnSignature = computeSignature(tx, keypair.privateKey);
const serialized = serialize(tx);
const serialized = binary.encode(tx);
return {
signedTransaction: serialized.to_hex(),
signedTransaction: serialized,
id: hashSerialization(serialized, HASH_TX_ID)
};
}

View File

@@ -6,7 +6,16 @@ const validate = utils.common.validate;
const Request = utils.common.core.Request;
const convertErrors = utils.common.convertErrors;
function isImmediateRejection(engineResult) {
type Submit = {
success: boolean,
engineResult: string,
engineResultCode: number,
engineResultMessage?: string,
txBlob?: string,
txJson?: Object
}
function isImmediateRejection(engineResult: string): boolean {
// note: "tel" errors mean the local server refused to process the
// transaction *at that time*, but it could potentially buffer the
// transaction and then process it at a later time, for example
@@ -37,7 +46,7 @@ function submitAsync(txBlob: string, callback: (err: any, data: any) => void
convertSubmitErrors(convertErrors(callback))));
}
function submit(txBlob: string) {
function submit(txBlob: string): Promise<Submit> {
return utils.promisify(submitAsync.bind(this))(txBlob);
}

View File

@@ -4,8 +4,18 @@ const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
import type {Instructions, Prepare} from './types.js';
import type {Memo} from '../common/types.js';
function createSuspendedPaymentCancellationTransaction(account, payment) {
type SuspendedPaymentCancellation = {
owner: string,
paymentSequence: number,
memos?: Array<Memo>
}
function createSuspendedPaymentCancellationTransaction(account: string,
payment: SuspendedPaymentCancellation
): Transaction {
validate.address(account);
validate.suspendedPaymentCancellation(payment);
@@ -24,15 +34,17 @@ function createSuspendedPaymentCancellationTransaction(account, payment) {
return transaction;
}
function prepareSuspendedPaymentCancellationAsync(account, payment,
instructions, callback) {
function prepareSuspendedPaymentCancellationAsync(account: string,
payment: SuspendedPaymentCancellation, instructions: Instructions, callback
) {
const transaction =
createSuspendedPaymentCancellationTransaction(account, payment);
utils.prepareTransaction(transaction, this.remote, instructions, callback);
}
function prepareSuspendedPaymentCancellation(account: string, payment: Object,
instructions = {}) {
function prepareSuspendedPaymentCancellation(account: string,
payment: SuspendedPaymentCancellation, instructions: Instructions = {}
): Promise<Prepare> {
return utils.promisify(prepareSuspendedPaymentCancellationAsync)
.call(this, account, payment, instructions);
}

View File

@@ -5,8 +5,21 @@ const utils = require('./utils');
const validate = utils.common.validate;
const toRippledAmount = utils.common.toRippledAmount;
const Transaction = utils.common.core.Transaction;
import type {Instructions, Prepare} from './types.js';
import type {Adjustment, MaxAdjustment, Memo} from '../common/types.js';
function createSuspendedPaymentCreationTransaction(account, payment) {
type SuspendedPaymentCreation = {
source: MaxAdjustment,
destination: Adjustment,
memos?: Array<Memo>,
digest?: string,
allowCancelAfter?: number,
allowExecuteAfter?: number
}
function createSuspendedPaymentCreationTransaction(account: string,
payment: SuspendedPaymentCreation
): Transaction {
validate.address(account);
validate.suspendedPaymentCreation(payment);
@@ -41,15 +54,17 @@ function createSuspendedPaymentCreationTransaction(account, payment) {
return transaction;
}
function prepareSuspendedPaymentCreationAsync(account, payment, instructions,
callback) {
function prepareSuspendedPaymentCreationAsync(account: string,
payment: SuspendedPaymentCreation, instructions: Instructions, callback
) {
const transaction =
createSuspendedPaymentCreationTransaction(account, payment);
utils.prepareTransaction(transaction, this.remote, instructions, callback);
}
function prepareSuspendedPaymentCreation(account: string, payment: Object,
instructions = {}) {
function prepareSuspendedPaymentCreation(account: string,
payment: SuspendedPaymentCreation, instructions: Instructions = {}
): Promise<Prepare> {
return utils.promisify(prepareSuspendedPaymentCreationAsync)
.call(this, account, payment, instructions);
}

View File

@@ -4,8 +4,21 @@ const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
import type {Instructions, Prepare} from './types.js';
import type {Memo} from '../common/types.js';
function createSuspendedPaymentExecutionTransaction(account, payment) {
type SuspendedPaymentExecution = {
owner: string,
paymentSequence: number,
memos?: Array<Memo>,
method?: number,
digest?: string,
proof?: string
}
function createSuspendedPaymentExecutionTransaction(account: string,
payment: SuspendedPaymentExecution
): Transaction {
validate.address(account);
validate.suspendedPaymentExecution(payment);
@@ -34,15 +47,17 @@ function createSuspendedPaymentExecutionTransaction(account, payment) {
return transaction;
}
function prepareSuspendedPaymentExecutionAsync(account, payment, instructions,
callback) {
function prepareSuspendedPaymentExecutionAsync(account: string,
payment: SuspendedPaymentExecution, instructions: Instructions, callback
) {
const transaction =
createSuspendedPaymentExecutionTransaction(account, payment);
utils.prepareTransaction(transaction, this.remote, instructions, callback);
}
function prepareSuspendedPaymentExecution(account: string, payment: Object,
instructions = {}) {
function prepareSuspendedPaymentExecution(account: string,
payment: SuspendedPaymentExecution, instructions: Instructions = {}
): Promise<Prepare> {
return utils.promisify(prepareSuspendedPaymentExecutionAsync)
.call(this, account, payment, instructions);
}

View File

@@ -4,6 +4,8 @@ const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
const BigNumber = require('bignumber.js');
import type {Instructions, Prepare} from './types.js';
import type {TrustLineSpecification} from '../ledger/trustlines-types.js';
const TrustSetFlags = {
authorized: {set: 'SetAuth'},
@@ -16,7 +18,9 @@ function convertQuality(quality) {
(new BigNumber(quality)).shift(9).truncated().toNumber();
}
function createTrustlineTransaction(account, trustline) {
function createTrustlineTransaction(account: string,
trustline: TrustLineSpecification
): Transaction {
validate.address(account);
validate.trustline(trustline);
@@ -33,13 +37,16 @@ function createTrustlineTransaction(account, trustline) {
return transaction;
}
function prepareTrustlineAsync(account, trustline, instructions, callback) {
function prepareTrustlineAsync(account: string,
trustline: TrustLineSpecification, instructions: Instructions, callback
) {
const transaction = createTrustlineTransaction(account, trustline);
utils.prepareTransaction(transaction, this.remote, instructions, callback);
}
function prepareTrustline(account: string, trustline: Object, instructions = {}
) {
function prepareTrustline(account: string,
trustline: TrustLineSpecification, instructions: Instructions = {}
): Promise<Prepare> {
return utils.promisify(prepareTrustlineAsync.bind(this))(
account, trustline, instructions);
}

View File

@@ -0,0 +1,19 @@
/* @flow */
'use strict';
export type Instructions = {
sequence?: number,
fee?: string,
maxFee?: string,
maxLedgerVersion?: number,
maxLedgerVersionOffset?: number
}
export type Prepare = {
txJSON: string,
instructions: {
fee: string,
sequence: number,
maxLedgerVersion?: number
}
}

View File

@@ -5,8 +5,12 @@ const async = require('async');
const BigNumber = require('bignumber.js');
const common = require('../common');
const composeAsync = common.composeAsync;
import type {Remote} from '../../core/remote';
import type {Transaction} from '../../core/transaction';
import type {Instructions} from './types.js';
function setTransactionBitFlags(transaction: any, values: any, flags: any
function setTransactionBitFlags(transaction: Transaction, values: any,
flags: any
): void {
for (const flagName in flags) {
const flagValue = values[flagName];
@@ -21,14 +25,14 @@ function setTransactionBitFlags(transaction: any, values: any, flags: any
}
}
function getFeeDrops(remote, callback) {
function getFeeDrops(remote: Remote, callback) {
const feeUnits = 10; // all transactions currently have a fee of 10 fee units
remote.feeTxAsync(feeUnits, (err, data) => {
callback(err, data ? data.to_text() : undefined);
});
}
function formatPrepareResponse(txJSON) {
function formatPrepareResponse(txJSON: Object): Object {
const instructions = {
fee: txJSON.Fee,
sequence: txJSON.Sequence,
@@ -41,9 +45,9 @@ function formatPrepareResponse(txJSON) {
}
type Callback = (err: ?(typeof Error),
data: {txJSON: string, instructions: any}) => void;
function prepareTransaction(transaction: any, remote: any, instructions: any,
callback: Callback
data: {txJSON: string, instructions: Instructions}) => void;
function prepareTransaction(transaction: Transaction, remote: Remote,
instructions: Instructions, callback: Callback
): void {
common.validate.instructions(instructions);
@@ -54,11 +58,11 @@ function prepareTransaction(transaction: any, remote: any, instructions: any,
function prepareMaxLedgerVersion(callback_) {
if (instructions.maxLedgerVersion !== undefined) {
txJSON.LastLedgerSequence = parseInt(instructions.maxLedgerVersion, 10);
txJSON.LastLedgerSequence = instructions.maxLedgerVersion;
callback_();
} else {
const offset = instructions.maxLedgerVersionOffset !== undefined ?
parseInt(instructions.maxLedgerVersionOffset, 10) : 3;
instructions.maxLedgerVersionOffset : 3;
remote.getLedgerSequence((error, ledgerVersion) => {
txJSON.LastLedgerSequence = ledgerVersion + offset;
callback_(error);
@@ -84,7 +88,7 @@ function prepareTransaction(transaction: any, remote: any, instructions: any,
function prepareSequence(callback_) {
if (instructions.sequence !== undefined) {
txJSON.Sequence = parseInt(instructions.sequence, 10);
txJSON.Sequence = instructions.sequence;
callback_(null, formatPrepareResponse(txJSON));
} else {
remote.findAccount(account).getNextSequence(function(error, sequence) {

View File

@@ -6,10 +6,11 @@
const assert = require('assert');
const extend = require('extend');
const utils = require('./utils');
const Currency = require('./currency').Currency;
const normalizeCurrency = require('./currency').normalizeCurrency;
const {XRPValue, IOUValue} = require('ripple-lib-value');
const {isValidAddress} = require('ripple-address-codec');
const {ACCOUNT_ONE, ACCOUNT_ZERO} = require('./constants');
const {ACCOUNT_ONE, ACCOUNT_ZERO, CURRENCY_ONE}
= require('./constants');
type Value = XRPValue | IOUValue;
@@ -21,7 +22,7 @@ function Amount(value = new XRPValue(NaN)) {
this._value = value;
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
this._currency = new Currency();
this._currency = null;
this._issuer = 'NaN';
}
@@ -105,7 +106,7 @@ Amount.NaN = function() {
return result; // but let's be careful
};
Amount.from_components_unsafe = function(value: Value, currency: Currency,
Amount.from_components_unsafe = function(value: Value, currency: string,
issuer: string, isNative: boolean
) {
const result = new Amount(value);
@@ -190,12 +191,9 @@ Amount.prototype.divide = function(divisor) {
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
*/
Amount.prototype.ratio_human = function(denom, opts) {
const options = extend({ }, opts);
Amount.prototype.ratio_human = function(denom) {
const numerator = this.clone();
let denominator = Amount.from_json(denom);
const denominator = Amount.from_json(denom);
// If either operand is NaN, the result is NaN.
if (!numerator.is_valid() || !denominator.is_valid()) {
@@ -206,14 +204,6 @@ Amount.prototype.ratio_human = function(denom, opts) {
return new Amount(NaN);
}
// Apply interest/demurrage
//
// We only need to apply it to the second factor, because the currency unit of
// the first factor will carry over into the result.
if (options.reference_date) {
denominator = denominator.applyInterest(options.reference_date);
}
// Special case: The denominator is a native (XRP) amount.
//
// In that case, it's going to be expressed as base units (1 XRP =
@@ -252,23 +242,14 @@ Amount.prototype.ratio_human = function(denom, opts) {
* for Ripple epoch.
* @return {Amount} The product. Unit will be the same as the first factor.
*/
Amount.prototype.product_human = function(factor, options = {}) {
let fac = Amount.from_json(factor);
Amount.prototype.product_human = function(factor) {
const fac = Amount.from_json(factor);
// If either operand is NaN, the result is NaN.
if (!this.is_valid() || !fac.is_valid()) {
return new Amount();
}
// Apply interest/demurrage
//
// We only need to apply it to the second factor, because the currency unit of
// the first factor will carry over into the result.
if (options.reference_date) {
fac = fac.applyInterest(options.reference_date);
}
const product = this.multiply(fac);
// Special case: The second factor is a native (XRP) amount expressed as base
@@ -398,7 +379,7 @@ Amount.prototype.equals = function(d, ignore_issuer) {
return this.is_valid() && d.is_valid()
&& this._is_native === d._is_native
&& this._value.equals(d._value)
&& (this._is_native || (this._currency.equals(d._currency)
&& (this._is_native || ((this._currency === d._currency)
&& (ignore_issuer || this._issuer === d._issuer)));
};
@@ -425,7 +406,7 @@ Amount.prototype.is_valid = function() {
};
Amount.prototype.is_valid_full = function() {
return this.is_valid() && this._currency.is_valid()
return this.is_valid() && this._currency !== null
&& isValidAddress(this._issuer) && this._issuer !== ACCOUNT_ZERO;
};
@@ -464,9 +445,7 @@ Amount.prototype.negate = function() {
* $
*/
Amount.prototype.parse_human = function(j, options) {
const opts = options || {};
Amount.prototype.parse_human = function(j) {
const hex_RE = /^[a-fA-F0-9]{40}$/;
const currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/;
@@ -516,14 +495,6 @@ Amount.prototype.parse_human = function(j, options) {
(this._is_native ? new XRPValue(value) :
new IOUValue(value));
this._set_value(newValue);
// Apply interest/demurrage
if (opts.reference_date && this._currency.has_interest()) {
const interest = this._currency.get_interest_at(opts.reference_date);
this._set_value(
this._value.divide(new IOUValue(interest.toString())));
}
return this;
};
@@ -570,7 +541,7 @@ Amount.prototype.parse_quality =
function(quality, counterCurrency, counterIssuer, opts) {
const options = opts || {};
const baseCurrency = Currency.from_json(options.base_currency);
const baseCurrency = options.base_currency;
const mantissa_hex = quality.substring(quality.length - 14);
const offset_hex = quality.substring(
@@ -578,11 +549,11 @@ function(quality, counterCurrency, counterIssuer, opts) {
const mantissa = new IOUValue(mantissa_hex, null, 16);
const offset = parseInt(offset_hex, 16) - 100;
this._currency = Currency.from_json(counterCurrency);
this._currency = normalizeCurrency(counterCurrency);
this._issuer = counterIssuer;
this._is_native = this._currency.is_native();
this._is_native = (this._currency === 'XRP');
if (this._is_native && baseCurrency.is_native()) {
if (this._is_native && baseCurrency === 'XRP') {
throw new Error('XRP/XRP quality is not allowed');
}
@@ -613,7 +584,7 @@ function(quality, counterCurrency, counterIssuer, opts) {
// pay:$price drops get:1 X
// pay:($price / 1,000,000) XRP get:1 X
nativeAdjusted = nativeAdjusted.divide(bi_xns_unit);
} else if (baseCurrency.is_valid() && baseCurrency.is_native()) {
} else if (baseCurrency === 'XRP') {
// pay:$price X get:1 drop
// pay:($price * 1,000,000) X get:1 XRP
nativeAdjusted = nativeAdjusted.multiply(bi_xns_unit);
@@ -627,18 +598,12 @@ function(quality, counterCurrency, counterIssuer, opts) {
this._set_value(nativeAdjusted);
}
if (options.reference_date && baseCurrency.is_valid()
&& baseCurrency.has_interest()) {
const interest = baseCurrency.get_interest_at(options.reference_date);
this._set_value(
this._value.divide(new IOUValue(interest.toString())));
}
return this;
};
Amount.prototype.parse_number = function(n) {
this._is_native = false;
this._currency = Currency.from_json(1);
this._currency = CURRENCY_ONE;
this._issuer = ACCOUNT_ONE;
this._set_value(new IOUValue(n));
return this;
@@ -653,7 +618,7 @@ Amount.prototype.parse_json = function(j) {
const m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
if (m) {
this._currency = Currency.from_json(m[2]);
this._currency = normalizeCurrency(m[2]);
if (m[3]) {
this._issuer = m[3];
} else {
@@ -662,7 +627,7 @@ Amount.prototype.parse_json = function(j) {
this.parse_value(m[1]);
} else {
this.parse_native(j);
this._currency = Currency.from_json('0');
this._currency = 'XRP';
this._issuer = ACCOUNT_ZERO;
}
break;
@@ -679,8 +644,7 @@ Amount.prototype.parse_json = function(j) {
if (j instanceof Amount) {
j.copyTo(this);
} else if (j.hasOwnProperty('value')) {
// Parse the passed value to sanitize and copy it.
this._currency.parse_json(j.currency, true); // Never XRP.
this._currency = normalizeCurrency(j.currency);
if (typeof j.issuer !== 'string') {
throw new Error('issuer must be a string');
@@ -727,8 +691,8 @@ Amount.prototype.parse_value = function(j) {
};
Amount.prototype.set_currency = function(c) {
this._currency = Currency.from_json(c);
this._is_native = this._currency.is_native();
this._currency = normalizeCurrency(c);
this._is_native = (c === 'XRP');
return this;
};
@@ -782,27 +746,6 @@ Amount.prototype.to_text = function() {
+ (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : '');
};
/**
* Calculate present value based on currency and a reference date.
*
* This only affects demurraging and interest-bearing currencies.
*
* User should not store amount objects after the interest is applied. This is
* intended by display functions such as toHuman().
*
* @param {Date|Number} referenceDate Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @return {Amount} The amount with interest applied.
*/
Amount.prototype.applyInterest = function(referenceDate) {
if (!this._currency.has_interest()) {
return this;
}
const interest = this._currency.get_interest_at(referenceDate);
return this._copy(
this._value.multiply(new IOUValue(interest.toString())));
};
/**
* Format only value in a human-readable format.
*
@@ -836,13 +779,9 @@ Amount.prototype.to_human = function(options) {
/* eslint-disable consistent-this */
// Apply demurrage/interest
let ref = this;
const ref = this;
/* eslint-enable consistent-this */
if (opts.reference_date) {
ref = this.applyInterest(opts.reference_date);
}
const isNegative = ref._value.isNegative();
const valueString = ref._value.abs().toFixed();
const parts = valueString.split('.');
@@ -930,7 +869,7 @@ Amount.prototype.to_human = function(options) {
Amount.prototype.to_human_full = function(options) {
const opts = options || {};
const value = this.to_human(opts);
const currency = this._currency.to_human();
const currency = this._currency;
const issuer = this._issuer;
const base = value + '/' + currency;
return this.is_native() ? base : (base + '/' + issuer);
@@ -943,8 +882,7 @@ Amount.prototype.to_json = function() {
const amount_json = {
value: this.to_text(),
currency: this._currency.has_interest() ?
this._currency.to_hex() : this._currency.to_json()
currency: this._currency
};
if (isValidAddress(this._issuer)) {
@@ -960,8 +898,7 @@ Amount.prototype.to_text_full = function() {
}
return this._is_native
? this.to_human() + '/XRP'
: this.to_text() + '/' + this._currency.to_json()
+ '/' + this._issuer;
: this.to_text() + '/' + this._currency + '/' + this._issuer;
};
// For debugging.
@@ -987,7 +924,7 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) {
return type + ' value differs.';
}
if (!this._is_native) {
if (!this._currency.equals(d._currency)) {
if (this._currency !== d._currency) {
return 'Non-XRP currency differs.';
}
if (!ignore_issuer && this._issuer !== d._issuer) {

View File

@@ -3,8 +3,8 @@
const _ = require('lodash');
const assert = require('assert');
const Amount = require('./amount').Amount;
const UInt160 = require('./uint160').UInt160;
const Utils = require('./orderbookutils');
const {toHexCurrency} = require('./currency');
function assertValidNumber(number, message) {
assert(!_.isNull(number) && !isNaN(number), message);
@@ -22,12 +22,10 @@ function AutobridgeCalculator(currencyGets, currencyPays,
) {
this._currencyGets = currencyGets;
this._currencyPays = currencyPays;
this._currencyGetsHex = currencyGets.to_hex();
this._currencyPaysHex = currencyPays.to_hex();
this._currencyGetsHex = toHexCurrency(currencyGets);
this._currencyPaysHex = toHexCurrency(currencyPays);
this._issuerGets = issuerGets;
this._issuerGetsObj = UInt160.from_json(issuerGets);
this._issuerPays = issuerPays;
this._issuerPaysObj = UInt160.from_json(issuerPays);
this.legOneOffers = _.cloneDeep(legOneOffers);
this.legTwoOffers = _.cloneDeep(legTwoOffers);
@@ -94,9 +92,9 @@ AutobridgeCalculator.prototype._calculateInternal = function(
}
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
this._currencyPays, this._issuerPaysObj);
this._currencyPays, this._issuerPays);
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
this._currencyGets, this._issuerGetsObj);
this._currencyGets, this._issuerGets);
if (legOneTakerGetsFunded.is_zero()) {
legOnePointer++;
@@ -157,19 +155,19 @@ AutobridgeCalculator.prototype._calculateInternal = function(
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegOne =
function(legOneOffer, legTwoOffer) {
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
this._currencyPays, this._issuerPaysObj);
this._currencyPays, this._issuerPays);
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
this._currencyGets, this._issuerGetsObj);
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets,
this._currencyPays, this._issuerPaysObj);
this._currencyGets, this._issuerGets);
const legOneQuality = Utils.getOfferQuality(legOneOffer,
this._currencyPays, this._issuerPays);
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer,
this._currencyGets, this._issuerGetsObj);
this._currencyGets, this._issuerGets);
const autobridgedTakerPays = legTwoTakerPaysFunded.multiply(legOneQuality);
if (legOneOffer.Account === legTwoOffer.Account) {
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer,
this._currencyPays, this._issuerPaysObj);
this._currencyPays, this._issuerPays);
const updatedTakerGets = legOneTakerGets.subtract(legTwoTakerPaysFunded);
this.setLegOneTakerGets(legOneOffer, updatedTakerGets);
@@ -202,19 +200,19 @@ function(legOneOffer, legTwoOffer) {
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegTwo =
function(legOneOffer, legTwoOffer) {
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
this._currencyPays, this._issuerPaysObj);
this._currencyPays, this._issuerPays);
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
this._currencyGets, this._issuerGetsObj);
const legTwoQuality = Utils.getOfferQuality(legTwoOffer, this._currencyGets,
this._currencyGets, this._issuerGetsObj);
this._currencyGets, this._issuerGets);
const legTwoQuality = Utils.getOfferQuality(legTwoOffer,
this._currencyGets, this._issuerGets);
const autobridgedTakerGets = legOneTakerGetsFunded.divide(legTwoQuality);
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer,
this._currencyPays, this._issuerPaysObj);
this._currencyPays, this._issuerPays);
// Update funded amount since leg two offer was not completely consumed
legTwoOffer.taker_gets_funded = Utils.getOfferTakerGetsFunded(legTwoOffer,
this._currencyGets, this._issuerGetsObj)
this._currencyGets, this._issuerGets)
.subtract(autobridgedTakerGets)
.to_text();
legTwoOffer.taker_pays_funded = legTwoTakerPaysFunded
@@ -239,9 +237,9 @@ function(legOneOffer, legTwoOffer) {
AutobridgeCalculator.prototype.getAutobridgedOfferWithoutClamps =
function(legOneOffer, legTwoOffer) {
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer,
this._currencyGets, this._issuerGetsObj);
this._currencyGets, this._issuerGets);
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer,
this._currencyPays, this._issuerPaysObj);
this._currencyPays, this._issuerPays);
return this.formatAutobridgedOffer(
autobridgedTakerGets,
@@ -378,12 +376,12 @@ AutobridgeCalculator.prototype.unclampLegOneOwnerFunds = function(legOneOffer) {
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
legOneOffer.initTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
this._currencyPays, this._issuerPaysObj);
this._currencyPays, this._issuerPays);
this.setLegOneTakerGetsFunded(
legOneOffer,
Utils.getOfferTakerGets(legOneOffer, this._currencyPays,
this._issuerPaysObj)
this._issuerPays)
);
};
@@ -405,7 +403,7 @@ AutobridgeCalculator.prototype.clampLegOneOwnerFunds = function(legOneOffer) {
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
const takerGets = Utils.getOfferTakerGets(legOneOffer, this._currencyPays,
this._issuerPaysObj);
this._issuerPays);
if (takerGets.compareTo(legOneOffer.initTakerGetsFunded) > 0) {
// After clamping, TakerGets is still greater than initial funded amount
@@ -431,15 +429,15 @@ function(legOneOffer) {
assert(!legOneOffer.is_fully_funded, 'Leg one offer cannot be fully funded');
const fundedSum = Utils.getOfferTakerGetsFunded(legOneOffer,
this._currencyPays, this._issuerPaysObj)
this._currencyPays, this._issuerPays)
.add(this.getLeftoverOwnerFunds(legOneOffer.Account));
if (fundedSum.compareTo(Utils.getOfferTakerGets(legOneOffer,
this._currencyPays, this._issuerPaysObj)) >= 0
this._currencyPays, this._issuerPays)) >= 0
) {
// There are enough extra funds to fully fund the offer
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer,
this._currencyPays, this._issuerPaysObj);
this._currencyPays, this._issuerPays);
const updatedLeftover = fundedSum.subtract(legOneTakerGets);
this.setLegOneTakerGetsFunded(legOneOffer, legOneTakerGets);
@@ -466,8 +464,8 @@ function setLegOneTakerGetsFunded(legOneOffer, takerGetsFunded) {
legOneOffer.taker_gets_funded = takerGetsFunded.to_text();
legOneOffer.taker_pays_funded = takerGetsFunded
.multiply(Utils.getOfferQuality(legOneOffer, this._currencyGets,
this._currencyPays, this._issuerPaysObj))
.multiply(Utils.getOfferQuality(legOneOffer,
this._currencyPays, this._issuerPays))
.to_text();
if (legOneOffer.taker_gets_funded === legOneOffer.TakerGets.value) {
@@ -488,8 +486,8 @@ function(legOneOffer, takerGets) {
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
assert(takerGets instanceof Amount, 'Taker gets funded is invalid');
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets,
this._currencyPays, this._issuerPaysObj);
const legOneQuality = Utils.getOfferQuality(legOneOffer,
this._currencyPays, this._issuerPays);
legOneOffer.TakerGets = takerGets.to_text();
legOneOffer.TakerPays = takerGets.multiply(legOneQuality).to_json();

View File

@@ -1,57 +0,0 @@
'use strict';
const BN = require('bn.js');
const extend = require('extend');
const {encode, decode} = require('ripple-address-codec');
const Base = {};
extend(Base, {
VER_NONE: 1,
VER_NODE_PUBLIC: 28,
VER_NODE_PRIVATE: 32,
VER_ACCOUNT_ID: 0,
VER_ACCOUNT_PUBLIC: 35,
VER_ACCOUNT_PRIVATE: 34,
VER_FAMILY_GENERATOR: 41,
VER_FAMILY_SEED: 33,
VER_ED25519_SEED: [0x01, 0xE1, 0x4B]
});
// --> input: big-endian array of bytes.
// <-- string at least as long as input.
Base.encode = function(input, alphabet) {
return encode(input, {alphabet});
};
// --> input: String
// <-- array of bytes or undefined.
Base.decode = function(input, alphabet) {
if (typeof input !== 'string') {
return undefined;
}
try {
return decode(input, {alphabet});
} catch (e) {
return undefined;
}
};
// --> input: Array
// <-- String
Base.encode_check = function(version, input, alphabet) {
return encode(input, {version, alphabet});
};
// --> input : String
// <-- NaN || BN
Base.decode_check = function(version, input, alphabet) {
try {
const decoded = decode(input, {version, alphabet});
return new BN(decoded);
} catch (e) {
return NaN;
}
};
exports.Base = Base;

View File

@@ -1,45 +0,0 @@
'use strict';
function normalize(digitArray) {
let i = 0;
while (digitArray[i] === 0) {
++i;
}
if (i > 0) {
digitArray.splice(0, i);
}
return digitArray;
}
function divmod(digitArray, base, divisor) {
let remainder = 0;
let temp;
let divided;
let j = -1;
const length = digitArray.length;
const quotient = new Array(length);
while (++j < length) {
temp = remainder * base + digitArray[j];
divided = temp / divisor;
quotient[j] = divided << 0;
remainder = temp % divisor;
}
return {quotient: normalize(quotient), remainder: remainder};
}
function convertBase(digitArray, fromBase, toBase) {
const result = [];
let dividend = digitArray;
let qr;
while (dividend.length > 0) {
qr = divmod(dividend, fromBase, toBase);
result.unshift(qr.remainder);
dividend = qr.quotient;
}
return normalize(result);
}
module.exports = convertBase;

View File

@@ -2,5 +2,7 @@
module.exports = {
ACCOUNT_ZERO: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
ACCOUNT_ONE: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
ACCOUNT_ONE: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
CURRENCY_ZERO: '0000000000000000000000000000000000000000',
CURRENCY_ONE: '0000000000000000000000000000000000000001'
};

View File

@@ -1,429 +1,58 @@
'use strict';
const _ = require('lodash');
const extend = require('extend');
const UInt160 = require('./uint160').UInt160;
const utils = require('./utils');
const Float = require('./ieee754').Float;
function isISOCode(currency) {
return /^[A-Z0-9]{3}$/.test(currency);
}
//
// Currency support
//
function isHexCurrency(currency) {
return /[A-Fa-f0-9]{40}/.test(currency);
}
const Currency = extend(function() {
// Internal form: 0 = XRP. 3 letter-code.
// XXX Internal should be 0 or hex with three letter annotation when valid.
// Json form:
// '', 'XRP', '0': 0
// 3-letter code: ...
// XXX Should support hex, C++ doesn't currently allow it.
this._value = NaN;
this._update();
}, UInt160);
Currency.prototype = Object.create(extend({}, UInt160.prototype));
Currency.prototype.constructor = Currency;
Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
/**
* Tries to correctly interpret a Currency as entered by a user.
*
* Examples:
*
* USD => currency
* USD - Dollar => currency with optional full currency
* name
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate
* per year
* XAU - Gold (-0.5%pa) => Optionally allowed full currency name
* USD (1%pa) => US dollars with 1% effective interest
* per year
* INR - Indian Rupees => Optional full currency name with spaces
* TYX - 30-Year Treasuries => Optional full currency with numbers
* and a dash
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers,
* dash and interest rate
*
* The regular expression below matches above cases, broken down for better
* understanding:
*
* ^\s* // start with any amount of whitespace
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3
* digit numeric currency-code. See ISO 4217
* (\s*-\s*[- \w]+) // optional full currency name following the dash
* after currency code, full currency code can
* contain letters, numbers and dashes
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and
* . notation (-0.5%pa)
* \s*$ // end with any amount of whitespace
*
*/
/* eslint-disable max-len*/
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9\<\>\(\)\{\}\[\]\|\?\!\@\#\$\%\^\&]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
/* eslint-enable max-len*/
Currency.from_json = function(j, shouldInterpretXrpAsIou) {
return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
};
Currency.from_human = function(j, opts) {
return (new Currency().parse_human(j, opts));
};
// this._value = NaN on error.
Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
this._value = NaN;
if (j instanceof Currency) {
this._value = j._value;
this._update();
return this;
}
switch (typeof j) {
case 'number':
if (!isNaN(j)) {
this.parse_number(j);
}
break;
case 'string':
if (!j || j === '0') {
// Empty string or XRP
this.parse_hex(shouldInterpretXrpAsIou
? Currency.HEX_CURRENCY_BAD
: Currency.HEX_ZERO);
break;
}
if (j === '1') {
// 'no currency'
this.parse_hex(Currency.HEX_ONE);
break;
}
if (/^[A-F0-9]{40}$/.test(j)) {
// Hex format
this.parse_hex(j);
break;
}
// match the given string to see if it's in an allowed format
const matches = j.match(this.human_RE);
if (matches) {
let currencyCode = matches[1];
// for the currency 'XRP' case
// we drop everything else that could have been provided
// e.g. 'XRP - Ripple'
if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) {
this.parse_hex(shouldInterpretXrpAsIou
? Currency.HEX_CURRENCY_BAD
: Currency.HEX_ZERO);
// early break, we can't have interest on XRP
break;
}
// the full currency is matched as it is part of the valid currency
// format, but not stored
// var full_currency = matches[2] || '';
const interest = matches[3] || '';
// interest is defined as interest per year, per annum (pa)
let percentage = interest.match(/(-?\d+\.?\d+)/);
currencyCode = currencyCode.toUpperCase();
const currencyData = utils.arraySet(20, 0);
if (percentage) {
/*
* 20 byte layout of a interest bearing currency
*
* 01 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __
* CURCODE- DATE------- RATE------------------- RESERVED---
*/
// byte 1 for type, use '1' to denote demurrage currency
currencyData[0] = 1;
// byte 2-4 for currency code
currencyData[1] = currencyCode.charCodeAt(0) & 0xff;
currencyData[2] = currencyCode.charCodeAt(1) & 0xff;
currencyData[3] = currencyCode.charCodeAt(2) & 0xff;
// byte 5-8 are for reference date, but should always be 0 so we
// won't fill it
// byte 9-16 are for the interest
percentage = parseFloat(percentage[0]);
// the interest or demurrage is expressed as a yearly (per annum)
// value
const secondsPerYear = 31536000; // 60 * 60 * 24 * 365
// Calculating the interest e-fold
// 0.5% demurrage is expressed 0.995, 0.005 less than 1
// 0.5% interest is expressed as 1.005, 0.005 more than 1
const interestEfold = secondsPerYear / Math.log(1 + percentage / 100);
const bytes = Float.toIEEE754Double(interestEfold);
for (let i = 0; i <= bytes.length; i++) {
currencyData[8 + i] = bytes[i] & 0xff;
}
// the last 4 bytes are reserved for future use, so we won't fill
// those
} else {
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
}
this.parse_bytes(currencyData);
}
break;
}
return this;
};
Currency.prototype.parse_human = function(j) {
return this.parse_json(j);
};
/**
* Recalculate internal representation.
*
* You should never need to call this.
*/
Currency.prototype._update = function() {
const bytes = this.to_bytes();
// is it 0 everywhere except 12, 13, 14?
let isZeroExceptInStandardPositions = true;
if (!bytes) {
return;
}
this._native = false;
this._type = -1;
this._interest_start = NaN;
this._interest_period = NaN;
this._iso_code = '';
for (let i = 0; i < 20; i++) {
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
&& (i === 12 || i === 13 || i === 14 || bytes[i] === 0);
}
if (isZeroExceptInStandardPositions) {
this._iso_code = String.fromCharCode(bytes[12])
+ String.fromCharCode(bytes[13])
+ String.fromCharCode(bytes[14]);
if (this._iso_code === '\u0000\u0000\u0000') {
this._native = true;
this._iso_code = 'XRP';
}
this._type = 0;
} else if (bytes[0] === 0x01) { // Demurrage currency
this._iso_code = String.fromCharCode(bytes[1])
+ String.fromCharCode(bytes[2])
+ String.fromCharCode(bytes[3]);
this._type = 1;
this._interest_start = (bytes[4] << 24) +
(bytes[5] << 16) +
(bytes[6] << 8) +
(bytes[7]);
this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16));
}
};
/**
* Returns copy.
*
* This copies code from UInt.copyTo so we do not call _update,
* bvecause to_bytes is very expensive.
*/
Currency.prototype.copyTo = function(d) {
d._value = this._value;
if (this._version_byte !== undefined) {
d._version_byte = this._version_byte;
}
if (!d.is_valid()) {
return d;
}
d._native = this._native;
d._type = this._type;
d._interest_start = this._interest_start;
d._interest_period = this._interest_period;
d._iso_code = this._iso_code;
return d;
};
// XXX Probably not needed anymore?
/*
Currency.prototype.parse_bytes = function(byte_array) {
if (Array.isArray(byte_array) && byte_array.length === 20) {
var result;
// is it 0 everywhere except 12, 13, 14?
var isZeroExceptInStandardPositions = true;
for (var i=0; i<20; i++) {
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
&& (i===12 || i===13 || i===14 || byte_array[0]===0)
}
if (isZeroExceptInStandardPositions) {
var currencyCode = String.fromCharCode(byte_array[12])
+ String.fromCharCode(byte_array[13])
+ String.fromCharCode(byte_array[14]);
if (/^[A-Z0-9]{3}$/.test(currencyCode) && currencyCode !== 'XRP' ) {
this._value = currencyCode;
} else if (currencyCode === '\0\0\0') {
this._value = 0;
} else {
this._value = NaN;
}
} else {
// XXX Should support non-standard currency codes
this._value = NaN;
}
} else {
this._value = NaN;
}
return this;
};
*/
Currency.prototype.is_native = function() {
return this._native;
};
/**
* @return {Boolean} whether this currency is an interest-bearing currency
*/
Currency.prototype.has_interest = function() {
return this._type === 1
&& !isNaN(this._interest_start)
&& !isNaN(this._interest_period);
};
/**
*
* @param {number} referenceDate_ number of seconds since the Ripple Epoch
* (0:00 on January 1, 2000 UTC) used to calculate the
* interest over provided interval pass in one years
* worth of seconds to ge the yearly interest
* @returns {number} interest for provided interval, can be negative for
* demurred currencies
*/
Currency.prototype.get_interest_at = function(referenceDate_) {
if (!this.has_interest()) {
return 0;
}
let referenceDate = referenceDate_;
// use one year as a default period
if (!referenceDate) {
referenceDate = this._interest_start + 3600 * 24 * 365;
}
if (referenceDate instanceof Date) {
referenceDate = utils.fromTimestamp(referenceDate.getTime());
}
// calculate interest by e-fold number
return Math.exp((referenceDate - this._interest_start)
/ this._interest_period);
};
Currency.prototype.get_interest_percentage_at = function(referenceDate,
decimals
) {
let interest = this.get_interest_at(referenceDate, decimals);
// convert to percentage
interest = (interest * 100) - 100;
const decimalMultiplier = decimals ? Math.pow(10, decimals) : 100;
// round to two decimals behind the dot
return Math.round(interest * decimalMultiplier) / decimalMultiplier;
};
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
//
// We could be doing further checks into the internal format of the
// currency data, since there are some values that are invalid.
//
// Currency.prototype.is_valid = function() {
// return UInt.prototype.is_valid() && ...;
// };
Currency.prototype.to_json = function(opts = {}) {
if (!this.is_valid()) {
// XXX This is backwards compatible behavior, but probably not very good.
function getISOCode(hexCurrency) {
const bytes = new Buffer(hexCurrency, 'hex');
if (_.every(bytes, octet => octet === 0)) {
return 'XRP';
}
let currency;
const fullName = opts && opts.full_name ? ' - ' + opts.full_name : '';
opts.show_interest = opts.show_interest !== undefined
? opts.show_interest
: this.has_interest();
if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) {
currency = this._iso_code + fullName;
if (opts.show_interest) {
const decimals = !isNaN(opts.decimals) ? opts.decimals : undefined;
const interestPercentage = this.has_interest()
? this.get_interest_percentage_at(
this._interest_start + 3600 * 24 * 365, decimals
)
: 0;
currency += ' (' + interestPercentage + '%pa)';
}
} else {
// Fallback to returning the raw currency hex
currency = this.to_hex();
// XXX This is to maintain backwards compatibility, but it is very, very
// odd behavior, so we should deprecate it and get rid of it as soon as
// possible.
if (currency === Currency.HEX_ONE) {
currency = 1;
}
if (!_.every(bytes, (octet, i) => octet === 0 || (i >= 12 && i <= 14))) {
return null;
}
const code = String.fromCharCode(bytes[12])
+ String.fromCharCode(bytes[13])
+ String.fromCharCode(bytes[14]);
return isISOCode(code) ? code : null;
}
return currency;
};
function normalizeCurrency(currency) {
if (isISOCode(currency.toUpperCase())) {
return currency.toUpperCase();
} else if (isHexCurrency(currency)) {
const code = getISOCode(currency);
return code === null ? currency.toUpperCase() : code;
}
throw new Error('invalid currency');
}
Currency.prototype.to_human = function(opts) {
// to_human() will always print the human-readable currency code if available.
return this.to_json(opts);
};
function toHexCurrency(currency) {
if (isISOCode(currency)) {
const bytes = new Buffer(20);
bytes.fill(0);
if (currency !== 'XRP') {
bytes[12] = currency.charCodeAt(0);
bytes[13] = currency.charCodeAt(1);
bytes[14] = currency.charCodeAt(2);
}
return bytes.toString('hex').toUpperCase();
} else if (isHexCurrency(currency)) {
return currency.toUpperCase();
}
throw new Error('invalid currency');
}
Currency.prototype.get_iso = function() {
return this._iso_code;
};
function isValidCurrency(currency) {
return isISOCode(currency.toUpperCase()) || isHexCurrency(currency);
}
exports.Currency = Currency;
exports.normalizeCurrency = normalizeCurrency;
exports.isValidCurrency = isValidCurrency;
exports.toHexCurrency = toHexCurrency;

View File

@@ -1,38 +0,0 @@
'use strict';
// TODO: move in helpers from serializedtypes to utils
function toBytes(n) {
return [n >>> 24, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];
}
/**
* Prefix for hashing functions.
*
* These prefixes are inserted before the source material used to
* generate various hashes. This is done to put each hash in its own
* "space." This way, two different types of objects with the
* same binary data will produce different hashes.
*
* Each prefix is a 4-byte value with the last byte set to zero
* and the first three bytes formed from the ASCII equivalent of
* some arbitrary string. For example "TXN".
*/
// transaction plus signature to give transaction ID
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
// transaction plus metadata
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
// inner node in tree
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
// leaf node in tree
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
// inner transaction to sign
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
// inner transaction to sign (TESTNET)
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
// inner transaction to multisign
exports.HASH_TX_MULTISIGN = 0x534D5400; // 'SMT'
Object.keys(exports).forEach(function(k) {
exports[k + '_BYTES'] = toBytes(exports[k]);
});

View File

@@ -1,107 +0,0 @@
// Convert a JavaScript number to IEEE-754 Double Precision
// value represented as an array of 8 bytes (octets)
//
// Based on:
// http://cautionsingularityahead.blogspot.com/2010/04/javascript-and-ieee754-redux.html
//
// Found and modified from:
// https://gist.github.com/bartaz/1119041
var Float = exports.Float = {};
Float.toIEEE754 = function(v, ebits, fbits) {
var bias = (1 << (ebits - 1)) - 1;
// Compute sign, exponent, fraction
var s, e, f;
if (isNaN(v)) {
e = (1 << bias) - 1; f = 1; s = 0;
}
else if (v === Infinity || v === -Infinity) {
e = (1 << bias) - 1; f = 0; s = (v < 0) ? 1 : 0;
}
else if (v === 0) {
e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
}
else {
s = v < 0;
v = Math.abs(v);
if (v >= Math.pow(2, 1 - bias)) {
var ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
e = ln + bias;
f = v * Math.pow(2, fbits - ln) - Math.pow(2, fbits);
}
else {
e = 0;
f = v / Math.pow(2, 1 - bias - fbits);
}
}
// Pack sign, exponent, fraction
var i, bits = [];
for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = Math.floor(f / 2); }
for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = Math.floor(e / 2); }
bits.push(s ? 1 : 0);
bits.reverse();
var str = bits.join('');
// Bits to bytes
var bytes = [];
while (str.length) {
bytes.push(parseInt(str.substring(0, 8), 2));
str = str.substring(8);
}
return bytes;
}
Float.fromIEEE754 = function(bytes, ebits, fbits) {
// Bytes to bits
var bits = [];
for (var i = bytes.length; i; i -= 1) {
var byte = bytes[i - 1];
for (var j = 8; j; j -= 1) {
bits.push(byte % 2 ? 1 : 0); byte = byte >> 1;
}
}
bits.reverse();
var str = bits.join('');
// Unpack sign, exponent, fraction
var bias = (1 << (ebits - 1)) - 1;
var s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
var e = parseInt(str.substring(1, 1 + ebits), 2);
var f = parseInt(str.substring(1 + ebits), 2);
// Produce number
if (e === (1 << ebits) - 1) {
return f !== 0 ? NaN : s * Infinity;
}
else if (e > 0) {
return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits));
}
else if (f !== 0) {
return s * Math.pow(2, -(bias-1)) * (f / Math.pow(2, fbits));
}
else {
return s * 0;
}
}
Float.fromIEEE754Double = function(b) { return Float.fromIEEE754(b, 11, 52); }
Float.toIEEE754Double = function(v) { return Float.toIEEE754(v, 11, 52); }
Float.fromIEEE754Single = function(b) { return Float.fromIEEE754(b, 8, 23); }
Float.toIEEE754Single = function(v) { return Float.toIEEE754(v, 8, 23); }
// Convert array of octets to string binary representation
// by bartaz
Float.toIEEE754DoubleString = function(v) {
return exports.toIEEE754Double(v)
.map(function(n){ for(n = n.toString(2);n.length < 8;n="0"+n); return n })
.join('')
.replace(/(.)(.{11})(.{52})/, "$1 $2 $3")
}

View File

@@ -5,26 +5,17 @@ exports.Amount = require('./amount').Amount;
exports.Account = require('./account').Account;
exports.Transaction = require('./transaction').Transaction;
exports.Currency = require('./currency').Currency;
exports.Base = require('./base').Base;
exports.UInt128 = require('./uint128').UInt128;
exports.UInt160 = require('./uint160').UInt160;
exports.UInt256 = require('./uint256').UInt256;
exports.Meta = require('./meta').Meta;
exports.SerializedObject = require('./serializedobject').SerializedObject;
exports.RippleError = require('./rippleerror').RippleError;
exports.binformat = require('./binformat');
exports.utils = require('./utils');
exports.Server = require('./server').Server;
exports.Ledger = require('./ledger').Ledger;
exports.TransactionQueue = require('./transactionqueue').TransactionQueue;
exports.convertBase = require('./baseconverter');
exports._test = {
Log: require('./log'),
PathFind: require('./pathfind').PathFind,
TransactionManager: require('./transactionmanager').TransactionManager,
TransactionQueue: require('./transactionqueue').TransactionQueue,
RangeSet: require('./rangeset').RangeSet,
HashPrefixes: require('./hashprefixes')
OrderbookUtils: require('./orderbookutils'),
constants: require('./constants')
};
exports.types = require('./serializedtypes');

View File

@@ -1,180 +0,0 @@
'use strict';
const BigNumber = require('bignumber.js');
const Transaction = require('./transaction').Transaction;
const SHAMap = require('./shamap').SHAMap;
const SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
const SerializedObject = require('./serializedobject').SerializedObject;
const stypes = require('./serializedtypes');
const UInt160 = require('./uint160').UInt160;
const Currency = require('./currency').Currency;
function Ledger() {
this.ledger_json = {};
}
Ledger.from_json = function(v) {
const ledger = new Ledger();
ledger.parse_json(v);
return ledger;
};
Ledger.space = require('./ledgerspaces');
/**
* Generate the key for an AccountRoot entry.
*
* @param {String|UInt160} accountArg - Ripple Account
* @return {UInt256}
*/
Ledger.calcAccountRootEntryHash =
Ledger.prototype.calcAccountRootEntryHash = function(accountArg) {
const account = UInt160.from_json(accountArg);
const index = new SerializedObject();
index.append([0, Ledger.space.account.charCodeAt(0)]);
index.append(account.to_bytes());
return index.hash();
};
/**
* Generate the key for an Offer entry.
*
* @param {String|UInt160} accountArg - Ripple Account
* @param {Number} sequence - Sequence number of the OfferCreate transaction
* that instantiated this offer.
* @return {UInt256}
*/
Ledger.calcOfferEntryHash =
Ledger.prototype.calcOfferEntryHash = function(accountArg, sequence) {
const account = UInt160.from_json(accountArg);
const index = new SerializedObject();
index.append([0, Ledger.space.offer.charCodeAt(0)]);
index.append(account.to_bytes());
stypes.Int32.serialize(index, sequence);
return index.hash();
};
/**
* Generate the key for a RippleState entry.
*
* The ordering of the two account parameters does not matter.
*
* @param {String|UInt160} _account1 - First Ripple Account
* @param {String|UInt160} _account2 - Second Ripple Account
* @param {String|Currency} _currency - The currency code
* @return {UInt256}
*/
Ledger.calcRippleStateEntryHash =
Ledger.prototype.calcRippleStateEntryHash = function(
_account1, _account2, _currency) {
const currency = Currency.from_json(_currency);
const account1 = UInt160.from_json(_account1);
const account2 = UInt160.from_json(_account2);
if (!account1.is_valid()) {
throw new Error('Invalid first account');
}
if (!account2.is_valid()) {
throw new Error('Invalid second account');
}
if (!currency.is_valid()) {
throw new Error('Invalid currency');
}
const swap = account1.greater_than(account2);
const lowAccount = swap ? account2 : account1;
const highAccount = swap ? account1 : account2;
const index = new SerializedObject();
index.append([0, Ledger.space.rippleState.charCodeAt(0)]);
index.append(lowAccount.to_bytes());
index.append(highAccount.to_bytes());
index.append(currency.to_bytes());
return index.hash();
};
Ledger.prototype.parse_json = function(v) {
this.ledger_json = v;
};
Ledger.prototype.calc_tx_hash = function() {
const tx_map = new SHAMap();
this.ledger_json.transactions.forEach(function(tx_json) {
const tx = Transaction.from_json(tx_json);
const meta = SerializedObject.from_json(tx_json.metaData);
const data = new SerializedObject();
stypes.VariableLength.serialize(data, tx.serialize().to_hex());
stypes.VariableLength.serialize(data, meta.to_hex());
tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
});
return tx_map.hash();
};
/**
* @param {Object} options - object
*
* @param {Boolean} [options.sanity_test=false] - If `true`, will serialize each
* accountState item to binary and then back to json before finally
* serializing for hashing. This is mostly to expose any issues with
* ripple-lib's binary <--> json codecs.
*
* @return {UInt256} - hash of shamap
*/
Ledger.prototype.calc_account_hash = function(options) {
const account_map = new SHAMap();
let erred;
this.ledger_json.accountState.forEach(function(le) {
let data = SerializedObject.from_json(le);
let json;
if (options && options.sanity_test) {
try {
json = data.to_json();
data = SerializedObject.from_json(json);
} catch (e) {
console.log('account state item: ', le);
console.log('to_json() ', json);
console.log('exception: ', e);
erred = true;
}
}
account_map.add_item(le.index, data, SHAMapTreeNode.TYPE_ACCOUNT_STATE);
});
if (erred) {
throw new Error('There were errors with sanity_test'); // all logged above
}
return account_map.hash();
};
// see rippled Ledger::updateHash()
Ledger.calculateLedgerHash =
Ledger.prototype.calculateLedgerHash = function(ledgerHeader) {
const so = new SerializedObject();
const prefix = 0x4C575200;
const totalCoins = (new BigNumber(ledgerHeader.total_coins)).toString(16);
stypes.Int32.serialize(so, Number(ledgerHeader.ledger_index));
stypes.Int64.serialize(so, totalCoins);
stypes.Hash256.serialize(so, ledgerHeader.parent_hash);
stypes.Hash256.serialize(so, ledgerHeader.transaction_hash);
stypes.Hash256.serialize(so, ledgerHeader.account_hash);
stypes.Int32.serialize(so, ledgerHeader.parent_close_time);
stypes.Int32.serialize(so, ledgerHeader.close_time);
stypes.Int8.serialize(so, ledgerHeader.close_time_resolution);
stypes.Int8.serialize(so, ledgerHeader.close_flags);
return so.hash(prefix).to_hex();
};
exports.Ledger = Ledger;

View File

@@ -1,22 +0,0 @@
/**
* Ripple ledger namespace prefixes.
*
* The Ripple ledger is a key-value store. In order to avoid name collisions,
* names are partitioned into namespaces.
*
* Each namespace is just a single character prefix.
*/
module.exports = {
account : 'a',
dirNode : 'd',
generatorMap : 'g',
nickname : 'n',
rippleState : 'r',
offer : 'o', // Entry for an offer.
ownerDir : 'O', // Directory of things owned by an account.
bookDir : 'B', // Directory of order books.
contract : 'c',
skipList : 's',
amendment : 'f',
feeSettings : 'e'
};

View File

@@ -55,7 +55,7 @@ Log.makeLevel = function(level) {
return function() {
const args = Array.prototype.slice.apply(arguments);
args[0] = this._prefix + args[0];
Log.engine.logObject.apply(Log, [level].concat(args[0], [args.slice(2)]));
Log.engine.logObject.apply(Log, [level].concat(args[0], [args.slice(1)]));
};
};

View File

@@ -1,7 +1,6 @@
'use strict';
const extend = require('extend');
const utils = require('./utils');
const UInt160 = require('./uint160').UInt160;
const Amount = require('./amount').Amount;
const ACCOUNT_ZERO = require('./constants').ACCOUNT_ZERO;
const {isValidAddress} = require('ripple-address-codec');
@@ -151,7 +150,7 @@ Meta.prototype.getAffectedAccounts = function() {
for (const fieldName in fields) {
const field = fields[fieldName];
if (this.isAccountField(fieldName) && UInt160.is_valid(field)) {
if (this.isAccountField(fieldName) && isValidAddress(field)) {
accounts.push(field);
} else if (
Meta.AMOUNT_FIELDS_AFFECTING_ISSUER.indexOf(fieldName) !== -1) {
@@ -185,8 +184,8 @@ Meta.prototype.getAffectedBooks = function() {
const gets = Amount.from_json(node.fields.TakerGets);
const pays = Amount.from_json(node.fields.TakerPays);
let getsKey = gets.currency().to_json();
let paysKey = pays.currency().to_json();
let getsKey = gets.currency();
let paysKey = pays.currency();
if (getsKey !== 'XRP') {
getsKey += '/' + gets.issuer();

View File

@@ -16,20 +16,14 @@ const extend = require('extend');
const assert = require('assert');
const async = require('async');
const EventEmitter = require('events').EventEmitter;
const {isValidAddress} = require('ripple-address-codec');
const Amount = require('./amount').Amount;
const UInt160 = require('./uint160').UInt160;
const Currency = require('./currency').Currency;
const AutobridgeCalculator = require('./autobridgecalculator');
const OrderBookUtils = require('./orderbookutils');
const log = require('./log').internal.sub('orderbook');
const {IOUValue} = require('ripple-lib-value');
function _sortOffers(a, b) {
const aQuality = OrderBookUtils.getOfferQuality(a, this._currencyGets);
const bQuality = OrderBookUtils.getOfferQuality(b, this._currencyGets);
return aQuality._value.comparedTo(bQuality._value);
}
const RippleError = require('./rippleerror').RippleError;
const {normalizeCurrency, isValidCurrency} = require('./currency');
function _sortOffersQuick(a, b) {
return a.qualityHex.localeCompare(b.qualityHex);
@@ -55,9 +49,9 @@ function OrderBook(remote,
const self = this;
this._remote = remote;
this._currencyGets = Currency.from_json(currencyGets);
this._currencyGets = normalizeCurrency(currencyGets);
this._issuerGets = issuerGets;
this._currencyPays = Currency.from_json(currencyPays);
this._currencyPays = normalizeCurrency(currencyPays);
this._issuerPays = issuerPays;
this._key = key;
this._subscribed = false;
@@ -93,11 +87,10 @@ function OrderBook(remote,
this._calculatorRunning = false;
this.sortOffers = this._currencyGets.has_interest() ?
_sortOffers.bind(this) : _sortOffersQuick;
this.sortOffers = _sortOffersQuick;
this._isAutobridgeable = !this._currencyGets.is_native()
&& !this._currencyPays.is_native();
this._isAutobridgeable = this._currencyGets !== 'XRP'
&& this._currencyPays !== 'XRP';
function computeAutobridgedOffersWrapperOne() {
if (!self._gotOffersFromLegOne) {
@@ -256,6 +249,9 @@ OrderBook.offerRewrite = function(offer) {
/**
* Initialize orderbook. Get orderbook offers and subscribe to transactions
* @api private
* NOTE: this method is not meant to be publicly used
* and it does not work for autobridged books since
* it does not add listeners for them
*/
OrderBook.prototype.subscribe = function() {
@@ -333,21 +329,23 @@ OrderBook.prototype.destroy = function() {
* Request orderbook entries from server
*
* @param {Function} callback
* @param {boolean} internal - internal request made on 'subscribe'
*/
OrderBook.prototype.requestOffers = function(callback = function() {},
internal = false) {
const self = this;
if (!this._remote.isConnected()) {
if (!this._remote.isConnected() && !internal) {
// do not make request if not online.
// that requests will be queued and
// eventually all of them will fire back
callback(new RippleError('remote is offline'));
return undefined;
}
if (!this._shouldSubscribe) {
callback(new Error('Should not request offers'));
callback(new RippleError('Should not request offers'));
return undefined;
}
@@ -355,7 +353,7 @@ OrderBook.prototype.requestOffers = function(callback = function() {},
log.info('requesting offers', this._key);
}
this._synchronized = false;
this._synced = false;
if (this._isAutobridgeable && !internal) {
this._gotOffersFromLegOne = false;
@@ -375,7 +373,7 @@ OrderBook.prototype.requestOffers = function(callback = function() {},
if (!Array.isArray(res.offers)) {
// XXX What now?
callback(new Error('Invalid response'));
callback(new RippleError('Invalid response'));
self.emit('model', []);
return;
}
@@ -426,7 +424,7 @@ OrderBook.prototype.requestTransferRate = function(callback) {
const self = this;
if (this._currencyGets.is_native()) {
if (this._currencyGets === 'XRP') {
// Transfer rate is default for the native currency
this._issuerTransferRate = OrderBook.DEFAULT_TRANSFER_RATE;
@@ -572,7 +570,7 @@ OrderBook.prototype.applyTransferRate = function(balance) {
OrderBook.prototype.getOwnerFunds = function(account) {
if (this.hasOwnerFunds(account)) {
if (this._currencyGets.is_native()) {
if (this._currencyGets === 'XRP') {
return Amount.from_json(this._ownerFunds[account]);
}
return OrderBookUtils.normalizeAmount(this._ownerFunds[account]);
@@ -692,7 +690,7 @@ OrderBook.prototype.getOwnerOfferTotal = function(account) {
if (amount) {
return amount;
}
if (this._currencyGets.is_native()) {
if (this._currencyGets === 'XRP') {
return OrderBook.ZERO_NATIVE_AMOUNT.clone();
}
return OrderBook.ZERO_NORMALIZED_AMOUNT.clone();
@@ -706,7 +704,7 @@ OrderBook.prototype.getOwnerOfferTotal = function(account) {
*/
OrderBook.prototype.resetOwnerOfferTotal = function(account) {
if (this._currencyGets.is_native()) {
if (this._currencyGets === 'XRP') {
this._ownerOffersTotal[account] = OrderBook.ZERO_NATIVE_AMOUNT.clone();
} else {
this._ownerOffersTotal[account] = OrderBook.ZERO_NORMALIZED_AMOUNT.clone();
@@ -743,12 +741,12 @@ OrderBook.prototype.setOfferFundedAmount = function(offer) {
} else if (previousOfferSum.compareTo(fundedAmount) < 0) {
offer.taker_gets_funded = fundedAmount.subtract(previousOfferSum).to_text();
const quality = OrderBookUtils.getOfferQuality(offer, this._currencyGets);
const quality = OrderBookUtils.getOfferQuality(offer);
const takerPaysFunded = quality.multiply(
OrderBookUtils.getOfferTakerGetsFunded(offer)
);
offer.taker_pays_funded = this._currencyPays.is_native()
offer.taker_pays_funded = (this._currencyPays === 'XRP')
? String(Math.floor(takerPaysFunded.to_number()))
: takerPaysFunded.to_json().value;
} else {
@@ -795,7 +793,7 @@ OrderBook.prototype.parseAccountBalanceFromNode = function(node) {
assert(!isNaN(result.balance), 'node has an invalid balance');
if (this._validAccounts[result.Account] === undefined) {
assert(UInt160.is_valid(result.account), 'node has an invalid account');
assert(isValidAddress(result.account), 'node has an invalid account');
this._validAccounts[result.Account] = true;
this._validAccountsCount++;
}
@@ -819,12 +817,12 @@ OrderBook.prototype.isBalanceChangeNode = function(node) {
}
// Check if taker gets currency is native and balance is not a number
if (this._currencyGets.is_native()) {
if (this._currencyGets === 'XRP') {
return !isNaN(node.fields.Balance);
}
// Check if balance change is not for taker gets currency
if (node.fields.Balance.currency !== this._currencyGets.to_json()) {
if (node.fields.Balance.currency !== this._currencyGets) {
return false;
}
@@ -875,7 +873,7 @@ OrderBook.prototype.onTransaction = function(transaction) {
OrderBook.prototype.updateFundedAmounts = function(transaction) {
const self = this;
if (!this._currencyGets.is_native() && !this._issuerTransferRate) {
if (this._currencyGets !== 'XRP' && !this._issuerTransferRate) {
if (this._remote.trace) {
log.info('waiting for transfer rate');
}
@@ -895,7 +893,7 @@ OrderBook.prototype.updateFundedAmounts = function(transaction) {
const affectedNodes = transaction.mmeta.getNodes({
nodeType: 'ModifiedNode',
entryType: this._currencyGets.is_native() ? 'AccountRoot' : 'RippleState'
entryType: this._currencyGets === 'XRP' ? 'AccountRoot' : 'RippleState'
});
_.each(affectedNodes, function(node) {
@@ -920,8 +918,6 @@ OrderBook.prototype.updateFundedAmounts = function(transaction) {
*/
OrderBook.prototype.updateOwnerOffersFundedAmount = function(account) {
// assert(UInt160.is_valid(account), 'Account is invalid');
const self = this;
if (!this.hasOwnerFunds(account)) {
@@ -1008,15 +1004,14 @@ OrderBook.prototype.notify = function(transaction) {
}
let takerGetsTotal = Amount.from_json(
'0' + ((Currency.from_json(this._currencyGets).is_native())
'0' + (this._currencyGets === 'XRP'
? ''
: ('/' + this._currencyGets.to_human() + '/' + this._issuerGets))
: ('/' + this._currencyGets + '/' + this._issuerGets))
);
let takerPaysTotal = Amount.from_json(
'0' + ((Currency.from_json(this._currencyPays).is_native())
? ''
: ('/' + this._currencyPays.to_human() + '/' + this._issuerPays))
'0' + (this._currencyPays === 'XRP' ? ''
: ('/' + this._currencyPays + '/' + this._issuerPays))
);
const isOfferCancel =
@@ -1027,7 +1022,7 @@ OrderBook.prototype.notify = function(transaction) {
switch (node.nodeType) {
case 'DeletedNode':
if (self._validAccounts[node.fields.Account] === undefined) {
assert(UInt160.is_valid(node.fields.Account),
assert(isValidAddress(node.fields.Account),
'node has an invalid account');
self._validAccounts[node.fields.Account] = true;
self._validAccountsCount++;
@@ -1043,7 +1038,7 @@ OrderBook.prototype.notify = function(transaction) {
case 'ModifiedNode':
if (self._validAccounts[node.fields.Account] === undefined) {
assert(UInt160.is_valid(node.fields.Account),
assert(isValidAddress(node.fields.Account),
'node has an invalid account');
self._validAccounts[node.fields.Account] = true;
self._validAccountsCount++;
@@ -1061,7 +1056,7 @@ OrderBook.prototype.notify = function(transaction) {
case 'CreatedNode':
if (self._validAccounts[node.fields.Account] === undefined) {
assert(UInt160.is_valid(node.fields.Account),
assert(isValidAddress(node.fields.Account),
'node has an invalid account');
self._validAccounts[node.fields.Account] = true;
self._validAccountsCount++;
@@ -1113,27 +1108,10 @@ OrderBook.prototype.insertOffer = function(node) {
const originalLength = this._offers.length;
if (!this._currencyGets.has_interest()) {
// use fast path
for (let i = 0; i < originalLength; i++) {
if (offer.qualityHex <= this._offers[i].qualityHex) {
this._offers.splice(i, 0, offer);
break;
}
}
} else {
for (let i = 0; i < originalLength; i++) {
const quality = OrderBookUtils.getOfferQuality(offer, this._currencyGets);
const existingOfferQuality = OrderBookUtils.getOfferQuality(
this._offers[i],
this._currencyGets
);
if (quality.compareTo(existingOfferQuality) <= 0) {
this._offers.splice(i, 0, offer);
break;
}
for (let i = 0; i < originalLength; i++) {
if (offer.qualityHex <= this._offers[i].qualityHex) {
this._offers.splice(i, 0, offer);
break;
}
}
@@ -1159,10 +1137,7 @@ OrderBook.prototype.insertOffer = function(node) {
*/
OrderBook.prototype.normalizeAmount = function(currency, amountObj) {
const value = currency.is_native()
? amountObj
: amountObj.value;
const value = (currency === 'XRP') ? amountObj : amountObj.value;
return OrderBookUtils.normalizeAmount(value);
};
@@ -1249,7 +1224,7 @@ OrderBook.prototype.setOffers = function(offers) {
offer = OrderBook.offerRewrite(offers[i]);
if (this._validAccounts[offer.Account] === undefined) {
assert(UInt160.is_valid(offer.Account), 'Account is invalid');
assert(isValidAddress(offer.Account), 'Account is invalid');
this._validAccounts[offer.Account] = true;
this._validAccountsCount++;
}
@@ -1321,18 +1296,18 @@ OrderBook.prototype.toJSON =
OrderBook.prototype.to_json = function() {
const json = {
taker_gets: {
currency: this._currencyGets.to_hex()
currency: this._currencyGets
},
taker_pays: {
currency: this._currencyPays.to_hex()
currency: this._currencyPays
}
};
if (!this._currencyGets.is_native()) {
if (this._currencyGets !== 'XRP') {
json.taker_gets.issuer = this._issuerGets;
}
if (!this._currencyPays.is_native()) {
if (this._currencyPays !== 'XRP') {
json.taker_pays.issuer = this._issuerPays;
}
@@ -1352,11 +1327,11 @@ OrderBook.prototype.isValid =
OrderBook.prototype.is_valid = function() {
// XXX Should check for same currency (non-native) && same issuer
return (
this._currencyPays && this._currencyPays.is_valid() &&
(this._currencyPays.is_native() || UInt160.is_valid(this._issuerPays)) &&
this._currencyGets && this._currencyGets.is_valid() &&
(this._currencyGets.is_native() || UInt160.is_valid(this._issuerGets)) &&
!(this._currencyPays.is_native() && this._currencyGets.is_native())
this._currencyPays && isValidCurrency(this._currencyPays) &&
(this._currencyPays === 'XRP' || isValidAddress(this._issuerPays)) &&
this._currencyGets && isValidCurrency(this._currencyGets) &&
(this._currencyGets === 'XRP' || isValidAddress(this._issuerGets)) &&
!(this._currencyPays === 'XRP' && this._currencyGets === 'XRP')
);
};
@@ -1367,7 +1342,7 @@ OrderBook.prototype.is_valid = function() {
OrderBook.prototype.computeAutobridgedOffers = function(callback = function() {}
) {
assert(!this._currencyGets.is_native() && !this._currencyPays.is_native(),
assert(this._currencyGets !== 'XRP' && this._currencyPays !== 'XRP',
'Autobridging is only for IOU:IOU orderbooks');
@@ -1392,7 +1367,7 @@ OrderBook.prototype.computeAutobridgedOffers = function(callback = function() {}
OrderBook.prototype.computeAutobridgedOffersWrapper = function() {
if (!this._gotOffersFromLegOne || !this._gotOffersFromLegTwo ||
!this._synchronized || this._destroyed || this._calculatorRunning
!this._synced || this._destroyed || this._calculatorRunning
) {
return;
}

View File

@@ -2,12 +2,10 @@
const _ = require('lodash');
const assert = require('assert');
const SerializedObject = require('./serializedobject').SerializedObject;
const Types = require('./serializedtypes');
const constants = require('./constants');
const Amount = require('./amount').Amount;
const Currency = require('./currency').Currency;
const UInt160 = require('./uint160').UInt160;
const {IOUValue} = require('ripple-lib-value');
const binary = require('ripple-binary-codec');
const OrderBookUtils = {};
function assertValidNumber(number, message) {
@@ -22,14 +20,9 @@ function assertValidNumber(number, message) {
* @return JSON amount object
*/
function createAmount(value, currency_, counterparty_) {
const currency = currency_ instanceof Currency ?
currency_ :
Currency.from_json(currency_);
const counterparty = counterparty_ instanceof UInt160 ?
counterparty_.to_json() : counterparty_;
function createAmount(value, currency, counterparty) {
assert(_.isString(counterparty), 'counterparty must be a string');
assert(_.isString(currency), 'currency must be a string');
return Amount.from_components_unsafe(new IOUValue(value),
currency, counterparty, false);
@@ -111,27 +104,10 @@ OrderBookUtils.getOfferTakerGets = function(offer, currency_, issuer_) {
* @param {Currency} currencyGets
*/
OrderBookUtils.getOfferQuality = function(offer, currencyGets, currency_,
issuer_
) {
let amount;
if (currencyGets.has_interest()) {
// XXX Should use Amount#from_quality
amount = Amount.from_json(
offer.TakerPays
).ratio_human(offer.TakerGets, {
reference_date: new Date()
});
} else {
const currency = currency_ || getCurrencyFromOffer(offer);
const issuer = issuer_ || getIssuerFromOffer(offer);
amount = createAmount(offer.quality, currency, issuer);
}
return amount;
OrderBookUtils.getOfferQuality = function(offer, currency_, issuer_) {
const currency = currency_ || getCurrencyFromOffer(offer);
const issuer = issuer_ || getIssuerFromOffer(offer);
return createAmount(offer.quality, currency, issuer);
};
/**
@@ -145,11 +121,7 @@ OrderBookUtils.getOfferQuality = function(offer, currencyGets, currency_,
OrderBookUtils.convertOfferQualityToHex = function(quality) {
assert(quality instanceof Amount, 'Quality is not an amount');
const so = new SerializedObject();
Types.Quality.serialize(so, quality.to_text());
return so.to_hex();
return OrderBookUtils.convertOfferQualityToHex(quality.to_text());
};
/**
@@ -162,17 +134,12 @@ OrderBookUtils.convertOfferQualityToHex = function(quality) {
*/
OrderBookUtils.convertOfferQualityToHexFromText = function(quality) {
const so = new SerializedObject();
Types.Quality.serialize(so, quality);
return so.to_hex();
return binary.encodeQuality(quality);
};
OrderBookUtils.CURRENCY_ONE = constants.CURRENCY_ONE;
OrderBookUtils.CURRENCY_ONE = Currency.from_json(1);
OrderBookUtils.ISSUER_ONE = UInt160.from_json(1);
OrderBookUtils.ISSUER_ONE = constants.ACCOUNT_ONE;
/**
*

View File

@@ -18,23 +18,22 @@ const assert = require('assert');
const _ = require('lodash');
const LRU = require('lru-cache');
const async = require('async');
const constants = require('./constants');
const EventEmitter = require('events').EventEmitter;
const Server = require('./server').Server;
const Request = require('./request').Request;
const Amount = require('./amount').Amount;
const Currency = require('./currency').Currency;
const UInt160 = require('./uint160').UInt160;
const UInt256 = require('./uint256').UInt256;
const {normalizeCurrency} = require('./currency');
const Transaction = require('./transaction').Transaction;
const Account = require('./account').Account;
const Meta = require('./meta').Meta;
const OrderBook = require('./orderbook').OrderBook;
const PathFind = require('./pathfind').PathFind;
const SerializedObject = require('./serializedobject').SerializedObject;
const RippleError = require('./rippleerror').RippleError;
const utils = require('./utils');
const hashprefixes = require('./hashprefixes');
const log = require('./log').internal.sub('remote');
const {isValidAddress} = require('ripple-address-codec');
const binary = require('ripple-binary-codec');
export type GetLedgerSequenceCallback = (err?: ?Error, index?: number) => void;
@@ -138,6 +137,9 @@ function Remote(options = {}) {
if (typeof this.submission_timeout !== 'number') {
throw new TypeError('submission_timeout must be a number');
}
if (typeof this.pathfind_timeout !== 'number') {
throw new TypeError('pathfind_timeout must be a number');
}
if (typeof this.automatic_resubmission !== 'boolean') {
throw new TypeError('automatic_resubmission must be a boolean');
}
@@ -197,6 +199,7 @@ Remote.DEFAULTS = {
max_fee: 1000000, // 1 XRP
max_attempts: 10,
submission_timeout: 1000 * 20,
pathfind_timeout: 1000 * 10,
automatic_resubmission: true,
last_ledger_offset: 3,
servers: [ ],
@@ -1188,9 +1191,13 @@ Remote.prototype.requestTransaction = function(options, callback) {
* @throws {Error} if a marker is provided, but no ledger_index or ledger_hash
*/
function isValidLedgerHash(hash) {
return /^[A-F0-9]{64}$/.test(hash);
}
Remote.prototype._accountRequest = function(command, options, callback) {
if (options.marker) {
if (!(Number(options.ledger) > 0) && !UInt256.is_valid(options.ledger)) {
if (!(Number(options.ledger) > 0) && !isValidLedgerHash(options.ledger)) {
throw new Error(
'A ledger_index or ledger_hash must be provided when using a marker');
}
@@ -1198,11 +1205,11 @@ Remote.prototype._accountRequest = function(command, options, callback) {
const request = new Request(this, command);
request.message.account = UInt160.json_rewrite(options.account);
request.message.account = options.account;
request.selectLedger(options.ledger);
if (UInt160.is_valid(options.peer)) {
request.message.peer = UInt160.json_rewrite(options.peer);
if (isValidAddress(options.peer)) {
request.message.peer = options.peer;
}
if (!isNaN(options.limit)) {
@@ -1404,27 +1411,26 @@ Remote.prototype.requestAccountTx = function(options, callback) {
*/
Remote.parseBinaryAccountTransaction = function(transaction) {
const tx_obj = new SerializedObject(transaction.tx_blob);
const tx_obj_json = tx_obj.to_json();
const meta = new SerializedObject(transaction.meta).to_json();
const tx_json = binary.decode(transaction.tx_blob);
const meta = binary.decode(transaction.meta);
const tx_result = {
validated: transaction.validated
};
tx_result.meta = meta;
tx_result.tx = tx_obj_json;
tx_result.tx.hash = tx_obj.hash(hashprefixes.HASH_TX_ID).to_hex();
tx_result.tx = tx_json;
tx_result.tx.hash = Transaction.from_json(tx_json).hash();
tx_result.tx.ledger_index = transaction.ledger_index;
tx_result.tx.inLedger = transaction.ledger_index;
if (typeof meta.DeliveredAmount === 'object') {
tx_result.meta.delivered_amount = meta.DeliveredAmount;
} else {
switch (typeof tx_obj_json.Amount) {
switch (typeof tx_json.Amount) {
case 'string':
case 'object':
tx_result.meta.delivered_amount = tx_obj_json.Amount;
tx_result.meta.delivered_amount = tx_json.Amount;
break;
}
}
@@ -1433,10 +1439,10 @@ Remote.parseBinaryAccountTransaction = function(transaction) {
};
Remote.parseBinaryTransaction = function(transaction) {
const tx_obj = new SerializedObject(transaction.tx).to_json();
const meta = new SerializedObject(transaction.meta).to_json();
const tx_json = binary.decode(transaction.tx);
const meta = binary.decode(transaction.meta);
const tx_result = tx_obj;
const tx_result = tx_json;
tx_result.date = transaction.date;
tx_result.hash = transaction.hash;
@@ -1451,10 +1457,10 @@ Remote.parseBinaryTransaction = function(transaction) {
tx_result.meta.delivered_amount = meta.DeliveredAmount;
break;
default:
switch (typeof tx_obj.Amount) {
switch (typeof tx_json.Amount) {
case 'string':
case 'object':
tx_result.meta.delivered_amount = tx_obj.Amount;
tx_result.meta.delivered_amount = tx_json.Amount;
break;
}
}
@@ -1473,7 +1479,7 @@ Remote.parseBinaryTransaction = function(transaction) {
*/
Remote.parseBinaryLedgerData = function(ledgerData) {
const data = new SerializedObject(ledgerData.data).to_json();
const data = binary.decode(ledgerData.data);
data.index = ledgerData.index;
return data;
};
@@ -1526,22 +1532,22 @@ Remote.prototype.requestBookOffers = function(options, callback) {
const request = new Request(this, 'book_offers');
request.message.taker_gets = {
currency: Currency.json_rewrite(taker_gets.currency, {force_hex: true})
currency: taker_gets.currency
};
if (!Currency.from_json(request.message.taker_gets.currency).is_native()) {
request.message.taker_gets.issuer = UInt160.json_rewrite(taker_gets.issuer);
if (normalizeCurrency(request.message.taker_gets.currency) !== 'XRP') {
request.message.taker_gets.issuer = taker_gets.issuer;
}
request.message.taker_pays = {
currency: Currency.json_rewrite(taker_pays.currency, {force_hex: true})
currency: taker_pays.currency
};
if (!Currency.from_json(request.message.taker_pays.currency).is_native()) {
request.message.taker_pays.issuer = UInt160.json_rewrite(taker_pays.issuer);
if (normalizeCurrency(request.message.taker_pays.currency) !== 'XRP') {
request.message.taker_pays.issuer = taker_pays.issuer;
}
request.message.taker = taker ? taker : UInt160.ACCOUNT_ONE;
request.message.taker = taker ? taker : constants.ACCOUNT_ONE;
request.selectLedger(ledger);
if (!isNaN(limit)) {
@@ -1775,7 +1781,7 @@ Remote.prototype.requestOwnerCount = function(options, callback) {
*/
Remote.prototype.getAccount = function(accountID) {
return this._accounts[UInt160.json_rewrite(accountID)];
return this._accounts[accountID];
};
/**
@@ -1809,6 +1815,19 @@ Remote.prototype.findAccount = function(accountID) {
return account ? account : this.addAccount(accountID);
};
/**
* Closes current pathfind, if there is one.
* After that new pathfind can be created, without adding to queue.
*
* @return {void} -
*/
Remote.prototype.closeCurrentPathFind = function() {
if (this._cur_path_find !== null) {
this._cur_path_find.close();
this._cur_path_find = null;
}
};
/**
* Create a pathfind
*
@@ -1818,6 +1837,10 @@ Remote.prototype.findAccount = function(accountID) {
*/
Remote.prototype.createPathFind = function(options, callback) {
if (this._cur_path_find !== null) {
if (callback === undefined) {
throw new Error('Only one streaming pathfind ' +
'request at a time is supported');
}
this._queued_path_finds.push({options, callback});
return null;
}
@@ -1831,8 +1854,15 @@ Remote.prototype.createPathFind = function(options, callback) {
}
if (callback) {
const updateTimeout = setTimeout(() => {
callback(new RippleError('tejTimeout'));
pathFind.close();
this._cur_path_find = null;
}, this.pathfind_timeout);
pathFind.on('update', (data) => {
if (data.full_reply && !data.closed) {
clearTimeout(updateTimeout);
this._cur_path_find = null;
callback(null, data);
// "A client can only have one pathfinding request open at a time.
@@ -1848,7 +1878,10 @@ Remote.prototype.createPathFind = function(options, callback) {
}
}
});
pathFind.on('error', callback);
pathFind.on('error', (error) => {
this._cur_path_find = null;
callback(error);
});
}
this._cur_path_find = pathFind;
@@ -1857,7 +1890,7 @@ Remote.prototype.createPathFind = function(options, callback) {
};
Remote.prepareTrade = function(currency, issuer) {
const suffix = Currency.from_json(currency).is_native() ? '' : ('/' + issuer);
const suffix = normalizeCurrency(currency) === 'XRP' ? '' : ('/' + issuer);
return currency + suffix;
};
@@ -1899,8 +1932,7 @@ Remote.prototype.book = Remote.prototype.createOrderBook = function(options) {
*/
Remote.prototype.accountSeq =
Remote.prototype.getAccountSequence = function(account_, advance) {
const account = UInt160.json_rewrite(account_);
Remote.prototype.getAccountSequence = function(account, advance) {
const accountInfo = this.accounts[account];
if (!accountInfo) {
@@ -1923,9 +1955,7 @@ Remote.prototype.getAccountSequence = function(account_, advance) {
*/
Remote.prototype.setAccountSequence =
Remote.prototype.setAccountSeq = function(account_, sequence) {
const account = UInt160.json_rewrite(account_);
Remote.prototype.setAccountSeq = function(account, sequence) {
if (!this.accounts.hasOwnProperty(account)) {
this.accounts[account] = { };
}
@@ -1991,8 +2021,7 @@ Remote.prototype.accountSeqCache = function(options, callback) {
* @param {String} account
*/
Remote.prototype.dirtyAccountRoot = function(account_) {
const account = UInt160.json_rewrite(account_);
Remote.prototype.dirtyAccountRoot = function(account) {
delete this.ledgers.current.account_root[account];
};
@@ -2065,8 +2094,7 @@ Remote.prototype.requestRippleBalance = function(options, callback) {
// accountHigh implies for account: balance is negated. highLimit is the
// limit set by account.
const accountHigh = UInt160.from_json(options.account)
.equals(highLimit.issuer());
const accountHigh = (options.account === highLimit.issuer());
request.emit('ripple_state', {
account_balance: (accountHigh
@@ -2107,12 +2135,11 @@ Remote.prepareCurrencies = function(currency) {
const newCurrency = { };
if (currency.hasOwnProperty('issuer')) {
newCurrency.issuer = UInt160.json_rewrite(currency.issuer);
newCurrency.issuer = currency.issuer;
}
if (currency.hasOwnProperty('currency')) {
newCurrency.currency =
Currency.json_rewrite(currency.currency, {force_hex: true});
newCurrency.currency = currency.currency;
}
return newCurrency;
@@ -2128,12 +2155,8 @@ Remote.prepareCurrencies = function(currency) {
Remote.prototype.requestRipplePathFind = function(options, callback) {
const request = new Request(this, 'ripple_path_find');
request.message.source_account = UInt160.json_rewrite(options.source_account);
request.message.destination_account =
UInt160.json_rewrite(options.destination_account);
request.message.source_account = options.source_account;
request.message.destination_account = options.destination_account;
request.message.destination_amount =
Amount.json_rewrite(options.destination_amount);
@@ -2159,11 +2182,8 @@ Remote.prototype.requestPathFindCreate = function(options, callback) {
const request = new Request(this, 'path_find');
request.message.subcommand = 'create';
request.message.source_account = UInt160.json_rewrite(options.source_account);
request.message.destination_account =
UInt160.json_rewrite(options.destination_account);
request.message.source_account = options.source_account;
request.message.destination_account = options.destination_account;
request.message.destination_amount =
Amount.json_rewrite(options.destination_amount);
@@ -2286,7 +2306,7 @@ Remote.prototype.requestGatewayBalances = function(options, callback) {
const request = new Request(this, 'gateway_balances');
request.message.account = UInt160.json_rewrite(options.account);
request.message.account = options.account;
if (!_.isUndefined(options.hotwallet)) {
request.message.hotwallet = options.hotwallet;

View File

@@ -4,8 +4,7 @@ const _ = require('lodash');
const EventEmitter = require('events').EventEmitter;
const util = require('util');
const async = require('async');
const UInt160 = require('./uint160').UInt160;
const Currency = require('./currency').Currency;
const {normalizeCurrency} = require('./currency');
const RippleError = require('./rippleerror').RippleError;
// Request events emitted:
@@ -80,17 +79,23 @@ Request.prototype.request = function(servers, callback_) {
// just in case
this.emit = _.noop;
this.cancel();
this.remote.removeListener('connected', doRequest);
}, this._timeout);
function onResponse() {
clearTimeout(timeout);
}
if (this.remote.isConnected()) {
this.remote.on('connected', doRequest);
}
this.once('response', onResponse);
function onRemoteError(error) {
self.emit('error', error);
}
this.remote.once('error', onRemoteError); // e.g. rate-limiting slowDown error
this.once('response', () => {
clearTimeout(timeout);
this.remote.removeListener('connected', doRequest);
this.remote.removeListener('error', onRemoteError);
});
doRequest();
@@ -127,6 +132,7 @@ Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) {
return this;
}
this.on('error', function() {});
let lastResponse = new Error('No servers available');
const connectTimeouts = { };
const emit = this.emit;
@@ -143,23 +149,44 @@ Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) {
}
};
let serversCallbacks = { };
let serversTimeouts = { };
let serversClearConnectHandlers = { };
function iterator(server, callback) {
// Iterator is called in parallel
if (server.isConnected()) {
// Listen for proxied success/error event and apply filter
self.once('proposed', function(res) {
lastResponse = res;
callback(isResponseSuccess(res));
});
const serverID = server.getServerID();
serversCallbacks[serverID] = callback;
function doRequest() {
return server._request(self);
}
if (server.isConnected()) {
const timeout = setTimeout(() => {
lastResponse = new RippleError('tejTimeout',
JSON.stringify(self.message));
server.removeListener('connect', doRequest);
delete serversCallbacks[serverID];
delete serversClearConnectHandlers[serverID];
callback(false);
}, self._timeout);
serversTimeouts[serverID] = timeout;
serversClearConnectHandlers[serverID] = function() {
server.removeListener('connect', doRequest);
};
server.on('connect', doRequest);
return doRequest();
}
// Server is disconnected but should reconnect. Wait for it to reconnect,
// and abort after a timeout
const serverID = server.getServerID();
function serverReconnected() {
clearTimeout(connectTimeouts[serverID]);
connectTimeouts[serverID] = null;
@@ -174,13 +201,59 @@ Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) {
server.once('connect', serverReconnected);
}
// Listen for proxied success/error event and apply filter
function onProposed(result, server) {
const serverID = server.getServerID();
lastResponse = result;
const callback = serversCallbacks[serverID];
delete serversCallbacks[serverID];
clearTimeout(serversTimeouts[serverID]);
delete serversTimeouts[serverID];
if (serversClearConnectHandlers[serverID] !== undefined) {
serversClearConnectHandlers[serverID]();
delete serversClearConnectHandlers[serverID];
}
if (callback !== undefined) {
callback(isResponseSuccess(result));
}
}
this.on('proposed', onProposed);
let complete_ = null;
// e.g. rate-limiting slowDown error
function onRemoteError(error) {
serversCallbacks = {};
_.forEach(serversTimeouts, clearTimeout);
serversTimeouts = {};
_.forEach(serversClearConnectHandlers, (handler) => {
handler();
});
serversClearConnectHandlers = {};
lastResponse = error instanceof RippleError ? error :
new RippleError(error);
complete_(false);
}
function complete(success) {
self.removeListener('proposed', onProposed);
self.remote.removeListener('error', onRemoteError);
// Emit success if the filter is satisfied by any server
// Emit error if the filter is not satisfied by any server
// Include the last response
emit.call(self, success ? 'success' : 'error', lastResponse);
}
complete_ = complete;
this.remote.once('error', onRemoteError);
const servers = this.remote._servers.filter(function(server) {
// Pre-filter servers that are disconnected and should not reconnect
return (server.isConnected() || server._shouldConnect)
@@ -242,7 +315,6 @@ Request.prototype.callback = function(callback, successEvent, errorEvent) {
let called = false;
function requestError(error) {
self.remote.removeListener('error', requestError);
if (!called) {
called = true;
@@ -255,14 +327,12 @@ Request.prototype.callback = function(callback, successEvent, errorEvent) {
}
function requestSuccess(message) {
self.remote.removeListener('error', requestError);
if (!called) {
called = true;
callback.call(self, null, message);
}
}
this.remote.once('error', requestError); // e.g. rate-limiting slowDown error
this.once(this.successEvent, requestSuccess);
this.once(this.errorEvent, requestError);
@@ -370,7 +440,7 @@ Request.prototype.selectLedger = function(ledger, defaultValue) {
};
Request.prototype.accountRoot = function(account) {
this.message.account_root = UInt160.json_rewrite(account);
this.message.account_root = account;
return this;
};
@@ -384,7 +454,7 @@ Request.prototype.index = function(index) {
// --> seq : sequence number of transaction creating offer (integer)
Request.prototype.offerId = function(account, sequence) {
this.message.offer = {
account: UInt160.json_rewrite(account),
account: account,
seq: sequence
};
return this;
@@ -422,8 +492,8 @@ Request.prototype.rippleState = function(account, issuer, currency) {
this.message.ripple_state = {
currency: currency,
accounts: [
UInt160.json_rewrite(account),
UInt160.json_rewrite(issuer)
account,
issuer
]
};
return this;
@@ -435,7 +505,7 @@ Request.prototype.accounts = function(accountsIn, proposed) {
// Process accounts parameters
const processedAccounts = accounts.map(function(account) {
return UInt160.json_rewrite(account);
return account;
});
if (proposed) {
@@ -453,7 +523,7 @@ Request.prototype.addAccount = function(account, proposed) {
return this;
}
const processedAccount = UInt160.json_rewrite(account);
const processedAccount = account;
const prop = proposed === true ? 'accounts_proposed' : 'accounts';
this.message[prop] = (this.message[prop] || []).concat(processedAccount);
@@ -502,13 +572,11 @@ Request.prototype.addBook = function(book, snapshot) {
}
const obj = json[side] = {
currency: Currency.json_rewrite(book[side].currency, {
force_hex: true
})
currency: normalizeCurrency(book[side].currency)
};
if (!Currency.from_json(obj.currency).is_native()) {
obj.issuer = UInt160.json_rewrite(book[side].issuer);
if (obj.currency !== 'XRP') {
obj.issuer = book[side].issuer;
}
}

View File

@@ -1,369 +0,0 @@
'use strict';
const assert = require('assert');
const extend = require('extend');
const BN = require('bn.js');
const hashjs = require('hash.js');
const sjclcodec = require('sjcl-codec');
const binformat = require('./binformat');
const stypes = require('./serializedtypes');
const utils = require('./utils');
const UInt256 = require('./uint256').UInt256;
const TRANSACTION_TYPES = { };
Object.keys(binformat.tx).forEach(function(key) {
TRANSACTION_TYPES[binformat.tx[key][0]] = key;
});
const LEDGER_ENTRY_TYPES = {};
Object.keys(binformat.ledger).forEach(function(key) {
LEDGER_ENTRY_TYPES[binformat.ledger[key][0]] = key;
});
const TRANSACTION_RESULTS = {};
Object.keys(binformat.ter).forEach(function(key) {
TRANSACTION_RESULTS[binformat.ter[key]] = key;
});
function fieldType(fieldName) {
const fieldDef = binformat.fieldsInverseMap[fieldName];
return binformat.types[fieldDef[0]];
}
function SerializedObject(buf) {
if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf))) {
this.buffer = buf;
} else if (typeof buf === 'string') {
this.buffer = sjclcodec.bytes.fromBits(sjclcodec.hex.toBits(buf));
} else if (!buf) {
this.buffer = [];
} else {
throw new Error('Invalid buffer passed.');
}
this.pointer = 0;
}
SerializedObject.from_json = function(obj) {
const so = new SerializedObject();
so.parse_json(obj);
return so;
};
SerializedObject.check_fields = function(typedef, obj) {
const missingFields = [];
const unknownFields = [];
const fieldsMap = {};
// Get missing required fields
typedef.forEach(function(field) {
const fieldName = field[0];
const isRequired = field[1] === binformat.REQUIRED;
if (isRequired && obj[fieldName] === undefined) {
missingFields.push(fieldName);
} else {
fieldsMap[fieldName] = true;
}
});
// Get fields that are not specified in format
Object.keys(obj).forEach(function(key) {
if (!fieldsMap[key] && /^[A-Z]/.test(key)) {
unknownFields.push(key);
}
});
if (!(missingFields.length || unknownFields.length)) {
// No missing or unknown fields
return;
}
let errorMessage;
if (obj.TransactionType !== undefined) {
errorMessage = SerializedObject.lookup_type_tx(obj.TransactionType);
} else if (obj.LedgerEntryType !== undefined) {
errorMessage = SerializedObject.lookup_type_le(obj.LedgerEntryType);
} else {
errorMessage = 'TransactionMetaData';
}
if (missingFields.length > 0) {
errorMessage += ' is missing fields: ' + JSON.stringify(missingFields);
}
if (unknownFields.length > 0) {
errorMessage += (missingFields.length ? ' and' : '')
+ ' has unknown fields: ' + JSON.stringify(unknownFields);
}
throw new Error(errorMessage);
};
SerializedObject.prototype.parse_json = function(obj_) {
// Create a copy of the object so we don't modify it
const obj = extend(true, {}, obj_);
let typedef;
if (typeof obj.TransactionType === 'number') {
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
if (!obj.TransactionType) {
throw new Error('Transaction type ID is invalid.');
}
}
if (typeof obj.LedgerEntryType === 'number') {
obj.LedgerEntryType = SerializedObject.lookup_type_le(obj.LedgerEntryType);
if (!obj.LedgerEntryType) {
throw new Error('LedgerEntryType ID is invalid.');
}
}
if (typeof obj.TransactionType === 'string') {
typedef = binformat.tx[obj.TransactionType];
if (!Array.isArray(typedef)) {
throw new Error('Transaction type is invalid');
}
typedef = typedef.slice();
obj.TransactionType = typedef.shift();
} else if (typeof obj.LedgerEntryType === 'string') {
typedef = binformat.ledger[obj.LedgerEntryType];
if (!Array.isArray(typedef)) {
throw new Error('LedgerEntryType is invalid');
}
typedef = typedef.slice();
obj.LedgerEntryType = typedef.shift();
} else if (typeof obj.AffectedNodes === 'object') {
typedef = binformat.metadata;
} else {
throw new Error('Object to be serialized must contain either' +
' TransactionType, LedgerEntryType or AffectedNodes.');
}
SerializedObject.check_fields(typedef, obj);
this.serialize(typedef, obj);
};
SerializedObject.prototype.append = function(bytes_) {
const bytes = bytes_ instanceof SerializedObject ? bytes_.buffer : bytes_;
// Make sure both buffer and bytes are Array. Either could be a Buffer.
if (Array.isArray(this.buffer) && Array.isArray(bytes)) {
// `this.buffer = this.buffer.concat(bytes)` can be unbearably slow for
// large bytes length and acceptable bytes length is limited for
// `Array.prototype.push.apply(this.buffer, bytes)` as every element in the
// bytes array is pushed onto the stack, potentially causing a RangeError
// exception. Both of these solutions are known to be problematic for
// ledger 7501326. KISS instead
for (let i = 0; i < bytes.length; i++) {
this.buffer.push(bytes[i]);
}
} else {
this.buffer = this.buffer.concat(bytes);
}
this.pointer += bytes.length;
};
SerializedObject.prototype.resetPointer = function() {
this.pointer = 0;
};
function readOrPeek(advance) {
return function(bytes) {
const start = this.pointer;
const end = start + bytes;
if (end > this.buffer.length) {
throw new Error('Buffer length exceeded');
}
const result = this.buffer.slice(start, end);
if (advance) {
this.pointer = end;
}
return result;
};
}
SerializedObject.prototype.read = readOrPeek(true);
SerializedObject.prototype.peek = readOrPeek(false);
SerializedObject.prototype.to_bits = function() {
return sjclcodec.bytes.toBits(this.buffer);
};
SerializedObject.prototype.to_hex = function() {
return sjclcodec.hex.fromBits(this.to_bits()).toUpperCase();
};
SerializedObject.prototype.to_json = function() {
const old_pointer = this.pointer;
this.resetPointer();
const output = { };
while (this.pointer < this.buffer.length) {
const key_and_value = stypes.parse(this);
const key = key_and_value[0];
const value = key_and_value[1];
output[key] = SerializedObject.jsonify_structure(value, key);
}
this.pointer = old_pointer;
return output;
};
SerializedObject.jsonify_structure = function(structure, fieldName) {
let output;
switch (typeof structure) {
case 'number':
switch (fieldName) {
case 'LedgerEntryType':
output = LEDGER_ENTRY_TYPES[structure];
break;
case 'TransactionResult':
output = TRANSACTION_RESULTS[structure];
break;
case 'TransactionType':
output = TRANSACTION_TYPES[structure];
break;
default:
output = structure;
}
break;
case 'object':
if (structure === null) {
break;
}
if (typeof structure.to_json === 'function') {
output = structure.to_json();
} else if (structure instanceof BN) {
// We assume that any BN is a UInt64 field
assert.equal(fieldType(fieldName), 'Int64');
output = utils.arrayToHex(structure.toArray('bn', 8));
} else {
// new Array or Object
output = new structure.constructor();
const keys = Object.keys(structure);
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i];
output[key] = SerializedObject.jsonify_structure(structure[key], key);
}
}
break;
default:
output = structure;
}
return output;
};
SerializedObject.prototype.serialize = function(typedef, obj) {
// Serialize object without end marker
stypes.Object.serialize(this, obj, true);
// ST: Old serialization
/*
// Ensure canonical order
typedef = SerializedObject.sort_typedef(typedef);
// Serialize fields
for (let i=0, l=typedef.length; i<l; i++) {
this.serialize_field(typedef[i], obj);
}
*/
};
SerializedObject.prototype.hash = function(prefix) {
const sign_buffer = new SerializedObject();
// Add hashing prefix
if (typeof prefix !== 'undefined') {
stypes.Int32.serialize(sign_buffer, prefix);
}
// Copy buffer to temporary buffer
sign_buffer.append(this.buffer);
const bytes = hashjs.sha512().update(sign_buffer.buffer).digest();
return UInt256.from_bytes(bytes.slice(0, 32));
};
// DEPRECATED
SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash;
SerializedObject.prototype.serialize_field = function(spec, obj) {
const name = spec[0];
const presence = spec[1];
if (typeof obj[name] !== 'undefined') {
try {
stypes.serialize(this, name, obj[name]);
} catch (e) {
// Add field name to message and rethrow
e.message = 'Error serializing "' + name + '": ' + e.message;
throw e;
}
} else if (presence === binformat.REQUIRED) {
throw new Error('Missing required field ' + name);
}
};
SerializedObject.get_field_header = function(type_id, field_id) {
const buffer = [0];
if (type_id > 0xF) {
buffer.push(type_id & 0xFF);
} else {
buffer[0] += (type_id & 0xF) << 4;
}
if (field_id > 0xF) {
buffer.push(field_id & 0xFF);
} else {
buffer[0] += field_id & 0xF;
}
return buffer;
};
SerializedObject.sort_typedef = function(typedef) {
assert(Array.isArray(typedef));
function sort_field_compare(a, b) {
// Sort by type id first, then by field id
return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2];
}
return typedef.sort(sort_field_compare);
};
SerializedObject.lookup_type_tx = function(id) {
assert.strictEqual(typeof id, 'number');
return TRANSACTION_TYPES[id];
};
SerializedObject.lookup_type_le = function(id) {
assert(typeof id === 'number');
return LEDGER_ENTRY_TYPES[id];
};
exports.SerializedObject = SerializedObject;

View File

@@ -1,935 +0,0 @@
'use strict';
/**
* Type definitions for binary format.
*
* This file should not be included directly. Instead, find the format you're
* trying to parse or serialize in binformat.js and pass that to
* SerializedObject.parse() or SerializedObject.serialize().
*/
const _ = require('lodash');
const assert = require('assert');
const extend = require('extend');
const BN = require('bn.js');
const GlobalBigNumber = require('bignumber.js');
const sjclcodec = require('sjcl-codec');
const Amount = require('./amount').Amount;
const Currency = require('./currency').Currency;
const binformat = require('./binformat');
const utils = require('./utils');
const UInt128 = require('./uint128').UInt128;
const UInt160 = require('./uint160').UInt160;
const UInt256 = require('./uint256').UInt256;
const Base = require('./base').Base;
const BigNumber = GlobalBigNumber.another({
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
DECIMAL_PLACES: 40
});
function SerializedType(methods) {
extend(this, methods);
}
function isNumber(val) {
return typeof val === 'number' && isFinite(val);
}
function isString(val) {
return typeof val === 'string';
}
function isHexInt64String(val) {
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
}
function serializeBytes(so, byteData, noLength) {
if (!noLength) {
SerializedType.serialize_varint(so, byteData.length);
}
so.append(byteData);
}
function serializeHex(so, hexData, noLength) {
serializeBytes(so, utils.hexToArray(hexData), noLength);
}
function convertHexToString(hexString) {
const bits = sjclcodec.hex.toBits(hexString);
return sjclcodec.utf8String.fromBits(bits);
}
function sort_fields(keys) {
function sort_field_compare(a, b) {
const a_field_coordinates = binformat.fieldsInverseMap[a];
const a_type_bits = a_field_coordinates[0];
const a_field_bits = a_field_coordinates[1];
const b_field_coordinates = binformat.fieldsInverseMap[b];
const b_type_bits = b_field_coordinates[0];
const b_field_bits = b_field_coordinates[1];
// Sort by type id first, then by field id
return a_type_bits !== b_type_bits
? a_type_bits - b_type_bits
: a_field_bits - b_field_bits;
}
return keys.sort(sort_field_compare);
}
SerializedType.serialize_varint = function(so, val) {
let value = val;
if (value < 0) {
throw new Error('Variable integers are unsigned.');
}
if (value <= 192) {
so.append([value]);
} else if (value <= 12480) {
value -= 193;
so.append([193 + (value >>> 8), value & 0xff]);
} else if (value <= 918744) {
value -= 12481;
so.append([241 + (value >>> 16), value >>> 8 & 0xff, value & 0xff]);
} else {
throw new Error('Variable integer overflow.');
}
};
SerializedType.prototype.parse_varint = function(so) {
const b1 = so.read(1)[0];
let b2;
let b3;
let result;
if (b1 > 254) {
throw new Error('Invalid varint length indicator');
}
if (b1 <= 192) {
result = b1;
} else if (b1 <= 240) {
b2 = so.read(1)[0];
result = 193 + (b1 - 193) * 256 + b2;
} else if (b1 <= 254) {
b2 = so.read(1)[0];
b3 = so.read(1)[0];
result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3;
}
return result;
};
// In the following, we assume that the inputs are in the proper range. Is this
// correct?
// Helper functions for 1-, 2-, and 4-byte integers.
/**
* Convert an integer value into an array of bytes.
*
* The result is appended to the serialized object ('so').
*
* @param {Number} val value
* @param {Number} bytes byte size
* @return {Array} byte array
*/
function convertIntegerToByteArray(val, bytes) {
if (!isNumber(val)) {
throw new Error('Value is not a number', bytes);
}
if (val < 0 || val >= Math.pow(256, bytes)) {
throw new Error('Value out of bounds ');
}
const newBytes = [ ];
for (let i = 0; i < bytes; i++) {
newBytes.unshift(val >>> (i * 8) & 0xff);
}
return newBytes;
}
// Convert a certain number of bytes from the serialized object ('so') into an
// integer.
function readAndSum(so, bytes) {
let sum = 0;
if (bytes > 4) {
throw new Error('This function only supports up to four bytes.');
}
for (let i = 0; i < bytes; i++) {
const byte = so.read(1)[0];
sum += (byte << (8 * (bytes - i - 1)));
}
// Convert to unsigned integer
return sum >>> 0;
}
const STInt8 = exports.Int8 = new SerializedType({
serialize: function(so, val) {
so.append(convertIntegerToByteArray(val, 1));
},
parse: function(so) {
return readAndSum(so, 1);
}
});
STInt8.id = 16;
function serialize(so, field_name, value) {
// so: a byte-stream to serialize into.
// field_name: a string for the field name ('LedgerEntryType' etc.)
// value: the value of that field.
const field_coordinates = binformat.fieldsInverseMap[field_name];
const type_bits = field_coordinates[0];
const field_bits = field_coordinates[1];
const tag_byte = (type_bits < 16
? type_bits << 4
: 0) | (field_bits < 16
? field_bits
: 0);
let val = value;
if (field_name === 'LedgerEntryType' && typeof val === 'string') {
val = binformat.ledger[val][0];
}
if (field_name === 'TransactionResult' && typeof val === 'string') {
val = binformat.ter[val];
}
STInt8.serialize(so, tag_byte);
if (type_bits >= 16) {
STInt8.serialize(so, type_bits);
}
if (field_bits >= 16) {
STInt8.serialize(so, field_bits);
}
// Get the serializer class (ST...)
let serialized_object_type;
if (field_name === 'Memo' && typeof val === 'object') {
// for Memo we override the default behavior with our STMemo serializer
serialized_object_type = exports.STMemo;
} else {
// for a field based on the type bits.
serialized_object_type = exports[binformat.types[type_bits]];
}
try {
serialized_object_type.serialize(so, val);
} catch (e) {
e.message += ' (' + field_name + ')';
throw e;
}
}
exports.serialize = exports.serialize_whatever = serialize;
// Take the serialized object, figure out what type/field it is, and return the
// parsing of that.
function parse(so) {
const tag_byte = so.read(1)[0];
let type_bits = tag_byte >> 4;
if (type_bits === 0) {
type_bits = so.read(1)[0];
}
const field_bits = tag_byte & 0x0f;
const field_name = (field_bits === 0)
? binformat.fields[type_bits][so.read(1)[0]]
: binformat.fields[type_bits][field_bits];
assert(field_name, 'Unknown field - header byte is 0x'
+ tag_byte.toString(16));
// Get the parser class (ST...) for a field based on the type bits.
const type = (field_name === 'Memo')
? exports.STMemo
: exports[binformat.types[type_bits]];
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
return [field_name, type.parse(so)]; // key, value
}
exports.parse = exports.parse_whatever = parse;
const STInt16 = exports.Int16 = new SerializedType({
serialize: function(so, val) {
so.append(convertIntegerToByteArray(val, 2));
},
parse: function(so) {
return readAndSum(so, 2);
}
});
STInt16.id = 1;
const STInt32 = exports.Int32 = new SerializedType({
serialize: function(so, val) {
so.append(convertIntegerToByteArray(val, 4));
},
parse: function(so) {
return readAndSum(so, 4);
}
});
STInt32.id = 2;
const STInt64 = exports.Int64 = new SerializedType({
serialize: function(so, val) {
let bigNumObject;
let value = val;
if (isNumber(value)) {
value = Math.floor(value);
if (value < 0) {
throw new Error('Negative value for unsigned Int64 is invalid.');
}
bigNumObject = new BN(value, 10);
} else if (isString(value)) {
if (!isHexInt64String(value)) {
throw new Error('Not a valid hex Int64.');
}
bigNumObject = new BN(value, 16);
} else if (value instanceof BN) {
if (value.cmpn(0) < 0) {
throw new Error('Negative value for unsigned Int64 is invalid.');
}
bigNumObject = value;
} else {
throw new Error('Invalid type for Int64: ' + (typeof value) + ' value');
}
// `'be'` means big endian, and the following arg is the byte length, which
// it will pad with 0s to if not enough bytes, or throw if over
serializeBytes(so, bigNumObject.toArray('be', 8), /* noLength= */true);
},
parse: function(so) {
const bytes = so.read(8);
return new BN(bytes);
}
});
STInt64.id = 3;
const STHash128 = exports.Hash128 = new SerializedType({
serialize: function(so, val) {
const hash = UInt128.from_json(val);
if (!hash.is_valid()) {
throw new Error('Invalid Hash128');
}
serializeBytes(so, hash.to_bytes(), true); // noLength = true
},
parse: function(so) {
return UInt128.from_bytes(so.read(16));
}
});
STHash128.id = 4;
const STHash256 = exports.Hash256 = new SerializedType({
serialize: function(so, val) {
const hash = UInt256.from_json(val);
if (!hash.is_valid()) {
throw new Error('Invalid Hash256');
}
serializeBytes(so, hash.to_bytes(), true); // noLength = true
},
parse: function(so) {
return UInt256.from_bytes(so.read(32));
}
});
STHash256.id = 5;
const STHash160 = exports.Hash160 = new SerializedType({
serialize: function(so, val) {
const hash = UInt160.from_json(val);
if (!hash.is_valid()) {
throw new Error('Invalid Hash160');
}
serializeBytes(so, hash.to_bytes(), true); // noLength = true
},
parse: function(so) {
return UInt160.from_bytes(so.read(20));
}
});
STHash160.id = 17;
// Internal
const STCurrency = new SerializedType({
serialize: function(so, val) {
const currencyData = val.to_bytes();
if (!currencyData) {
throw new Error(
'Tried to serialize invalid/unimplemented currency type.');
}
so.append(currencyData);
},
parse: function(so) {
const bytes = so.read(20);
const currency = Currency.from_bytes(bytes);
// XXX Disabled check. Theoretically, the Currency class should support any
// UInt160 value and consider it valid. But it doesn't, so for the
// deserialization to be usable, we need to allow invalid results for
// now.
// if (!currency.is_valid()) {
// throw new Error('Invalid currency: '+convertByteArrayToHex(bytes));
// }
return currency;
}
});
/**
* Quality is encoded into 64 bits:
* (8 bits offset) (56 bits mantissa)
*
* Quality differs from Amount because it does not need the first two bits
* to represent non-native and non-negative
*/
exports.Quality = new SerializedType({
serialize: function(so, val) {
let value;
// if in format: amount/currency/issuer
if (_.includes(val, '/')) {
const amount = Amount.from_json(val);
if (!amount.is_valid()) {
throw new Error('Not a valid Amount object.');
}
value = new BigNumber(amount.to_text());
} else {
value = new BigNumber(val);
}
let hi = 0;
let lo = 0;
const offset = value.e - 15;
if (val !== 0) {
// First eight bits: offset/exponent
hi |= ((100 + offset) & 0xff) << 24;
// Remaining 56 bits: mantissa
const mantissaDecimal = utils.getMantissaDecimalString(value.abs());
const mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
assert(mantissaHex.length <= 16,
'Mantissa hex representation ' + mantissaHex +
' exceeds the maximum length of 16');
hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0xffffff;
lo = parseInt(mantissaHex.slice(-8), 16);
}
const valueBytes = sjclcodec.bytes.fromBits([hi, lo]);
so.append(valueBytes);
}
});
/*
* Amount is encoded into 64 bits:
* (1 bit non-native) (1 bit non-negative) (8 bits offset) (54 bits mantissa)
*/
const STAmount = exports.Amount = new SerializedType({
serialize: function(so, val) {
const amount = Amount.from_json(val);
if (!amount.is_valid()) {
throw new Error('Not a valid Amount object.');
}
const value = new BigNumber(amount.to_text());
const offset = value.e - 15;
// Amount (64-bit integer)
let valueBytes = utils.arraySet(8, 0);
if (amount.is_native()) {
let valueHex = value.abs().toString(16);
if (Amount.strict_mode && value.abs().greaterThan(Amount.bi_xns_max)) {
throw new Error('Value out of bounds');
}
// Enforce correct length (64 bits)
if (Amount.strict_mode && valueHex.length > 16) {
throw new Error('Value out of bounds');
}
while (valueHex.length < 16) {
valueHex = '0' + valueHex;
}
valueBytes = sjclcodec.bytes.fromBits(sjclcodec.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;
}
} else {
let hi = 0;
let 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 + offset) & 0xff) << 22;
// Remaining 54 bits: mantissa
const mantissaDecimal = utils.getMantissaDecimalString(value.abs());
const mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
assert(mantissaHex.length <= 16,
'Mantissa hex representation ' + mantissaHex +
' exceeds the maximum length of 16');
hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0x3fffff;
lo = parseInt(mantissaHex.slice(-8), 16);
}
valueBytes = sjclcodec.bytes.fromBits([hi, lo]);
}
so.append(valueBytes);
if (!amount.is_native()) {
// Currency (160-bit hash)
const currency = amount.currency();
STCurrency.serialize(so, currency, true);
// Issuer (160-bit hash)
so.append(UInt160.from_json(amount.issuer()).to_bytes());
}
},
parse: function(so) {
const value_bytes = so.read(8);
let is_zero = !(value_bytes[0] & 0x7f);
for (let i = 1; i < 8; i++) {
is_zero = is_zero && !value_bytes[i];
}
const is_negative = !is_zero && !(value_bytes[0] & 0x40);
if (value_bytes[0] & 0x80) {
// non-native
const currency = STCurrency.parse(so);
const issuer_bytes = so.read(20);
const issuer = UInt160.from_bytes(issuer_bytes);
issuer.set_version(Base.VER_ACCOUNT_ID);
const offset =
((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
const mantissa_bytes = value_bytes.slice(1);
mantissa_bytes[0] &= 0x3f;
const mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16);
const sign = is_negative ? '-' : '';
const valueString = sign + mantissa.toString() + 'e' + offset.toString();
return Amount.from_json({
currency: currency,
issuer: issuer.to_json(),
value: valueString
});
}
// native
const integer_bytes = value_bytes.slice();
integer_bytes[0] &= 0x3f;
const integer_hex = utils.arrayToHex(integer_bytes);
const value = new BigNumber(integer_hex, 16);
return Amount.from_json((is_negative ? '-' : '') + value.toString());
}
});
STAmount.id = 6;
const STVL = exports.VariableLength = exports.VL = new SerializedType({
serialize: function(so, val) {
if (typeof val === 'string') {
serializeHex(so, val);
} else {
throw new Error('Unknown datatype.');
}
},
parse: function(so) {
const len = this.parse_varint(so);
return utils.arrayToHex(so.read(len));
}
});
STVL.id = 7;
const STAccount = exports.Account = new SerializedType({
serialize: function(so, val) {
const account = UInt160.from_json(val);
if (!account.is_valid()) {
throw new Error('Invalid account!');
}
serializeBytes(so, account.to_bytes());
},
parse: function(so) {
const len = this.parse_varint(so);
if (len !== 20) {
throw new Error('Non-standard-length account ID');
}
const result = UInt160.from_bytes(so.read(len));
result.set_version(Base.VER_ACCOUNT_ID);
if (false && !result.is_valid()) {
throw new Error('Invalid Account');
}
return result;
}
});
STAccount.id = 8;
const STPathSet = exports.PathSet = new SerializedType({
typeBoundary: 0xff,
typeEnd: 0x00,
typeAccount: 0x01,
typeCurrency: 0x10,
typeIssuer: 0x20,
serialize: function(so, val) {
for (let i = 0, l = val.length; i < l; i++) {
// Boundary
if (i) {
STInt8.serialize(so, this.typeBoundary);
}
for (let j = 0, l2 = val[i].length; j < l2; j++) {
const entry = val[i][j];
// if (entry.hasOwnProperty('_value')) {entry = entry._value;}
let type = 0;
if (entry.account) {
type |= this.typeAccount;
}
if (entry.currency) {
type |= this.typeCurrency;
}
if (entry.issuer) {
type |= this.typeIssuer;
}
STInt8.serialize(so, type);
if (entry.account) {
STHash160.serialize(so, entry.account);
}
if (entry.currency) {
const currency = Currency.from_json(entry.currency, entry.non_native);
STCurrency.serialize(so, currency);
}
if (entry.issuer) {
STHash160.serialize(so, entry.issuer);
}
}
}
STInt8.serialize(so, this.typeEnd);
},
parse: function(so) {
// should return a list of lists:
/*
[
[entry, entry],
[entry, entry, entry],
[entry],
[]
]
each entry has one or more of the following attributes:
amount, currency, issuer.
*/
const path_list = [];
let current_path = [];
let tag_byte;
/* eslint-disable no-cond-assign */
while ((tag_byte = so.read(1)[0]) !== this.typeEnd) {
// TODO: try/catch this loop, and catch when we run out of data without
// reaching the end of the data structure.
// Now determine: is this an end, boundary, or entry-begin-tag?
// console.log('Tag byte:', tag_byte);
if (tag_byte === this.typeBoundary) {
if (current_path) { // close the current path, if there is one,
path_list.push(current_path);
}
current_path = [ ]; // and start a new one.
continue;
}
// It's an entry-begin tag.
const entry = {};
let type = 0;
if (tag_byte & this.typeAccount) {
entry.account = STHash160.parse(so);
entry.account.set_version(Base.VER_ACCOUNT_ID);
type = type | this.typeAccount;
}
if (tag_byte & this.typeCurrency) {
entry.currency = STCurrency.parse(so);
if (entry.currency.to_json() === 'XRP' && !entry.currency.is_native()) {
entry.non_native = true;
}
type = type | this.typeCurrency;
}
if (tag_byte & this.typeIssuer) {
entry.issuer = STHash160.parse(so);
// Enable and set correct type of base-58 encoding
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
type = type | this.typeIssuer;
}
if (entry.account || entry.currency || entry.issuer) {
entry.type = type;
entry.type_hex = ('000000000000000' + type.toString(16)).slice(-16);
current_path.push(entry);
} else {
// It must have at least something in it.
throw new Error('Invalid path entry');
}
}
if (current_path) {
// close the current path, if there is one,
path_list.push(current_path);
}
return path_list;
}
});
STPathSet.id = 18;
const STVector256 = exports.Vector256 = new SerializedType({
serialize: function(so, val) {
// Assume val is an array of STHash256 objects.
SerializedType.serialize_varint(so, val.length * 32);
for (let i = 0, l = val.length; i < l; i++) {
STHash256.serialize(so, val[i]);
}
},
parse: function(so) {
const length = this.parse_varint(so);
const output = [];
// length is number of bytes not number of Hash256
for (let i = 0; i < length / 32; i++) {
output.push(STHash256.parse(so));
}
return output;
}
});
STVector256.id = 19;
// Internal
exports.STMemo = new SerializedType({
serialize: function(so, val, no_marker) {
let keys = [];
Object.keys(val).forEach(function(key) {
// Ignore lowercase field names - they're non-serializable fields by
// convention.
if (key[0] === key[0].toLowerCase()) {
return;
}
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
throw new Error('JSON contains unknown field: "' + key + '"');
}
keys.push(key);
});
// Sort fields
keys = sort_fields(keys);
keys.forEach(function(key) {
serialize(so, key, val[key]);
});
if (!no_marker) {
// Object ending marker
STInt8.serialize(so, 0xe1);
}
},
parse: function(so) {
const output = {};
while (so.peek(1)[0] !== 0xe1) {
const keyval = parse(so);
output[keyval[0]] = keyval[1];
}
if (output.MemoType !== undefined) {
try {
const parsedType = convertHexToString(output.MemoType);
if (parsedType !== 'unformatted_memo') {
output.parsed_memo_type = parsedType;
}
/* eslint-disable no-empty */
} catch (e) {
// empty
// we don't know what's in the binary, apparently it's not a UTF-8
// string
// this is fine, we won't add the parsed_memo_type field
}
/* eslint-enable no-empty */
}
if (output.MemoFormat !== undefined) {
try {
output.parsed_memo_format = convertHexToString(output.MemoFormat);
/* eslint-disable no-empty */
} catch (e) {
// empty
// we don't know what's in the binary, apparently it's not a UTF-8
// string
// this is fine, we won't add the parsed_memo_format field
}
/* eslint-enable no-empty */
}
if (output.MemoData !== undefined) {
try {
if (output.parsed_memo_format === 'json') {
// see if we can parse JSON
output.parsed_memo_data =
JSON.parse(convertHexToString(output.MemoData));
} else if (output.parsed_memo_format === 'text') {
// otherwise see if we can parse text
output.parsed_memo_data = convertHexToString(output.MemoData);
}
/* eslint-disable no-empty */
} catch (e) {
// empty
// we'll fail in case the content does not match what the MemoFormat
// described
// this is fine, we won't add the parsed_memo_data, the user has to
// parse themselves
}
/* eslint-enable no-empty */
}
so.read(1);
return output;
}
});
const STObject = exports.Object = new SerializedType({
serialize: function(so, val, no_marker) {
let keys = [];
Object.keys(val).forEach(function(key) {
// Ignore lowercase field names - they're non-serializable fields by
// convention.
if (key[0] === key[0].toLowerCase()) {
return;
}
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
throw new Error('JSON contains unknown field: "' + key + '"');
}
keys.push(key);
});
// Sort fields
keys = sort_fields(keys);
for (let i = 0; i < keys.length; i++) {
serialize(so, keys[i], val[keys[i]]);
}
if (!no_marker) {
// Object ending marker
STInt8.serialize(so, 0xe1);
}
},
parse: function(so) {
const output = {};
while (so.peek(1)[0] !== 0xe1) {
const keyval = parse(so);
output[keyval[0]] = keyval[1];
}
so.read(1);
return output;
}
});
STObject.id = 14;
const STArray = exports.Array = new SerializedType({
serialize: function(so, val) {
for (let i = 0, l = val.length; i < l; i++) {
const keys = Object.keys(val[i]);
if (keys.length !== 1) {
throw new Error(
'Cannot serialize an array containing non-single-key objects');
}
const field_name = keys[0];
const value = val[i][field_name];
serialize(so, field_name, value);
}
// Array ending marker
STInt8.serialize(so, 0xf1);
},
parse: function(so) {
const output = [ ];
while (so.peek(1)[0] !== 0xf1) {
const keyval = parse(so);
const obj = { };
obj[keyval[0]] = keyval[1];
output.push(obj);
}
so.read(1);
return output;
}
});
STArray.id = 15;

View File

@@ -482,11 +482,21 @@ Server.prototype.connect = function() {
self.emit('message', message);
};
function onRemoteError() {}
ws.onopen = function onOpen() {
if (ws === self._ws) {
self.emit('socket_open');
// e.g. rate-limiting slowDown error
self._remote.once('error', onRemoteError);
// Subscribe to events
self._request(self._remote._serverPrepareSubscribe(self));
const request = self._remote._serverPrepareSubscribe(self);
request.once('response', () => {
self._remote.removeListener('error', onRemoteError);
});
self._request(request);
}
};
@@ -676,7 +686,7 @@ Server.prototype._handleResponse = function(message) {
const result = message.result;
const responseEvent = 'response_' + command;
request.emit('success', result);
request.emit('success', result, this);
[this, this._remote].forEach(function(emitter) {
emitter.emit(responseEvent, result, request, message);
@@ -690,9 +700,9 @@ Server.prototype._handleResponse = function(message) {
error: 'remoteError',
error_message: 'Remote reported an error.',
remote: message
});
}, this);
}
request.emit('response', message);
request.emit('response', message, this);
};
Server.prototype._handlePathFind = function(message) {
@@ -800,7 +810,16 @@ Server.prototype._sendMessage = function(message) {
if (this._remote.trace) {
log.info(this.getServerID(), 'request:', message);
}
this._ws.send(JSON.stringify(message));
this._ws.send(JSON.stringify(message), (error) => {
// sometimes gives 'not opened'
// without callback it wil throw
if (error) {
// resend in case of error
this.once('connect', () => {
this._sendMessage(message);
});
}
});
}
};

View File

@@ -1,187 +0,0 @@
'use strict';
var util = require('util');
var hashprefixes = require('./hashprefixes');
var UInt256 = require('./uint256').UInt256;
var SerializedObject = require('./serializedobject').SerializedObject;
/**
* Abstract class representing a node in a SHAMap tree.
*
* Can be either SHAMapTreeNodeInner or SHAMapTreeNodeLeaf.
*
* @class
*/
function SHAMapTreeNode() { }
SHAMapTreeNode.TYPE_INNER = 1;
SHAMapTreeNode.TYPE_TRANSACTION_NM = 2;
SHAMapTreeNode.TYPE_TRANSACTION_MD = 3;
SHAMapTreeNode.TYPE_ACCOUNT_STATE = 4;
/**
* @param {String} tag (64 hexadecimal characters)
* @param {SHAMapTreeNode} node
* @return {void}
* @virtual
*/
/*eslint-disable no-unused-vars*/
SHAMapTreeNode.prototype.add_item = function(tag, node) {
throw new Error(
'Called unimplemented virtual method SHAMapTreeNode#add_item.');
};
/*eslint-enable no-unused-vars*/
SHAMapTreeNode.prototype.hash = function() {
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
};
/**
* Inner (non-leaf) node in a SHAMap tree.
* @param {Number} depth (i.e. how many parent inner nodes)
* @class
*/
function SHAMapTreeNodeInner(depth) {
SHAMapTreeNode.call(this);
this.leaves = {};
this.type = SHAMapTreeNode.INNER;
this.depth = depth === undefined ? 0 : depth;
this.empty = true;
}
util.inherits(SHAMapTreeNodeInner, SHAMapTreeNode);
/**
* @param {String} tag (equates to a ledger entry `index`)
* @param {SHAMapTreeNode} node (to add)
* @return {void}
*/
SHAMapTreeNodeInner.prototype.add_item = function(tag, node) {
var depth = this.depth;
var existing_node = this.get_node(tag[depth]);
if (existing_node) {
// A node already exists in this slot
if (existing_node instanceof SHAMapTreeNodeInner) {
// There is an inner node, so we need to go deeper
existing_node.add_item(tag, node);
} else if (existing_node.tag === tag) {
// Collision
throw new Error(
'Tried to add a node to a SHAMap that was already in there.');
} else {
// Turn it into an inner node
var new_inner_node = new SHAMapTreeNodeInner(depth + 1);
// Parent new and existing node
new_inner_node.add_item(existing_node.tag, existing_node);
new_inner_node.add_item(tag, node);
// And place the newly created inner node in the slot
this.set_node(tag[depth], new_inner_node);
}
} else {
// Neat, we have a nice open spot for the new node
this.set_node(tag[depth], node);
}
};
/**
* Overwrite the node that is currently in a given slot.
* @param {String} slot (a character 0-F)
* @param {SHAMapTreeNode} node (to place)
* @return {void}
*/
SHAMapTreeNodeInner.prototype.set_node = function(slot, node) {
this.leaves[slot] = node;
this.empty = false;
};
SHAMapTreeNodeInner.prototype.get_node = function(slot) {
return this.leaves[slot];
};
SHAMapTreeNodeInner.prototype.hash = function() {
if (this.empty) {
return UInt256.from_hex(UInt256.HEX_ZERO);
}
var hash_buffer = new SerializedObject();
for (var i = 0; i < 16; i++) {
var leafHash = UInt256.from_hex(UInt256.HEX_ZERO);
var slot = i.toString(16).toUpperCase();
if (typeof this.leaves[slot] === 'object') {
leafHash = this.leaves[slot].hash();
}
hash_buffer.append(leafHash.to_bytes());
}
var hash = hash_buffer.hash(hashprefixes.HASH_INNER_NODE);
return UInt256.from_bits(hash);
};
/**
* Leaf node in a SHAMap tree.
* @param {String} tag (equates to a ledger entry `index`)
* @param {SerializedObject} node (bytes of account state, transaction etc)
* @param {Number} type (one of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc)
* @class
*/
function SHAMapTreeNodeLeaf(tag, node, type) {
SHAMapTreeNode.call(this);
if (typeof tag !== 'string') {
throw new Error('Tag is unexpected type.');
}
this.tag = tag;
this.tag_bytes = UInt256.from_hex(this.tag).to_bytes();
this.type = type;
this.node = node;
}
util.inherits(SHAMapTreeNodeLeaf, SHAMapTreeNode);
SHAMapTreeNodeLeaf.prototype.hash = function() {
var buffer = new SerializedObject();
switch (this.type) {
case SHAMapTreeNode.TYPE_ACCOUNT_STATE:
buffer.append(this.node);
buffer.append(this.tag_bytes);
return buffer.hash(hashprefixes.HASH_LEAF_NODE);
case SHAMapTreeNode.TYPE_TRANSACTION_NM:
return this.tag_bytes;
case SHAMapTreeNode.TYPE_TRANSACTION_MD:
buffer.append(this.node);
buffer.append(this.tag_bytes);
return buffer.hash(hashprefixes.HASH_TX_NODE);
default:
throw new Error('Tried to hash a SHAMap node of unknown type.');
}
};
function SHAMap() {
this.root = new SHAMapTreeNodeInner(0);
}
SHAMap.prototype.add_item = function(tag, node, type) {
node = new SHAMapTreeNodeLeaf(tag, node, type);
this.root.add_item(tag, node);
};
SHAMap.prototype.hash = function() {
return this.root.hash();
};
exports.SHAMap = SHAMap;
exports.SHAMapTreeNode = SHAMapTreeNode;
exports.SHAMapTreeNodeInner = SHAMapTreeNodeInner;
exports.SHAMapTreeNodeLeaf = SHAMapTreeNodeLeaf;

View File

@@ -8,13 +8,13 @@ const EventEmitter = require('events').EventEmitter;
const utils = require('./utils');
const sjclcodec = require('sjcl-codec');
const Amount = require('./amount').Amount;
const Currency = require('./currency').Currency;
const UInt160 = require('./uint160').UInt160;
const SerializedObject = require('./serializedobject').SerializedObject;
const {normalizeCurrency, isValidCurrency} = require('./currency');
const RippleError = require('./rippleerror').RippleError;
const hashprefixes = require('./hashprefixes');
const log = require('./log').internal.sub('transaction');
const {isValidAddress} = require('ripple-address-codec');
const {isValidAddress, decodeAddress} = require('ripple-address-codec');
const binary = require('ripple-binary-codec');
const {computeTransactionHash, computeTransactionSigningHash}
= require('ripple-hashes');
/**
* @constructor Transaction
@@ -385,24 +385,32 @@ Transaction.prototype.err = function(error, errorMessage) {
};
Transaction.prototype.complete = function() {
// Auto-fill the secret
this._secret = this._secret || this.getSecret();
const hasMultiSigners = this.hasMultiSigners();
if (_.isUndefined(this._secret)) {
return this.err('tejSecretUnknown', 'Missing secret');
}
if (!hasMultiSigners) {
// Auto-fill the secret
this._secret = this._secret || this.getSecret();
if (this.remote && !(this.remote.local_signing || this.remote.trusted)) {
return this.err(
'tejServerUntrusted',
'Attempt to give secret to untrusted server');
if (_.isUndefined(this._secret)) {
return this.err('tejSecretUnknown', 'Missing secret');
}
if (this.remote && !(this.remote.local_signing || this.remote.trusted)) {
return this.err(
'tejServerUntrusted',
'Attempt to give secret to untrusted server');
}
}
if (_.isUndefined(this.tx_json.SigningPubKey)) {
try {
this.setSigningPubKey(this.getSigningPubKey());
} catch (e) {
return this.err('tejSecretInvalid', 'Invalid secret');
if (hasMultiSigners) {
this.setSigningPubKey('');
} else {
try {
this.setSigningPubKey(this.getSigningPubKey());
} catch (e) {
return this.err('tejSecretInvalid', 'Invalid secret');
}
}
}
@@ -452,42 +460,23 @@ Transaction.prototype.setCanonicalFlag = function() {
};
Transaction.prototype.serialize = function() {
return SerializedObject.from_json(this.tx_json);
return binary.encode(this.tx_json);
};
Transaction.prototype.signingHash = function(testnet) {
return this.hash(testnet ? 'HASH_TX_SIGN_TESTNET' : 'HASH_TX_SIGN');
Transaction.prototype.signingHash = function() {
return computeTransactionSigningHash(this.tx_json);
};
Transaction.prototype.signingData = function() {
const so = new SerializedObject();
so.append(hashprefixes.HASH_TX_SIGN_BYTES);
so.parse_json(this.tx_json);
return so;
return binary.encodeForSigning(this.tx_json);
};
Transaction.prototype.multiSigningData = function(account) {
const so = new SerializedObject();
so.append(hashprefixes.HASH_TX_MULTISIGN_BYTES);
so.parse_json(this.tx_json);
so.append(UInt160.from_json(account).to_bytes());
return so;
return binary.encodeForMultisigning(this.tx_json, account);
};
Transaction.prototype.hash = function(prefix_, asUINT256, serialized) {
let prefix;
if (typeof prefix_ !== 'string') {
prefix = hashprefixes.HASH_TX_ID;
} else if (!hashprefixes.hasOwnProperty(prefix_)) {
throw new Error('Unknown hashing prefix requested: ' + prefix_);
} else {
prefix = hashprefixes[prefix_];
}
const hash = (serialized || this.serialize()).hash(prefix);
return asUINT256 ? hash : hash.to_hex();
Transaction.prototype.hash = function() {
return computeTransactionHash(this.tx_json);
};
Transaction.prototype.sign = function(secret) {
@@ -506,8 +495,7 @@ Transaction.prototype.sign = function(secret) {
}
const keypair = deriveKeypair(secret || this._secret);
this.tx_json.TxnSignature = sign(this.signingData().buffer,
keypair.privateKey);
this.tx_json.TxnSignature = sign(this.signingData(), keypair.privateKey);
this.previousSigningHash = hash;
return this;
@@ -692,14 +680,10 @@ Transaction.prototype.sourceTag = function(tag) {
};
Transaction.prototype._setAccount = function(name, value) {
const uInt160 = UInt160.from_json(value);
if (!uInt160.is_valid()) {
if (!isValidAddress(value)) {
throw new Error(name + ' must be a valid account');
}
this.tx_json[name] = uInt160.to_json();
this.tx_json[name] = value;
return this;
};
@@ -715,12 +699,12 @@ Transaction.prototype._setAmount = function(name, amount, options_) {
throw new Error(name + ' value must be non-negative');
}
const isNative = parsedAmount.currency().is_native();
const isNative = parsedAmount.is_native();
if (isNative && options.no_native) {
throw new Error(name + ' must be a non-native amount');
}
if (!(isNative || parsedAmount.currency().is_valid())) {
if (!(isNative || isValidCurrency(parsedAmount.currency()))) {
throw new Error(name + ' must have a valid currency');
}
if (!(isNative || isValidAddress(parsedAmount.issuer()))) {
@@ -1176,15 +1160,15 @@ Transaction._rewritePath = function(path) {
const newNode = { };
if (node.hasOwnProperty('account')) {
newNode.account = UInt160.json_rewrite(node.account);
newNode.account = node.account;
}
if (node.hasOwnProperty('issuer')) {
newNode.issuer = UInt160.json_rewrite(node.issuer);
newNode.issuer = node.issuer;
}
if (node.hasOwnProperty('currency')) {
newNode.currency = Currency.json_rewrite(node.currency);
newNode.currency = normalizeCurrency(node.currency);
}
if (node.hasOwnProperty('type_hex')) {
@@ -1384,7 +1368,7 @@ Transaction.prototype.offerCancel = function(options_) {
Transaction._prepareSignerEntry = function(signer) {
const {account, weight} = signer;
assert(UInt160.is_valid(account), 'Signer account invalid');
assert(isValidAddress(account), 'Signer account invalid');
assert(_.isNumber(weight), 'Signer weight missing');
assert(weight > 0 && weight <= 65535, 'Signer weight must be 1-65535');
@@ -1606,7 +1590,7 @@ Transaction.prototype.setSigners = function(signers) {
};
Transaction.prototype.addMultiSigner = function(signer) {
assert(UInt160.is_valid(signer.Account), 'Signer must have a valid Account');
assert(isValidAddress(signer.Account), 'Signer must have a valid Account');
if (_.isUndefined(this.tx_json.Signers)) {
this.tx_json.Signers = [];
@@ -1615,8 +1599,8 @@ Transaction.prototype.addMultiSigner = function(signer) {
this.tx_json.Signers.push({Signer: signer});
this.tx_json.Signers.sort((a, b) => {
return UInt160.from_json(a.Signer.Account)
.cmp(UInt160.from_json(b.Signer.Account));
return (new Buffer(decodeAddress(a.Signer.Account))).compare(
new Buffer(decodeAddress(b.Signer.Account)));
});
return this;
@@ -1659,7 +1643,7 @@ Transaction.prototype.multiSign = function(account, secret) {
const signer = {
Account: account,
TxnSignature: sign(signingData.buffer, keypair.privateKey),
TxnSignature: sign(signingData, keypair.privateKey),
SigningPubKey: keypair.publicKey
};

View File

@@ -524,9 +524,9 @@ TransactionManager.prototype._prepareRequest = function(tx) {
tx.sign();
const serialized = tx.serialize();
submitRequest.txBlob(serialized.to_hex());
submitRequest.txBlob(serialized);
const hash = tx.hash(null, null, serialized);
const hash = tx.hash(null, serialized);
tx.addId(hash);
} else {
if (tx.hasMultiSigners()) {
@@ -724,6 +724,10 @@ TransactionManager.prototype.submit = function(tx) {
return;
}
tx.once('cleanup', function() {
self.getPending().remove(tx);
});
if (!_.isNumber(tx.tx_json.Sequence)) {
// Honor manually-set sequences
tx.setSequence(this._nextSequence++);
@@ -735,13 +739,8 @@ TransactionManager.prototype.submit = function(tx) {
if (tx.hasMultiSigners()) {
tx.setResubmittable(false);
tx.setSigningPubKey('');
}
tx.once('cleanup', function() {
self.getPending().remove(tx);
});
if (!tx.complete()) {
this._nextSequence -= 1;
return;

View File

@@ -1,269 +0,0 @@
'use strict';
/* eslint new-cap: 1 */
const assert = require('assert');
const lodash = require('lodash');
const sjclcodec = require('sjcl-codec');
const utils = require('./utils');
const BN = require('bn.js');
//
// Abstract UInt class
//
// Base class for UInt classes
//
function UInt() {
// Internal form: NaN or BN
this._value = NaN;
}
UInt.json_rewrite = function(j, opts) {
return this.from_json(j).to_json(opts);
};
// Return a new UInt from j.
UInt.from_generic = function(j) {
if (j instanceof this) {
return j.clone();
}
return (new this()).parse_generic(j);
};
// Return a new UInt from j.
UInt.from_hex = function(j) {
if (j instanceof this) {
return j.clone();
}
return (new this()).parse_hex(j);
};
// Return a new UInt from j.
UInt.from_json = function(j) {
if (j instanceof this) {
return j.clone();
}
return (new this()).parse_json(j);
};
// Return a new UInt from j.
UInt.from_bits = function(j) {
if (j instanceof this) {
return j.clone();
}
return (new this()).parse_bits(j);
};
// Return a new UInt from j.
UInt.from_bytes = function(j) {
if (j instanceof this) {
return j.clone();
}
return (new this()).parse_bytes(j);
};
// Return a new UInt from j.
UInt.from_number = function(j) {
if (j instanceof this) {
return j.clone();
}
return (new this()).parse_number(j);
};
UInt.is_valid = function(j) {
return this.from_json(j).is_valid();
};
UInt.prototype.clone = function() {
return this.copyTo(new this.constructor());
};
// Returns copy.
UInt.prototype.copyTo = function(d) {
d._value = this._value;
if (this._version_byte !== undefined) {
d._version_byte = this._version_byte;
}
if (typeof d._update === 'function') {
d._update();
}
return d;
};
UInt.prototype.equals = function(o) {
return this.is_valid() &&
o.is_valid() &&
// This throws but the expression will short circuit
this.cmp(o) === 0;
};
UInt.prototype.cmp = function(o) {
assert(this.is_valid() && o.is_valid());
return this._value.cmp(o._value);
};
UInt.prototype.greater_than = function(o) {
return this.cmp(o) > 0;
};
UInt.prototype.less_than = function(o) {
return this.cmp(o) < 0;
};
UInt.prototype.is_valid = function() {
return this._value instanceof BN;
};
UInt.prototype.is_zero = function() {
// cmpn means cmp with N)umber
return this.is_valid() && this._value.cmpn(0) === 0;
};
/**
* Update any derivative values.
*
* This allows subclasses to maintain caches of any data that they derive from
* the main _value. For example, the Currency class keeps the currency type, the
* currency code and other information about the currency cached.
*
* The reason for keeping this mechanism in this class is so every subclass can
* call it whenever it modifies the internal state.
*
* @return {void}
*/
UInt.prototype._update = function() {
// Nothing to do by default. Subclasses will override this.
};
// value = NaN on error.
UInt.prototype.parse_generic = function(j) {
const subclass = this.constructor;
assert(typeof subclass.width === 'number', 'UInt missing width');
this._value = NaN;
switch (j) {
case undefined:
case '0':
case subclass.STR_ZERO:
case subclass.ACCOUNT_ZERO:
case subclass.HEX_ZERO:
this._value = new BN(0);
break;
case '1':
case subclass.STR_ONE:
case subclass.ACCOUNT_ONE:
case subclass.HEX_ONE:
this._value = new BN(1);
break;
default:
if (lodash.isString(j)) {
switch (j.length) {
case subclass.width:
const hex = utils.arrayToHex(utils.stringToArray(j));
this._value = new BN(hex, 16);
break;
case subclass.width * 2:
// Assume hex, check char set
this.parse_hex(j);
break;
}
} else if (lodash.isNumber(j)) {
this.parse_number(j);
} else if (lodash.isArray(j)) {
// Assume bytes array
this.parse_bytes(j);
}
}
this._update();
return this;
};
UInt.prototype.parse_hex = function(j) {
if (new RegExp(`^[0-9A-Fa-f]{${this.constructor.width * 2}}$`).test(j)) {
this._value = new BN(j, 16);
} else {
this._value = NaN;
}
this._update();
return this;
};
UInt.prototype.parse_bits = function(j) {
return this.parse_bytes(sjclcodec.bytes.fromBits(j));
};
UInt.prototype.parse_bytes = function(j) {
if (Array.isArray(j) && j.length === this.constructor.width) {
this._value = new BN(j);
} else {
this._value = NaN;
}
this._update();
return this;
};
UInt.prototype.parse_json = UInt.prototype.parse_hex;
UInt.prototype.parse_number = function(j) {
this._value = NaN;
if (typeof j === 'number' && isFinite(j) && j >= 0) {
this._value = new BN(j);
}
this._update();
return this;
};
// Convert from internal form.
UInt.prototype.to_bytes = function() {
if (!this.is_valid()) {
return null;
}
return this._value.toArray('be', this.constructor.width);
};
UInt.prototype.to_hex = function() {
if (!this.is_valid()) {
return null;
}
return utils.arrayToHex(this.to_bytes());
};
UInt.prototype.to_json = UInt.prototype.to_hex;
// Convert from internal form.
UInt.prototype.to_bits = function() {
if (!this.is_valid()) {
return null;
}
return sjclcodec.bytes.toBits(this.to_bytes());
};
exports.UInt = UInt;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,25 +0,0 @@
'use strict';
const utils = require('./utils');
const extend = require('extend');
const UInt = require('./uint').UInt;
//
// UInt128 support
//
const UInt128 = extend(function() {
this._value = NaN;
}, UInt);
UInt128.width = 16;
UInt128.prototype = Object.create(extend({}, UInt.prototype));
UInt128.prototype.constructor = UInt128;
const HEX_ZERO = UInt128.HEX_ZERO = '00000000000000000000000000000000';
const HEX_ONE = UInt128.HEX_ONE = '00000000000000000000000000000000';
UInt128.STR_ZERO = utils.hexToString(HEX_ZERO);
UInt128.STR_ONE = utils.hexToString(HEX_ONE);
exports.UInt128 = UInt128;

View File

@@ -1,97 +0,0 @@
'use strict';
const utils = require('./utils');
const extend = require('extend');
const UInt = require('./uint').UInt;
const Base = require('./base').Base;
//
// UInt160 support
//
const UInt160 = extend(function() {
this._value = NaN;
this._version_byte = undefined;
this._update();
}, UInt);
UInt160.width = 20;
UInt160.prototype = Object.create(extend({}, UInt.prototype));
UInt160.prototype.constructor = UInt160;
const HEX_ZERO = UInt160.HEX_ZERO = '0000000000000000000000000000000000000000';
const HEX_ONE = UInt160.HEX_ONE = '0000000000000000000000000000000000000001';
UInt160.ACCOUNT_ZERO = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp';
UInt160.ACCOUNT_ONE = 'rrrrrrrrrrrrrrrrrrrrBZbvji';
UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
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) {
if (typeof j === 'number' && !isNaN(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.parse_number(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._version_byte = Base.VER_ACCOUNT_ID;
} else {
this.parse_hex(j);
}
this._update();
return this;
};
UInt160.prototype.parse_generic = function(j) {
UInt.prototype.parse_generic.call(this, j);
if (isNaN(this._value)) {
if ((typeof j === 'string') && j[0] === 'r') {
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
}
}
this._update();
return this;
};
// XXX Json form should allow 0 and 1, C++ doesn't currently allow it.
UInt160.prototype.to_json = function(opts = {}) {
if (this.is_valid()) {
// If this value has a type, return a Base58 encoded string.
if (typeof this._version_byte === 'number') {
let output = Base.encode_check(this._version_byte, this.to_bytes());
if (opts.gateways && output in opts.gateways) {
output = opts.gateways[output];
}
return output;
}
return this.to_hex();
}
return NaN;
};
exports.UInt160 = UInt160;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,28 +0,0 @@
'use strict';
const utils = require('./utils');
const extend = require('extend');
const UInt = require('./uint').UInt;
//
// UInt256 support
//
const UInt256 = extend(function() {
this._value = NaN;
}, UInt);
UInt256.width = 32;
UInt256.prototype = Object.create(extend({}, UInt.prototype));
UInt256.prototype.constructor = UInt256;
const HEX_ZERO = UInt256.HEX_ZERO = '00000000000000000000000000000000' +
'00000000000000000000000000000000';
const HEX_ONE = UInt256.HEX_ONE = '00000000000000000000000000000000' +
'00000000000000000000000000000001';
UInt256.STR_ZERO = utils.hexToString(HEX_ZERO);
UInt256.STR_ONE = utils.hexToString(HEX_ONE);
exports.UInt256 = UInt256;

View File

@@ -1,4 +1,13 @@
'use strict';
const sha512 = require('hash.js').sha512;
// For a hash function, rippled uses SHA-512 and then truncates the result
// to the first 256 bytes. This algorithm, informally called SHA-512Half,
// provides an output that has comparable security to SHA-256, but runs
// faster on 64-bit processors.
function sha512half(buffer) {
return sha512().update(buffer).digest('hex').toUpperCase().slice(0, 64);
}
// returns the mantissa from the passed in string,
// adding zeros until it has 16 sd
@@ -21,7 +30,7 @@ function getMantissaDecimalString(bignum) {
function trace(comment, func) {
return function() {
console.log('%s: %s', trace, arguments.toString);
console.log('%s: %s', comment, arguments.toString);
func(arguments);
};
}
@@ -112,7 +121,8 @@ function assert(assertion, msg) {
* @return {Array} unique values (for string representation of value) in `arr`
*/
function arrayUnique(arr) {
const u = {}, a = [];
const u = {};
const a = [];
for (let i = 0, l = arr.length; i < l; i++) {
const k = arr[i];
@@ -151,6 +161,7 @@ exports.time = {
toRipple: fromTimestamp
};
exports.sha512half = sha512half;
exports.trace = trace;
exports.arraySet = arraySet;
exports.hexToString = hexToString;

View File

@@ -2,8 +2,6 @@
'use strict';
const assert = require('assert');
const Amount = require('ripple-lib').Amount;
const UInt160 = require('ripple-lib').UInt160;
describe('Amount', function() {
describe('Negatives', function() {
@@ -170,10 +168,10 @@ describe('Amount', function() {
assert.strictEqual(Amount.from_human('12345.6789 XAU').to_human_full(), '12,345.6789/XAU/NaN');
});
it('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000', function() {
assert.strictEqual(Amount.from_human('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_text_full(), '12345.6789/XAU (-0.5%pa)/NaN');
assert.strictEqual(Amount.from_human('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_text_full(), '12345.6789/015841551A748AD2C1F76FF6ECB0CCCD00000000/NaN');
});
it('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000 human', function() {
assert.strictEqual(Amount.from_human('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_human_full(), '12,345.6789/XAU (-0.5%pa)/NaN');
assert.strictEqual(Amount.from_human('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_human_full(), '12,345.6789/015841551A748AD2C1F76FF6ECB0CCCD00000000/NaN');
});
it('12345.6789 0000000000000000000000005553440000000000', function() {
assert.strictEqual(Amount.from_human('12345.6789 0000000000000000000000005553440000000000').to_text_full(), '12345.6789/USD/NaN');
@@ -246,22 +244,22 @@ describe('Amount', function() {
});
describe('from_number', function() {
it('Number 1', function() {
assert.strictEqual(Amount.from_number(1).to_text_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(1).to_text_full(), '1/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Number 1 human', function() {
assert.strictEqual(Amount.from_number(1).to_human_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(1).to_human_full(), '1/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Number 2', function() {
assert.strictEqual(Amount.from_number(2).to_text_full(), '2/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(2).to_text_full(), '2/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Number 2 human', function() {
assert.strictEqual(Amount.from_number(2).to_human_full(), '2/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(2).to_human_full(), '2/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply 2 "1" with 3 "1", by product_human', function() {
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_text_full(), '6/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_text_full(), '6/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply 2 "1" with 3 "1", by product_human human', function() {
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_human_full(), '6/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_human_full(), '6/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply 3 USD with 3 "1"', function() {
assert.strictEqual(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_number(3)).to_text_full(), '9/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
@@ -270,16 +268,16 @@ describe('Amount', function() {
assert.strictEqual(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_number(3)).to_human_full(), '9/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Multiply -1 "1" with 3 USD', function() {
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply -1 "1" with 3 USD human', function() {
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply -1 "1" with 3 USD, by product_human', function() {
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
it('Multiply -1 "1" with 3 USD, by product_human human', function() {
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
});
});
describe('text_full_rewrite', function() {
@@ -292,26 +290,6 @@ describe('Amount', function() {
assert.strictEqual('1', Amount.json_rewrite(1));
});
});
describe('UInt160', function() {
it('Parse 0 export', function() {
assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_generic('0').set_version(0).to_json());
});
it('Parse 1', function() {
assert.deepEqual(UInt160.ACCOUNT_ONE, UInt160.from_generic('1').set_version(0).to_json());
});
it('Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export', function() {
assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_json('rrrrrrrrrrrrrrrrrrrrrhoLvTp').to_json());
});
it('Parse rrrrrrrrrrrrrrrrrrrrBZbvji export', function() {
assert.strictEqual(UInt160.ACCOUNT_ONE, UInt160.from_json('rrrrrrrrrrrrrrrrrrrrBZbvji').to_json());
});
it('is_valid rrrrrrrrrrrrrrrrrrrrrhoLvTp', function() {
assert(UInt160.is_valid('rrrrrrrrrrrrrrrrrrrrrhoLvTp'));
});
it('!is_valid rrrrrrrrrrrrrrrrrrrrrhoLvT', function() {
assert(!UInt160.is_valid('rrrrrrrrrrrrrrrrrrrrrhoLvT'));
});
});
describe('Amount validity', function() {
it('is_valid 1', function() {
assert(Amount.is_valid(1));
@@ -342,16 +320,10 @@ describe('Amount', function() {
assert(isNaN(Amount.from_json('x').to_text()));
});
it('parse dem', function() {
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('parse dem human', function() {
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('parse dem', function() {
assert.strictEqual(Amount.from_json('10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('parse dem human', function() {
assert.strictEqual(Amount.from_json('10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full(), '10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Parse native 0', function() {
assert.strictEqual('0/XRP', Amount.from_json('0').to_text_full());
@@ -970,9 +942,6 @@ describe('Amount', function() {
it('Multiply XRP with XRP', function() {
assert.strictEqual(Amount.from_json('10000000').product_human(Amount.from_json('10')).to_text_full(), '0.0001/XRP');
});
it('Multiply USD with XAU (dem)', function() {
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '19900.00316303883/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Multiply 0 XRP with 0 XRP human', function() {
assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0')).to_human_full());
});
@@ -1042,9 +1011,6 @@ describe('Amount', function() {
it('Multiply XRP with XRP human', function() {
assert.strictEqual(Amount.from_json('10000000').product_human(Amount.from_json('10')).to_human_full(), '0.0001/XRP');
});
it('Multiply USD with XAU (dem) human', function() {
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_human_full(), '19,900.00316303883/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
});
describe('ratio_human', function() {
@@ -1059,12 +1025,6 @@ describe('Amount', function() {
assert.deepEqual(c.to_json(), {value: '0.005441114728882572',
currency: 'USD', issuer: 'rLFPPebckMYZf3urdomLsaqRGmQ6zHVrrK'});
});
it('Divide USD by XAU (dem)', function() {
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').ratio_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '201.0049931765529/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('Divide USD by XAU (dem) human', function() {
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').ratio_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_human_full(), '201.0049931765529/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
});
describe('_invert', function() {
@@ -1112,18 +1072,6 @@ describe('Amount', function() {
it('BTC/USD inverse', function() {
assert.strictEqual(Amount.from_quality('20294C923E80A51B487EB9547B3835FD483748B170D2D0A455071AFD498D0000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {inverse: true, base_currency: 'BTC'}).to_text_full(), '0.5/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
});
it('XAU(dem)/XRP', function() {
assert.strictEqual(Amount.from_quality('587322CCBDE0ABD01704769A73A077C32FB39057D813D4165F1FF973CAF997EF', 'XRP', NaN, {base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_text_full(), '90,452.246928/XRP');
});
it('XAU(dem)/XRP inverse', function() {
assert.strictEqual(Amount.from_quality('F72C7A9EAE4A45ED1FB547AD037D07B9B965C6E662BEBAFA4A03F2A976804235', 'XRP', NaN, {inverse: true, base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_text_full(), '90,442.196677/XRP');
});
it('USD/XAU(dem)', function() {
assert.strictEqual(Amount.from_quality('4743E58E44974B325D42FD2BB683A6E36950F350EE46DD3A521B644B99782F5F', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007710100231303007/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
});
it('USD/XAU(dem) inverse', function() {
assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007675186123263489/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
});
it('BTC/XRP human', function() {
assert.strictEqual(Amount.from_quality('7B73A610A009249B0CC0D4311E8BA7927B5A34D86634581C5F0FF9FF678E1000', 'XRP', NaN, {base_currency: 'BTC'}).to_human_full(), '44,970/XRP');
});
@@ -1142,57 +1090,6 @@ describe('Amount', function() {
it('BTC/USD inverse human', function() {
assert.strictEqual(Amount.from_quality('20294C923E80A51B487EB9547B3835FD483748B170D2D0A455071AFD498D0000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {inverse: true, base_currency: 'BTC'}).to_human_full(), '0.5/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
});
it('XAU(dem)/XRP human', function() {
assert.strictEqual(Amount.from_quality('587322CCBDE0ABD01704769A73A077C32FB39057D813D4165F1FF973CAF997EF', 'XRP', NaN, {base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_human_full(), '90,452.246928/XRP');
});
it('XAU(dem)/XRP inverse human', function() {
assert.strictEqual(Amount.from_quality('F72C7A9EAE4A45ED1FB547AD037D07B9B965C6E662BEBAFA4A03F2A976804235', 'XRP', NaN, {inverse: true, base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_human_full(), '90,442.196677/XRP');
});
it('USD/XAU(dem) human', function() {
assert.strictEqual(Amount.from_quality('4743E58E44974B325D42FD2BB683A6E36950F350EE46DD3A521B644B99782F5F', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {base_currency: 'USD', reference_date: 443845330 + 31535000}).to_human_full(), '0.007710100231303007/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
});
it('USD/XAU(dem) inverse human', function() {
assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_human_full(), '0.007675186123263489/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
});
});
describe('apply interest', function() {
it('from_json apply interest 10 XAU', function() {
let demAmount = Amount.from_json('10/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
demAmount = demAmount.applyInterest(459990264);
assert.strictEqual(demAmount.to_text_full(), '9.294949401870436/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('from_json apply interest XAU', function() {
let demAmount = Amount.from_json('1235.5/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_text_full(), '1235.5/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
demAmount = demAmount.applyInterest(459990264);
assert.strictEqual(demAmount.to_text_full(), '1148.390998601092/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('from_human with reference date', function() {
const demAmount = Amount.from_human('10 0158415500000000C1F76FF6ECB0BAC600000000', {reference_date: 459990264});
demAmount.set_issuer('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_text_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('from_json apply interest 10 XAU human', function() {
let demAmount = Amount.from_json('10/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
demAmount = demAmount.applyInterest(459990264);
assert.strictEqual(demAmount.to_human_full(), '9.294949401870436/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('from_json apply interest XAU human', function() {
let demAmount = Amount.from_json('1235.5/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_human_full(), '1,235.5/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
demAmount = demAmount.applyInterest(459990264);
assert.strictEqual(demAmount.to_human_full(), '1,148.390998601092/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
it('from_human with reference date human', function() {
const demAmount = Amount.from_human('10 0158415500000000C1F76FF6ECB0BAC600000000', {reference_date: 459990264});
demAmount.set_issuer('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
assert.strictEqual(demAmount.to_human_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
});
describe('amount limits', function() {

View File

@@ -196,9 +196,45 @@ describe('RippleAPI', function() {
});
});
it('getBalances', function() {
return this.api.getBalances(address).then(
_.partial(checkResult, responses.getBalances, 'getBalances'));
describe('RippleAPI', function() {
it('getBalances', function() {
return this.api.getBalances(address).then(
_.partial(checkResult, responses.getBalances, 'getBalances'));
});
it('getBalances - limit', function() {
const options = {
limit: 3
};
const expectedResponse = responses.getBalances.slice(0, 3);
return this.api.getBalances(address, options).then(
_.partial(checkResult, expectedResponse, 'getBalances'));
});
it('getBalances - limit & currency', function() {
const options = {
currency: 'USD',
limit: 3
};
const expectedResponse = _.filter(responses.getBalances,
item => item.currency === 'USD').slice(0, 3);
return this.api.getBalances(address, options).then(
_.partial(checkResult, expectedResponse, 'getBalances'));
});
it('getBalances - limit & currency & issuer', function() {
const options = {
currency: 'USD',
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
limit: 3
};
const expectedResponse = _.filter(responses.getBalances,
item => item.currency === 'USD' &&
item.counterparty === 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B').slice(0, 3);
return this.api.getBalances(address, options).then(
_.partial(checkResult, expectedResponse, 'getBalances'));
});
});
it('getBalanceSheet', function() {

View File

@@ -1,66 +0,0 @@
'use strict';
const assert = require('assert');
const Base = require('ripple-lib').Base;
const fixtures = require('./fixtures/base58.json');
function digitArray(str) {
return str.split('').map(function(d) {
return parseInt(d, 10);
});
}
function hexToByteArray(hex) {
const byteArray = [];
for (let i = 0; i < hex.length / 2; i++) {
byteArray.push(parseInt(hex.slice(2 * i, 2 * i + 2), 16));
}
return byteArray;
}
describe('Base', function() {
describe('encode_check', function() {
it('0', function() {
const encoded = Base.encode_check(0, digitArray('00000000000000000000'));
assert.strictEqual(encoded, 'rrrrrrrrrrrrrrrrrrrrrhoLvTp');
});
it('1', function() {
const encoded = Base.encode_check(0, digitArray('00000000000000000001'));
assert.strictEqual(encoded, 'rrrrrrrrrrrrrrrrrrrrBZbvji');
});
});
describe('decode_check', function() {
it('rrrrrrrrrrrrrrrrrrrrrhoLvTp', function() {
const decoded = Base.decode_check(0, 'rrrrrrrrrrrrrrrrrrrrrhoLvTp');
assert(decoded.cmpn(0) === 0);
});
it('rrrrrrrrrrrrrrrrrrrrBZbvji', function() {
const decoded = Base.decode_check(0, 'rrrrrrrrrrrrrrrrrrrrBZbvji');
assert(decoded.cmpn(1) === 0);
});
});
describe('decode-encode identity', function() {
it('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
const decoded = Base.decode('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
const encoded = Base.encode(decoded);
assert.strictEqual(encoded, 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
});
});
describe('encode', function() {
it('fixtures', function() {
for (let i = 0; i < fixtures.ripple.length; i++) {
const testCase = fixtures.ripple[i];
const encoded = Base.encode(hexToByteArray(testCase.hex));
assert.strictEqual(encoded, testCase.string);
}
});
});
describe('decode', function() {
it('fixtures', function() {
for (let i = 0; i < fixtures.ripple.length; i++) {
const testCase = fixtures.ripple[i];
const decoded = Base.decode(testCase.string);
assert.deepEqual(decoded, hexToByteArray(testCase.hex));
}
});
});
});

View File

@@ -1,53 +0,0 @@
'use strict';
var assert = require('assert');
var convertBase = require('ripple-lib').convertBase;
// Test cases from RFC-1924 (a joke RFC)
var BASE85 = ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ 'abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~');
var BASE10 = BASE85.slice(0, 10);
var BASE16 = BASE85.slice(0, 16);
var DATA16 = '108000000000000000080800200C417A';
var DATA10 = '21932261930451111902915077091070067066';
var DATA85 = '4)+k&C#VzJ4br>0wv%Yp';
function encode(digitArray, encoding) {
return digitArray.map(function(i) {
return encoding.charAt(i);
}).join('');
}
function decode(encoded, encoding) {
return encoded.split('').map(function(c) {
return encoding.indexOf(c);
});
}
function convertBaseEncoded(value, fromEncoding, toEncoding) {
var digitArray = decode(value, fromEncoding);
var converted = convertBase(digitArray, fromEncoding.length,
toEncoding.length);
return encode(converted, toEncoding);
}
describe('convertBase', function() {
it('DEC -> HEX', function () {
assert.strictEqual(convertBaseEncoded(DATA10, BASE10, BASE16), DATA16);
});
it('HEX -> DEC', function () {
assert.strictEqual(convertBaseEncoded(DATA16, BASE16, BASE10), DATA10);
});
it('DEC -> B85', function () {
assert.strictEqual(convertBaseEncoded(DATA10, BASE10, BASE85), DATA85);
});
it('HEX -> B85', function () {
assert.strictEqual(convertBaseEncoded(DATA16, BASE16, BASE85), DATA85);
});
it('B85 -> DEC', function () {
assert.strictEqual(convertBaseEncoded(DATA85, BASE85, BASE10), DATA10);
});
it('B85 -> HEX', function () {
assert.strictEqual(convertBaseEncoded(DATA85, BASE85, BASE16), DATA16);
});
});

View File

@@ -1,302 +0,0 @@
/*eslint-disable */
var assert = require('assert');
var currency = require('ripple-lib').Currency;
var timeUtil = require('ripple-lib').utils.time;
describe('Currency', function() {
describe('json_rewrite', function() {
it('json_rewrite("USD") == "USD"', function() {
assert.strictEqual('USD', currency.json_rewrite('USD'));
});
it('json_rewrite("NaN") == "XRP"', function() {
assert.strictEqual('XRP', currency.json_rewrite(NaN));
});
it('json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000") == "XAU (-0.5%pa)"', function() {
assert.strictEqual(currency.json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000"),
"XAU (-0.5%pa)");
});
});
describe('from_json', function() {
it('from_json().to_json() == "XRP"', function() {
var r = currency.from_json();
assert(!r.is_valid());
assert.strictEqual('XRP', r.to_json());
});
it('from_json(NaN).to_json() == "XRP"', function() {
var r = currency.from_json(NaN);
assert(!r.is_valid());
assert.strictEqual('XRP', r.to_json());
});
it('from_json().to_json("") == "XRP"', function() {
var r = currency.from_json('');
assert(r.is_valid());
assert(r.is_native());
assert.strictEqual('XRP', r.to_json());
});
it('from_json("XRP").to_json() == "XRP"', function() {
var r = currency.from_json('XRP');
assert(r.is_valid());
assert(r.is_native());
assert.strictEqual('XRP', r.to_json());
});
it('from_json("0000000000000000000000000000000000000000").to_json() == "XRP"', function() {
var r = currency.from_json('0000000000000000000000000000000000000000');
assert(r.is_valid());
assert(r.is_native());
assert.strictEqual('XRP', r.to_json());
});
it('from_json("111").to_human()', function() {
var r = currency.from_json("111");
assert(r.is_valid());
assert.strictEqual('111', r.to_json());
});
it('from_json("1D2").to_human()', function() {
var r = currency.from_json("1D2");
assert(r.is_valid());
assert.strictEqual('1D2', r.to_json());
});
it('from_json("1").to_human()', function() {
var r = currency.from_json('1');
assert(r.is_valid());
assert.strictEqual(1, r.to_json());
});
it('from_json("#$%").to_human()', function() {
var r = currency.from_json('#$%');
assert(r.is_valid());
assert.strictEqual('0000000000000000000000002324250000000000', r.to_json());
});
it('from_json("XAU").to_json() hex', function() {
var r = currency.from_json("XAU");
assert.strictEqual('0000000000000000000000005841550000000000', r.to_json({force_hex: true}));
});
it('from_json("XAU (0.5%pa").to_json() hex', function() {
var r = currency.from_json("XAU (0.5%pa)");
assert.strictEqual('015841550000000041F78E0A28CBF19200000000', r.to_json({force_hex: true}));
});
it('json_rewrite("015841550000000041F78E0A28CBF19200000000").to_json() hex', function() {
var r = currency.json_rewrite('015841550000000041F78E0A28CBF19200000000');
assert.strictEqual('XAU (0.5%pa)', r);
});
it('json_rewrite("015841550000000041F78E0A28CBF19200000000") hex', function() {
var r = currency.json_rewrite('015841550000000041F78E0A28CBF19200000000', {force_hex: true});
assert.strictEqual('015841550000000041F78E0A28CBF19200000000', r);
});
});
describe('from_human', function() {
it('From human "USD - Gold (-25%pa)"', function() {
var cur = currency.from_human('USD - Gold (-25%pa)');
assert.strictEqual(cur.to_json(), 'USD (-25%pa)');
assert.strictEqual(cur.to_hex(), '0155534400000000C19A22BC51297F0B00000000');
assert.strictEqual(cur.to_json(), cur.to_human());
});
it('From human "EUR (-0.5%pa)', function() {
var cur = currency.from_human('EUR (-0.5%pa)');
assert.strictEqual(cur.to_json(), 'EUR (-0.5%pa)');
});
it('From human "EUR (0.5361%pa)", test decimals', function() {
var cur = currency.from_human('EUR (0.5361%pa)');
assert.strictEqual(cur.to_json(), 'EUR (0.54%pa)');
assert.strictEqual(cur.to_json({decimals:4}), 'EUR (0.5361%pa)');
assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361);
});
it('From human "EUR - Euro (0.5361%pa)", test decimals and full_name', function() {
var cur = currency.from_human('EUR (0.5361%pa)');
assert.strictEqual(cur.to_json(), 'EUR (0.54%pa)');
assert.strictEqual(cur.to_json({decimals:4, full_name:'Euro'}), 'EUR - Euro (0.5361%pa)');
assert.strictEqual(cur.to_json({decimals:void(0), full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
assert.strictEqual(cur.to_json({decimals:undefined, full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
assert.strictEqual(cur.to_json({decimals:'henk', full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361);
});
it('From human "TYX - 30-Year Treasuries (1.5%pa)"', function() {
var cur = currency.from_human('TYX - 30-Year Treasuries (1.5%pa)');
assert.strictEqual(cur.to_json(), 'TYX (1.5%pa)');
});
it('From human "TYX - 30-Year Treasuries"', function() {
var cur = currency.from_human('TYX - 30-Year Treasuries');
assert.strictEqual(cur.to_json(), 'TYX');
});
it('From human "INR - Indian Rupees (-0.5%)"', function() {
var cur = currency.from_human('INR - Indian Rupees (-0.5%pa)');
assert.strictEqual(cur.to_json(), 'INR (-0.5%pa)');
});
it('From human "INR - 30 Indian Rupees"', function() {
var cur = currency.from_human('INR - 30 Indian Rupees');
assert.strictEqual(cur.to_json(), 'INR');
});
it('From human "XRP"', function() {
var cur = currency.from_human('XRP');
assert.strictEqual(cur.to_json(), 'XRP');
assert(cur.is_native(), true);
});
it('From human "XRP - Ripples"', function() {
var cur = currency.from_human('XRP - Ripples');
assert.strictEqual(cur.to_json(), 'XRP');
assert(cur.is_native(), true);
});
});
describe('to_human', function() {
it('"USD".to_human() == "USD"', function() {
assert.strictEqual('USD', currency.from_json('USD').to_human());
});
it('"NaN".to_human() == "XRP"', function() {
assert.strictEqual('XRP', currency.from_json(NaN).to_human());
});
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human(), 'XAU (-0.5%pa)');
});
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold'}), 'XAU - Gold (-0.5%pa)');
});
it('to_human interest XAU with full name, do not show interest', function() {
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold', show_interest:false}), 'XAU - Gold');
});
it('to_human interest XAU with full name, show interest', function() {
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold', show_interest:true}), 'XAU - Gold (-0.5%pa)');
});
it('to_human interest XAU, do show interest', function() {
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({show_interest:true}), 'XAU (-0.5%pa)');
});
it('to_human interest XAU, do not show interest', function() {
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({show_interest:false}), 'XAU');
});
it('to_human with full_name "USD - US Dollar show interest"', function() {
assert.strictEqual(currency.from_json('USD').to_human({full_name:'US Dollar', show_interest:true}), 'USD - US Dollar (0%pa)');
});
it('to_human with full_name "USD - US Dollar do not show interest"', function() {
assert.strictEqual(currency.from_json('USD').to_human({full_name:'US Dollar', show_interest:false}), 'USD - US Dollar');
});
it('to_human with full_name "USD - US Dollar"', function() {
assert.strictEqual('USD - US Dollar', currency.from_json('USD').to_human({full_name:'US Dollar'}));
});
it('to_human with full_name "XRP - Ripples"', function() {
assert.strictEqual('XRP - Ripples', currency.from_json('XRP').to_human({full_name:'Ripples'}));
});
it('to_human human "TIM" without full_name', function() {
var cur = currency.from_json("TIM");
assert.strictEqual(cur.to_human(), "TIM");
});
it('to_human "TIM" with null full_name', function() {
var cur = currency.from_json("TIM");
assert.strictEqual(cur.to_human({full_name: null}), "TIM");
});
});
describe('from_hex', function() {
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000" === "XAU (-0.5%pa)"', function() {
var cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000');
assert.strictEqual(cur.to_json(), 'XAU (-0.5%pa)');
assert.strictEqual(cur.to_hex(), '015841551A748AD2C1F76FF6ECB0CCCD00000000');
assert.strictEqual(cur.to_json(), cur.to_human());
});
});
describe('parse_json', function() {
it('should parse a currency object', function() {
assert.strictEqual('USD', new currency().parse_json(currency.from_json('USD')).to_json());
assert.strictEqual('USD (0.5%pa)', new currency().parse_json(currency.from_json('USD (0.5%pa)')).to_json());
});
it('should clone for parse_json on itself', function() {
var cur = currency.from_json('USD');
var cur2 = currency.from_json(cur);
assert.strictEqual(cur.to_json(), cur2.to_json());
cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000');
cur2 = currency.from_json(cur);
assert.strictEqual(cur.to_json(), cur2.to_json());
});
it('should parse json 0', function() {
var cur = currency.from_json(0);
assert.strictEqual(cur.to_json(), 'XRP');
assert.strictEqual(cur.get_iso(), 'XRP');
});
it('should parse json 0', function() {
var cur = currency.from_json('0');
assert.strictEqual(cur.to_json(), 'XRP');
assert.strictEqual(cur.get_iso(), 'XRP');
});
});
describe('is_valid', function() {
it('Currency.is_valid("XRP")', function() {
assert(currency.is_valid('XRP'));
});
it('!Currency.is_valid(NaN)', function() {
assert(!currency.is_valid(NaN));
});
it('from_json("XRP").is_valid()', function() {
assert(currency.from_json('XRP').is_valid());
});
it('!from_json(NaN).is_valid()', function() {
assert(!currency.from_json(NaN).is_valid());
});
});
describe('clone', function() {
it('should clone currency object', function() {
var c = currency.from_json('XRP');
assert.strictEqual('XRP', c.clone().to_json());
});
});
describe('to_human', function() {
it('should generate human string', function() {
assert.strictEqual('XRP', currency.from_json('XRP').to_human());
});
});
describe('has_interest', function() {
it('should be true for type 1 currency codes', function() {
assert(currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000').has_interest());
assert(currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000').has_interest());
});
it('should be false for type 0 currency codes', function() {
assert(!currency.from_hex('0000000000000000000000005553440000000000').has_interest());
assert(!currency.from_json('USD').has_interest());
});
});
function precision(num, precision) {
return +(Math.round(num + "e+"+precision) + "e-"+precision);
}
describe('get_interest_at', function() {
it('should return demurred value for demurrage currency', function() {
var cur = currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000');
// At start, no demurrage should occur
assert.equal(1, cur.get_interest_at(443845330));
assert.equal(1, precision(cur.get_interest_at(new Date(timeUtil.fromRipple(443845330))), 14));
// After one year, 0.5% should have occurred
assert.equal(0.995, precision(cur.get_interest_at(443845330 + 31536000), 14));
assert.equal(0.995, precision(cur.get_interest_at(new Date(timeUtil.fromRipple(443845330 + 31536000))), 14));
// After one demurrage period, 1/e should have occurred
var epsilon = 1e-14;
assert(Math.abs(
1/Math.E - cur.get_interest_at(443845330 + 6291418827.05)) < epsilon);
// One year before start, it should be (roughly) 0.5% higher.
assert.equal(1.005, precision(cur.get_interest_at(443845330 - 31536000), 4));
// One demurrage period before start, rate should be e
assert.equal(Math.E, cur.get_interest_at(443845330 - 6291418827.05));
});
it('should return 0 for currency without interest', function() {
var cur = currency.from_json('USD - US Dollar');
assert.equal(0, cur.get_interest_at(443845330));
assert.equal(0, cur.get_interest_at(443845330 + 31536000));
});
});
describe('get_iso', function() {
it('should get "XRP" iso_code', function() {
assert.strictEqual('XRP', currency.from_json('XRP').get_iso());
});
it('should get iso_code', function() {
assert.strictEqual('USD', currency.from_json('USD - US Dollar').get_iso());
});
it('should get iso_code', function() {
assert.strictEqual('USD', currency.from_json('USD (0.5%pa)').get_iso());
});
});
});

View File

@@ -1,6 +1,6 @@
[
{
"hash": "f8f337dee5d5b238a10af4a4d56926ba26c83ee7af5a5a6474340c56f9252df3",
"hash": "F8F337DEE5D5B238A10AF4A4D56926BA26C83EE7AF5A5A6474340C56F9252DF3",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
@@ -60,7 +60,7 @@
}
},
{
"hash": "f8d5de632b1d8b64e577c46912cce483d6df4fd4e2cf4a3d586a099de3b27021",
"hash": "F8D5DE632B1D8B64E577C46912CCE483D6DF4FD4E2CF4A3D586A099DE3B27021",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
@@ -120,7 +120,7 @@
}
},
{
"hash": "e9004490a92413e92dacd621ac73fd434a8950c350f7572ffeaf4d6aaf8fc288",
"hash": "E9004490A92413E92DACD621AC73FD434A8950C350F7572FFEAF4D6AAF8FC288",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
@@ -180,7 +180,7 @@
}
},
{
"hash": "d44bff924d23211b82b8f604af6d92f260f8dd13103a96f03e48825c4a978fd6",
"hash": "D44BFF924D23211B82B8F604AF6D92F260F8DD13103A96F03E48825C4A978FD6",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
@@ -240,7 +240,7 @@
}
},
{
"hash": "c978d915bfb17687335cbfc4b207d9e7213bcee35b468c2eee016cdce4edb6e4",
"hash": "C978D915BFB17687335CBFC4B207D9E7213BCEE35B468C2EEE016CDCE4EDB6E4",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
@@ -372,7 +372,7 @@
}
},
{
"hash": "31b34fd7c90cdc6cf680a814debc6f616c69275c0e99711f904de088a8ed4b28",
"hash": "31B34FD7C90CDC6CF680A814DEBC6F616C69275C0E99711F904DE088A8ED4B28",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
@@ -412,7 +412,7 @@
}
},
{
"hash": "260bc2964ffe6d81cb25c152f8054ffb2ce6ed04ff89d8d0d0559bc14bef0e46",
"hash": "260BC2964FFE6D81CB25C152F8054FFB2CE6ED04FF89D8D0D0559BC14BEF0E46",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {

View File

@@ -25,7 +25,7 @@
"value": "0.001"
}
},
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]"
"paths": "[[{\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\"}]]"
},
"outcome": {
"result": "tesSUCCESS",
@@ -117,7 +117,7 @@
"value": "0.001"
}
},
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]"
"paths": "[[{\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\"}]]"
},
"outcome": {
"result": "tesSUCCESS",

View File

@@ -2,7 +2,7 @@
const _ = require('lodash');
const BASE_LEDGER_INDEX = 8819951;
module.exports.normal = function(request, options={}) {
module.exports.normal = function(request, options = {}) {
_.defaults(options, {
ledger: BASE_LEDGER_INDEX
});
@@ -16,8 +16,7 @@ module.exports.normal = function(request, options={}) {
marker: options.marker,
limit: request.limit,
ledger_index: options.ledger,
lines: [
{
lines: _.filter([{
account: 'r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z',
balance: '0',
currency: 'ASP',
@@ -252,12 +251,12 @@ module.exports.normal = function(request, options={}) {
quality_out: 0,
freeze: true
}
]
], item => !request.peer || item.account === request.peer)
}
});
};
module.exports.counterparty = function(request, options={}) {
module.exports.counterparty = function(request, options = {}) {
_.defaults(options, {
ledger: BASE_LEDGER_INDEX
});
@@ -271,8 +270,7 @@ module.exports.counterparty = function(request, options={}) {
marker: options.marker,
limit: request.limit,
ledger_index: options.ledger,
lines: [
{
lines: [{
account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
balance: '0.3488146605801446',
currency: 'CHF',

View File

@@ -3,9 +3,9 @@
const _ = require('lodash');
const hashes = require('../../hashes');
const addresses = require('../../addresses');
const SerializedObject = require('ripple-lib').SerializedObject;
const AccountSet = require('./tx/account-set.json');
const NotFound = require('./tx/not-found.json');
const binary = require('ripple-binary-codec');
module.exports = function(request, options = {}) {
_.defaults(options, {
@@ -229,8 +229,8 @@ module.exports = function(request, options = {}) {
transactions: [
{
ledger_index: 348860 - Number(marker || 100),
tx_blob: SerializedObject.from_json(tx).to_hex(),
meta: SerializedObject.from_json(meta).to_hex(),
tx_blob: binary.encode(tx),
meta: binary.encode(meta),
validated: options.validated
}
]

View File

@@ -368,84 +368,56 @@
"Paths": [
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
},
{
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
},
{
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
}
]
],
@@ -697,84 +669,56 @@
"Paths": [
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
},
{
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
},
{
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
}
]
],
@@ -794,4 +738,4 @@
"validated": true
}
}
}
}

View File

@@ -104,61 +104,41 @@
"Paths": [
[
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
],
[
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
}
],
[
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rP9Lt7SZUEErkXAXyggceibTCEYbfSyx6n",
"type": 1,
"type_hex": "0000000000000001"
"account": "rP9Lt7SZUEErkXAXyggceibTCEYbfSyx6n"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
],
[
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rwBWBFZrbLzHoe3PhwWYv89iHJdxAFrxcB",
"type": 1,
"type_hex": "0000000000000001"
"account": "rwBWBFZrbLzHoe3PhwWYv89iHJdxAFrxcB"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
]
],
@@ -405,84 +385,56 @@
"Paths": [
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
},
{
"currency": "USD",
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
},
{
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
}
],
[
{
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
},
{
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 48,
"type_hex": "0000000000000030"
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
}
]
],
@@ -871,60 +823,40 @@
"Paths": [
[
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
}
],
[
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
}
],
[
{
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
"type": 1,
"type_hex": "0000000000000001"
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
{
"account": "rULnR9YhAkj9HrcxAcudzBhaXRSqT7zJkq",
"type": 1,
"type_hex": "0000000000000001"
"account": "rULnR9YhAkj9HrcxAcudzBhaXRSqT7zJkq"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
}
],
[
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
{
"account": "rP5ShE8dGBH6hHtNvRESdMceen36XFBQmh",
"type": 1,
"type_hex": "0000000000000001"
"account": "rP5ShE8dGBH6hHtNvRESdMceen36XFBQmh"
},
{
"currency": "XRP",
"type": 16,
"type_hex": "0000000000000010"
"currency": "XRP"
}
]
],
@@ -1225,4 +1157,4 @@
"validated": true
}
}
}
}

View File

@@ -5,9 +5,8 @@
const _ = require('lodash');
const addresses = require('./addresses');
const Meta = require('ripple-lib').Meta;
const SerializedObject = require('ripple-lib').SerializedObject;
const Types = require('ripple-lib').types;
const IOUValue = require('ripple-lib-value').IOUValue;
const binary = require('ripple-binary-codec');
module.exports.FIAT_BALANCE = '10';
module.exports.NATIVE_BALANCE = '55';
@@ -824,10 +823,7 @@ module.exports.transactionWithCreatedOffer = function(options) {
const takerPays = new IOUValue(module.exports.TAKER_PAYS);
const quality = takerPays.divide(takerGets);
const so = new SerializedObject();
Types.Quality.serialize(so, quality);
const BookDirectory = so.to_hex();
const BookDirectory = binary.encodeQuality(quality.toString());
const meta = new Meta({
AffectedNodes: [

View File

@@ -3,5 +3,5 @@
"title": "ledgerhash",
"description": "A ledger hash",
"type": "string",
"format": "ledgerHash"
"pattern": "^[A-F0-9]{64}$"
}

View File

@@ -1,94 +0,0 @@
/* eslint-disable max-len, valid-jsdoc */
'use strict';
const assert = require('assert');
const fs = require('fs');
const Ledger = require('ripple-lib').Ledger;
/**
* @param ledger_index {Number}
* Expects a corresponding ledger dump in $repo/test/fixtures/ folder
*/
function create_ledger_test(ledger_index) {
describe(String(ledger_index), function() {
const path = __dirname + '/fixtures/ledger-full-' + ledger_index + '.json';
const ledger_raw = fs.readFileSync(path);
const ledger_json = JSON.parse(ledger_raw);
const ledger = Ledger.from_json(ledger_json);
const hasAccounts = Array.isArray(ledger_json.accountState)
&& ledger_json.accountState.length > 0;
if (hasAccounts) {
it('has account_hash of ' + ledger_json.account_hash, function() {
assert.equal(ledger_json.account_hash,
ledger.calc_account_hash({sanity_test: true}).to_hex());
});
}
it('has transaction_hash of ' + ledger_json.transaction_hash, function() {
assert.equal(ledger_json.transaction_hash,
ledger.calc_tx_hash().to_hex());
});
});
}
describe('Ledger', function() {
// This is the first recorded ledger with a non empty transaction set
create_ledger_test(38129);
// Because, why not.
create_ledger_test(40000);
// 1311 AffectedNodes, no accounts
create_ledger_test(7501326);
describe('#calcAccountRootEntryHash', function() {
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
const account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
const expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
const actualEntryHash = Ledger.calcAccountRootEntryHash(account);
assert.equal(actualEntryHash.to_hex(), expectedEntryHash);
});
});
describe('#calcRippleStateEntryHash', function() {
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function() {
const account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
const account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
const currency = 'USD';
const expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
const actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
const actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
assert.equal(actualEntryHash1.to_hex(), expectedEntryHash);
assert.equal(actualEntryHash2.to_hex(), expectedEntryHash);
});
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function() {
const account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
const account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
const currency = 'UAM';
const expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
const actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
const actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
assert.equal(actualEntryHash1.to_hex(), expectedEntryHash);
assert.equal(actualEntryHash2.to_hex(), expectedEntryHash);
});
});
describe('#calcOfferEntryHash', function() {
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function() {
const account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
const sequence = 137;
const expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
const actualEntryHash = Ledger.calcOfferEntryHash(account, sequence);
assert.equal(actualEntryHash.to_hex(), expectedEntryHash);
});
});
});
// vim:sw=2:sts=2:ts=8:et

View File

@@ -5,7 +5,7 @@
const _ = require('lodash');
const assert = require('assert-diff');
const Remote = require('ripple-lib').Remote;
const Currency = require('ripple-lib').Currency;
const OrderbookUtils = require('ripple-lib')._test.OrderbookUtils;
const addresses = require('./fixtures/addresses');
const fixtures = require('./fixtures/orderbook');
const IOUValue = require('ripple-lib-value').IOUValue;
@@ -32,10 +32,10 @@ describe('OrderBook Autobridging', function() {
issuer_pays: addresses.ISSUER
});
assert.deepEqual(book._legOneBook._currencyGets.to_hex(), Currency.from_json('XRP').to_hex());
assert.deepEqual(book._legOneBook._currencyPays.to_hex(), Currency.from_json('USD').to_hex());
assert.deepEqual(book._legTwoBook._currencyGets.to_hex(), Currency.from_json('EUR').to_hex());
assert.deepEqual(book._legTwoBook._currencyPays.to_hex(), Currency.from_json('XRP').to_hex());
assert.deepEqual(book._legOneBook._currencyGets, 'XRP');
assert.deepEqual(book._legOneBook._currencyPays, 'USD');
assert.deepEqual(book._legTwoBook._currencyGets, 'EUR');
assert.deepEqual(book._legTwoBook._currencyPays, 'XRP');
});
it('Compute autobridged offers', function(done) {
@@ -843,4 +843,14 @@ describe('OrderBook Autobridging', function() {
});
});
it('convertOfferQualityToHexFromText', function() {
const bookDirectory =
'4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5D06F4C3362FE1D0';
const quality = '195796912.5171664';
assert.strictEqual(
OrderbookUtils.convertOfferQualityToHexFromText(quality),
bookDirectory.slice(-16)
);
});
});

View File

@@ -4,7 +4,6 @@
const assert = require('assert-diff');
const Remote = require('ripple-lib').Remote;
const Currency = require('ripple-lib').Currency;
const Amount = require('ripple-lib').Amount;
const Meta = require('ripple-lib').Meta;
const addresses = require('./fixtures/addresses');
@@ -12,7 +11,6 @@ const fixtures = require('./fixtures/orderbook');
const IOUValue = require('ripple-lib-value').IOUValue;
describe('OrderBook', function() {
this.timeout(0);
function createRemote() {
const remote = new Remote();
@@ -33,10 +31,10 @@ describe('OrderBook', function() {
assert.deepEqual(book.toJSON(), {
taker_gets: {
currency: Currency.from_json('XRP').to_hex()
currency: 'XRP'
},
taker_pays: {
currency: Currency.from_json('BTC').to_hex(),
currency: 'BTC',
issuer: addresses.ISSUER
}
});
@@ -49,11 +47,11 @@ describe('OrderBook', function() {
assert.deepEqual(book.toJSON(), {
taker_gets: {
currency: Currency.from_json('BTC').to_hex(),
currency: 'BTC',
issuer: addresses.ISSUER
},
taker_pays: {
currency: Currency.from_json('XRP').to_hex()
currency: 'XRP'
}
});
});
@@ -68,40 +66,155 @@ describe('OrderBook', function() {
assert(book.isValid());
});
it('Automatic subscription (based on listeners)', function(done) {
const book = createRemote().createOrderBook({
currency_gets: 'XRP',
issuer_pays: addresses.ISSUER,
currency_pays: 'BTC'
});
book.subscribe = function() {
done();
};
book.on('model', function() {});
});
it('Subscribe', function(done) {
const book = createRemote().createOrderBook({
currency_gets: 'XRP',
issuer_pays: addresses.ISSUER,
currency_pays: 'BTC'
const remote = createRemote();
const book = remote.createOrderBook({
currency_pays: 'XRP',
issuer_gets: addresses.ISSUER,
currency_gets: 'BTC'
});
let requestedOffers = false;
remote.request = function(request) {
const response = {};
book.subscribeTransactions = function() {
assert(requestedOffers);
done();
};
if (request.message.command === 'account_info') {
response.account_data = {
TransferRate: 1002000000
};
book.requestOffers = function(callback) {
requestedOffers = true;
callback();
} else if (request.message.command === 'book_offers') {
response.offers = [];
}
request.emit('success', response);
};
book.subscribe();
assert.strictEqual(book._subscribed, true);
done();
});
it('Subscribe - gets XRP', function(done) {
const remote = createRemote();
const book = remote.createOrderBook({
currency_gets: 'XRP',
issuer_pays: addresses.ISSUER,
currency_pays: 'BTC'
});
remote.isConnected = function() {
return false;
};
remote.request = function(request) {
const response = {};
if (request.message.command === 'book_offers') {
response.offers = [];
}
request.emit('success', response);
};
book.subscribe();
assert.strictEqual(book._subscribed, true, '_subscribed');
done();
});
it('Subscribe - autobridged', function(done) {
const remote = createRemote();
const book = remote.createOrderBook({
currency_pays: 'USD',
issuer_pays: addresses.ISSUER,
currency_gets: 'BTC',
issuer_gets: addresses.ISSUER
});
remote.request = function(request) {
const response = {};
if (request.message.command === 'account_info') {
response.account_data = {
TransferRate: 1002000000
};
} else if (request.message.command === 'book_offers') {
response.offers = [];
}
request.emit('success', response);
};
book.subscribe();
assert.strictEqual(book._subscribed, true, '_subscribed');
done();
});
it('Automatic subscription (based on listeners)', function(done) {
this.timeout(100);
const remote = createRemote();
const book = remote.createOrderBook({
currency_gets: 'XRP',
issuer_pays: addresses.ISSUER,
currency_pays: 'BTC'
});
remote.request = function(request) {
const response = {};
if (request.message.command === 'account_info') {
response.account_data = {
TransferRate: 1002000000
};
} else if (request.message.command === 'book_offers') {
response.offers = [];
}
setImmediate(function() {
request.emit('success', response);
});
};
book.on('model', function(offers) {
assert.strictEqual(offers.length, 0);
done();
});
});
it('Automatic subscription (based on listeners) - autobridged', function(done) {
this.timeout(100);
const remote = createRemote();
const book = remote.createOrderBook({
currency_pays: 'USD',
issuer_pays: addresses.ISSUER,
currency_gets: 'BTC',
issuer_gets: addresses.ISSUER
});
remote.request = function(request) {
const response = {};
if (request.message.command === 'account_info') {
response.account_data = {
TransferRate: 1002000000
};
} else if (request.message.command === 'book_offers') {
response.offers = [];
}
setImmediate(function() {
request.emit('success', response);
});
};
book.on('model', function(offers) {
assert.strictEqual(offers.length, 0);
done();
});
});
it('Unsubscribe', function(done) {
@@ -2332,10 +2445,10 @@ describe('OrderBook', function() {
command: 'book_offers',
id: undefined,
taker_gets: {
currency: '0000000000000000000000000000000000000000'
currency: 'XRP'
},
taker_pays: {
currency: '0000000000000000000000005553440000000000',
currency: 'USD',
issuer: addresses.ISSUER
},
taker: 'rrrrrrrrrrrrrrrrrrrrBZbvji',

View File

@@ -4,15 +4,14 @@
const assert = require('assert-diff');
const lodash = require('lodash');
const ripple = require('ripple-lib');
const Remote = require('ripple-lib').Remote;
const Server = require('ripple-lib').Server;
const Transaction = require('ripple-lib').Transaction;
const UInt160 = require('ripple-lib').UInt160;
const Currency = require('ripple-lib').Currency;
const Amount = require('ripple-lib').Amount;
const PathFind = require('ripple-lib')._test.PathFind;
const Log = require('ripple-lib')._test.Log;
const ACCOUNT_ONE = require('ripple-lib')._test.constants.ACCOUNT_ONE;
const RippleError = require('ripple-lib').RippleError;
let options;
let remote;
@@ -30,7 +29,7 @@ const TX_JSON = {
Flags: 0,
TransactionType: 'Payment',
Account: ADDRESS,
Destination: ripple.UInt160.ACCOUNT_ONE,
Destination: ACCOUNT_ONE,
Amount: {
value: '1',
currency: 'USD',
@@ -921,7 +920,7 @@ describe('Remote', function() {
value: 1
}), {
issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
currency: '0000000000000000000000005553440000000000'
currency: 'USD'
});
});
@@ -1492,7 +1491,7 @@ describe('Remote', function() {
it('Construct account_tx request', function() {
let request = remote.requestAccountTransactions({
account: UInt160.ACCOUNT_ONE,
account: ACCOUNT_ONE,
ledger_index_min: -1,
ledger_index_max: -1,
limit: 5,
@@ -1503,7 +1502,7 @@ describe('Remote', function() {
assert.deepEqual(request.message, {
command: 'account_tx',
id: undefined,
account: UInt160.ACCOUNT_ONE,
account: ACCOUNT_ONE,
ledger_index_min: -1,
ledger_index_max: -1,
binary: true,
@@ -1513,14 +1512,14 @@ describe('Remote', function() {
});
request = remote.requestAccountTransactions({
account: UInt160.ACCOUNT_ONE,
account: ACCOUNT_ONE,
min_ledger: -1,
max_ledger: -1
});
assert.deepEqual(request.message, {
command: 'account_tx',
id: undefined,
account: UInt160.ACCOUNT_ONE,
account: ACCOUNT_ONE,
binary: true,
ledger_index_min: -1,
ledger_index_max: -1
@@ -1528,7 +1527,7 @@ describe('Remote', function() {
});
it('Construct account_tx request -- no binary', function() {
const request = remote.requestAccountTransactions({
account: UInt160.ACCOUNT_ONE,
account: ACCOUNT_ONE,
ledger_index_min: -1,
ledger_index_max: -1,
limit: 5,
@@ -1540,7 +1539,7 @@ describe('Remote', function() {
assert.deepEqual(request.message, {
command: 'account_tx',
id: undefined,
account: UInt160.ACCOUNT_ONE,
account: ACCOUNT_ONE,
ledger_index_min: -1,
ledger_index_max: -1,
binary: false,
@@ -1612,13 +1611,13 @@ describe('Remote', function() {
command: 'book_offers',
id: undefined,
taker_gets: {
currency: Currency.from_human('USD').to_hex(),
currency: 'USD',
issuer: ADDRESS
},
taker_pays: {
currency: Currency.from_human('XRP').to_hex()
currency: 'XRP'
},
taker: UInt160.ACCOUNT_ONE
taker: ACCOUNT_ONE
});
});
@@ -1639,13 +1638,13 @@ describe('Remote', function() {
command: 'book_offers',
id: undefined,
taker_gets: {
currency: Currency.from_human('USD').to_hex(),
currency: 'USD',
issuer: ADDRESS
},
taker_pays: {
currency: Currency.from_human('XRP').to_hex()
currency: 'XRP'
},
taker: UInt160.ACCOUNT_ONE,
taker: ACCOUNT_ONE,
ledger_hash: LEDGER_HASH,
limit: 10
});
@@ -1902,30 +1901,107 @@ describe('Remote', function() {
},
source_currencies: [{
issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6',
currency: '0000000000000000000000004254430000000000'
currency: 'BTC'
}]
});
});
it('createPathFind', function() {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
remote._servers = servers;
const pathfind = remote.createPathFind({
src_account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
dst_account: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6',
dst_amount: '1/USD/rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
describe('createPathFind', function() {
const pathfindParams = {
src_account: 'r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b',
dst_account: 'rB31JZB8o5BvxyvPiRABatGsXwKYVaqGmN',
dst_amount: '0.001/USD/rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
src_currencies: [{
currency: 'BTC', issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6'
}]
};
const response1 = {
id: 1,
result: {
alternatives: [],
destination_account: 'rB31JZB8o5BvxyvPiRABatGsXwKYVaqGmN',
destination_amount: {
currency: 'USD',
issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
value: '0.001'
},
full_reply: false,
id: 1,
source_account: 'r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b'
},
status: 'success',
type: 'response'
};
const response2 = {
alternatives: [],
destination_account: 'rB31JZB8o5BvxyvPiRABatGsXwKYVaqGmN',
destination_amount: {
currency: 'USD',
issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
value: '0.001'
},
full_reply: true,
id: 1,
source_account: 'r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b',
type: 'path_find'
};
it('createPathFind', function(done) {
const servers = [
makeServer('wss://localhost:5006')
];
remote._servers = servers;
servers[0]._request = function(req) {
setTimeout(() => {
req.emit('success', response1, this);
setTimeout(() => {
remote._handleMessage(response2, this);
}, 5);
}, 5);
};
remote.createPathFind(pathfindParams, (err, result) => {
(function() {})(err);
assert.deepEqual(result, response2);
done();
});
});
// TODO: setup a mock server to provide a response
pathfind.on('update', message => console.log(message));
it('createPathFind - timeout', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
remote.submission_timeout = 50;
remote._servers = servers;
const pathfind = remote.createPathFind(pathfindParams);
pathfind.on('error', (error) => {
assert(error instanceof RippleError);
assert.strictEqual(error.result, 'tejTimeout');
done();
});
});
it('createPathFind - throw error without callback if already running', function() {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
remote._servers = servers;
const pathfind = remote.createPathFind(pathfindParams);
pathfind.on('error', function() { });
assert.throws(
function() {
remote.createPathFind(pathfindParams);
}, Error);
});
});
it('Construct path_find create request', function() {
@@ -1951,7 +2027,7 @@ describe('Remote', function() {
},
source_currencies: [{
issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6',
currency: '0000000000000000000000004254430000000000'
currency: 'BTC'
}]
});
});

View File

@@ -3,7 +3,6 @@ const assert = require('assert');
const Request = require('ripple-lib').Request;
const Remote = require('ripple-lib').Remote;
const Server = require('ripple-lib').Server;
const Currency = require('ripple-lib').Currency;
const RippleError = require('ripple-lib').RippleError;
function makeServer(url) {
@@ -49,6 +48,10 @@ describe('Request', function() {
},
on: function() {
},
once: function() {
},
removeListener: function() {
},
isConnected: function() {
return true;
}
@@ -97,13 +100,7 @@ describe('Request', function() {
});
});
it('Send request -- filterRequest', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
let requests = 0;
describe('Broadcast', function() {
const successResponse = {
account_data: {
@@ -122,76 +119,6 @@ describe('Request', function() {
ledger_current_index: 9022821,
validated: false
};
const errorResponse = {
error: 'remoteError',
error_message: 'Remote reported an error.',
remote: {
id: 3,
status: 'error',
type: 'response',
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
error: 'actNotFound',
error_code: 15,
error_message: 'Account not found.',
ledger_current_index: 9022856,
request: {
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
command: 'account_info',
id: 3
},
validated: false
}
};
function checkRequest(req) {
assert(req instanceof Request);
assert.strictEqual(typeof req.message, 'object');
assert.strictEqual(req.message.command, 'account_info');
}
servers[0]._request = function(req) {
++requests;
checkRequest(req);
req.emit('error', errorResponse);
};
servers[1]._request = function(req) {
++requests;
checkRequest(req);
setImmediate(function() {
req.emit('success', successResponse);
});
};
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
const request = new Request(remote, 'account_info');
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.filter(function(res) {
return res
&& typeof res === 'object'
&& !res.hasOwnProperty('error');
});
request.callback(function(err, res) {
assert.ifError(err);
assert.strictEqual(requests, 2, 'Failed to broadcast');
assert.deepEqual(res, successResponse);
done();
});
});
it('Send request -- filterRequest -- no success', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
let requests = 0;
const errorResponse = {
error: 'remoteError',
@@ -220,295 +147,283 @@ describe('Request', function() {
assert.strictEqual(req.message.command, 'account_info');
}
function sendError(req) {
++requests;
checkRequest(req);
req.emit('error', errorResponse);
}
servers[0]._request = sendError;
servers[1]._request = sendError;
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
const request = new Request(remote, 'account_info');
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.filter(function(res) {
function isSuccessResponse(res) {
return res
&& typeof res === 'object'
&& !res.hasOwnProperty('error');
});
request.callback(function(err) {
setImmediate(function() {
assert.strictEqual(requests, 2, 'Failed to broadcast');
assert.deepEqual(err, new RippleError(errorResponse));
done();
});
});
});
it('Send request -- filterRequest -- ledger prefilter', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
let requests = 0;
const successResponse = {
account_data: {
Account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
Balance: '13188802787',
Flags: 0,
LedgerEntryType: 'AccountRoot',
OwnerCount: 17,
PreviousTxnID:
'C6A2313CD9E34FFA3EB42F82B2B30F7FE12A045F1F4FDDAF006B25D7286536DD',
PreviousTxnLgrSeq: 8828020,
Sequence: 1406,
index:
'4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05'
},
ledger_current_index: 9022821,
validated: false
};
function checkRequest(req) {
assert(req instanceof Request);
assert.strictEqual(typeof req.message, 'object');
assert.strictEqual(req.message.command, 'account_info');
}
servers[0]._request = function() {
assert(false, 'Should not request; server does not have ledger');
};
it('Send request', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
servers[1]._request = function(req) {
++requests;
checkRequest(req);
setImmediate(function() {
req.emit('success', successResponse);
});
};
let requests = 0;
servers[0]._ledgerRanges.addRange(5, 6);
servers[1]._ledgerRanges.addRange(1, 4);
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
servers[0]._request = function(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('error', errorResponse, this);
}, 2);
};
const request = new Request(remote, 'account_info');
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.selectLedger(4);
servers[1]._request = function(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('success', successResponse, this);
}, 10);
};
request.filter(function(res) {
return res
&& typeof res === 'object'
&& !res.hasOwnProperty('error');
});
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
request.callback(function(err, res) {
assert.ifError(err);
assert.deepEqual(res, successResponse);
done();
});
});
const request = new Request(remote, 'account_info');
it('Send request -- filterRequest -- server reconnects', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
let requests = 0;
request.filter(isSuccessResponse);
const successResponse = {
account_data: {
Account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
Balance: '13188802787',
Flags: 0,
LedgerEntryType: 'AccountRoot',
OwnerCount: 17,
PreviousTxnID:
'C6A2313CD9E34FFA3EB42F82B2B30F7FE12A045F1F4FDDAF006B25D7286536DD',
PreviousTxnLgrSeq: 8828020,
Sequence: 1406,
index:
'4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05'
},
ledger_current_index: 9022821,
validated: false
};
const errorResponse = {
error: 'remoteError',
error_message: 'Remote reported an error.',
remote: {
id: 3,
status: 'error',
type: 'response',
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
error: 'actNotFound',
error_code: 15,
error_message: 'Account not found.',
ledger_current_index: 9022856,
request: {
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
command: 'account_info',
id: 3
},
validated: false
}
};
function checkRequest(req) {
assert(req instanceof Request);
assert.strictEqual(typeof req.message, 'object');
assert.strictEqual(req.message.command, 'account_info');
}
servers[0]._connected = false;
servers[0]._shouldConnect = true;
servers[0].removeAllListeners('connect');
servers[0]._request = function(req) {
++requests;
checkRequest(req);
req.emit('success', successResponse);
};
servers[1]._request = function(req) {
++requests;
checkRequest(req);
req.emit('error', errorResponse);
servers[0]._connected = true;
servers[0].emit('connect');
};
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
const request = new Request(remote, 'account_info');
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.filter(function(res) {
return res
&& typeof res === 'object'
&& !res.hasOwnProperty('error');
});
request.callback(function(err, res) {
assert.ifError(err);
setImmediate(function() {
request.callback(function(err, res) {
assert.ifError(err);
assert.strictEqual(requests, 2, 'Failed to broadcast');
assert.deepEqual(res, successResponse);
done();
});
});
});
it('Send request -- filterRequest -- server fails to reconnect',
function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
it('Send request -- timeout', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
let requests = 0;
let requests = 0;
const successResponse = {
account_data: {
Account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
Balance: '13188802787',
Flags: 0,
LedgerEntryType: 'AccountRoot',
OwnerCount: 17,
PreviousTxnID:
'C6A2313CD9E34FFA3EB42F82B2B30F7FE12A045F1F4FDDAF006B25D7286536DD',
PreviousTxnLgrSeq: 8828020,
Sequence: 1406,
index:
'4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05'
},
ledger_current_index: 9022821,
validated: false
};
const errorResponse = {
error: 'remoteError',
error_message: 'Remote reported an error.',
remote: {
id: 3,
status: 'error',
type: 'response',
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
error: 'actNotFound',
error_code: 15,
error_message: 'Account not found.',
ledger_current_index: 9022856,
request: {
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
command: 'account_info',
id: 3
},
validated: false
}
};
servers[0]._request = function(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('error', errorResponse, this);
}, 15);
};
function checkRequest(req) {
assert(req instanceof Request);
assert.strictEqual(typeof req.message, 'object');
assert.strictEqual(req.message.command, 'account_info');
}
servers[1]._request = function(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('success', successResponse, this);
}, 15);
};
servers[0]._connected = false;
servers[0]._shouldConnect = true;
servers[0].removeAllListeners('connect');
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
setTimeout(function() {
servers[0]._connected = true;
servers[0].emit('connect');
}, 20);
const request = new Request(remote, 'account_info');
servers[0]._request = function(req) {
++requests;
checkRequest(req);
req.emit('success', successResponse);
};
servers[1]._request = function(req) {
++requests;
checkRequest(req);
req.emit('error', errorResponse);
};
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.setTimeout(10);
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
request.filter(isSuccessResponse);
const request = new Request(remote, 'account_info');
request.setReconnectTimeout(10);
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.filter(function(res) {
return res
&& typeof res === 'object'
&& !res.hasOwnProperty('error');
request.callback(function(err, res) {
setTimeout(function() {
assert.strictEqual(requests, 2, 'Failed to broadcast');
assert.strictEqual(res, undefined);
assert(err instanceof RippleError);
assert.strictEqual(err.result, 'tejTimeout');
done();
}, 20);
});
});
request.callback(function(err) {
setTimeout(function() {
// Wait for the request that would emit 'success' to time out
assert.deepEqual(err, new RippleError(errorResponse));
assert.deepEqual(servers[0].listeners('connect'), []);
it('Send request -- no success', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
let requests = 0;
function sendError(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('error', errorResponse, this);
}, 2);
}
servers[0]._request = sendError;
servers[1]._request = sendError;
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
const request = new Request(remote, 'account_info');
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.filter(isSuccessResponse);
request.callback(function(err) {
setImmediate(function() {
assert.strictEqual(requests, 2, 'Failed to broadcast');
assert.deepEqual(err, new RippleError(errorResponse));
done();
});
});
});
it('Send request -- ledger prefilter', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
let requests = 0;
servers[0]._request = function() {
assert(false, 'Should not request; server does not have ledger');
};
servers[1]._request = function(req) {
++requests;
checkRequest(req);
setImmediate(() => {
req.emit('success', successResponse, this);
});
};
servers[0]._ledgerRanges.addRange(5, 6);
servers[1]._ledgerRanges.addRange(1, 4);
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
const request = new Request(remote, 'account_info');
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.selectLedger(4);
request.filter(isSuccessResponse);
request.callback(function(err, res) {
assert.ifError(err);
assert.deepEqual(res, successResponse);
done();
});
});
it('Send request -- server reconnects', function(done) {
const servers = [
makeServer('wss://localhost:5006'),
makeServer('wss://localhost:5007')
];
let requests = 0;
servers[0]._connected = false;
servers[0]._shouldConnect = true;
servers[0].removeAllListeners('connect');
servers[0]._request = function(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('success', successResponse, this);
}, 4);
};
servers[1]._request = function(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('error', errorResponse, this);
setTimeout(function() {
servers[0]._connected = true;
servers[0].emit('connect');
}, 4);
}, 2);
};
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
const request = new Request(remote, 'account_info');
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.filter(isSuccessResponse);
request.callback(function(err, res) {
assert.ifError(err);
setImmediate(function() {
assert.strictEqual(requests, 2, 'Failed to broadcast');
assert.deepEqual(res, successResponse);
done();
});
});
});
it('Send request -- server fails to reconnect',
function(done) {
const servers = [
makeServer('wss://localhost:5008'),
makeServer('wss://localhost:5009')
];
let requests = 0;
servers[0]._connected = false;
servers[0]._shouldConnect = true;
servers[0].removeAllListeners('connect');
setTimeout(function() {
servers[0]._connected = true;
servers[0].emit('connect');
}, 20);
servers[0]._request = function(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('success', successResponse, this);
}, 1);
};
servers[1]._request = function(req) {
++requests;
checkRequest(req);
setTimeout(() => {
req.emit('error', errorResponse, this);
}, 2);
};
const remote = new Remote();
remote._connected = true;
remote._servers = servers;
const request = new Request(remote, 'account_info');
request.setReconnectTimeout(10);
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
request.filter(isSuccessResponse);
request.callback(function(err) {
setTimeout(function() {
// Wait for the request that would emit 'success' to time out
assert.deepEqual(err, new RippleError(errorResponse));
assert.deepEqual(servers[0].listeners('connect'), []);
done();
}, 20);
});
});
});
@@ -1024,11 +939,11 @@ describe('Request', function() {
assert.deepEqual(request.message.books, [
{
'taker_gets': {
'currency': Currency.from_json('EUR').to_hex(),
'currency': 'EUR',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'taker_pays': {
'currency': Currency.from_json('USD').to_hex(),
'currency': 'USD',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'snapshot': true
@@ -1056,11 +971,11 @@ describe('Request', function() {
assert.deepEqual(request.message.books, [
{
'taker_gets': {
'currency': Currency.from_json('CNY').to_hex(),
'currency': 'CNY',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'taker_pays': {
'currency': Currency.from_json('USD').to_hex(),
'currency': 'USD',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'snapshot': true
@@ -1085,11 +1000,11 @@ describe('Request', function() {
assert.deepEqual(request.message.books, [
{
'taker_gets': {
'currency': '0000000000000000000000004555520000000000', // EUR hex
'currency': 'EUR',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'taker_pays': {
'currency': '0000000000000000000000005553440000000000', // USD hex
'currency': 'USD',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'snapshot': true
@@ -1143,11 +1058,11 @@ describe('Request', function() {
assert.deepEqual(request.message.books, [{
'taker_gets': {
'currency': Currency.from_json('EUR').to_hex(),
'currency': 'EUR',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'taker_pays': {
'currency': Currency.from_json('USD').to_hex(),
'currency': 'USD',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'both': true,
@@ -1179,11 +1094,11 @@ describe('Request', function() {
assert.deepEqual(request.message.books, [{
'taker_gets': {
'currency': Currency.from_json('EUR').to_hex(),
'currency': 'EUR',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'taker_pays': {
'currency': Currency.from_json('USD').to_hex(),
'currency': 'USD',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'both': true
@@ -1234,11 +1149,11 @@ describe('Request', function() {
'books': [
{
'taker_gets': {
'currency': '0000000000000000000000004555520000000000',
'currency': 'EUR',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'taker_pays': {
'currency': '0000000000000000000000005553440000000000',
'currency': 'USD',
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
},
'snapshot': true

View File

@@ -1,352 +0,0 @@
'use strict';
/* eslint-disable max-len*/
const assert = require('assert');
const lodash = require('lodash');
const SerializedObject = require('ripple-lib').SerializedObject;
const Amount = require('ripple-lib').Amount;
const sjclcodec = require('sjcl-codec');
// Shortcuts
const hex = sjclcodec.hex;
const utf8 = sjclcodec.utf8String;
describe('Serialized object', function() {
function convertStringToHex(string) {
return hex.fromBits(utf8.toBits(string)).toUpperCase();
}
describe('#from_json(v).to_json() == v', function() {
it('outputs same as passed to from_json', function() {
const input_json = {
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Amount: '274579388',
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Fee: '15',
Flags: 0,
Paths: [[
{
account: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
currency: 'USD',
issuer: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
type: 49,
type_hex: '0000000000000031'
},
{
currency: 'XRP',
type: 16,
type_hex: '0000000000000010'
}
]],
SendMax: {
currency: 'USD',
issuer: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
value: '2.74579388'
},
Sequence: 351,
SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A',
TransactionType: 'Payment',
TxnSignature: '30450221009DA3A42DD25E3B22EC45AD8BA8FC7A954264264A816D300B2DF69F814D7D4DD2022072C9627F97EEC6DA13DE841E06E2CD985EF06A0FBB15DDBF0800D0730C8986BF'
};
const output_json = SerializedObject.from_json(input_json).to_json();
assert.deepEqual(input_json, output_json);
});
});
describe('#from_json(v).to_json() == v -- invalid amount', function() {
it('outputs same as passed to from_json', function() {
const input_json = {
Account: 'rUR9gTCcrUY9fMkz9rwcM9urPREh3LKXoW',
Fee: '10',
Flags: 0,
Sequence: 65,
SigningPubKey: '033D0B1FB932E0408C119107483190B61561DCE8529E29CB5D1C69128DA54DA715',
TakerGets: '2188313981504612096',
TakerPays: {
currency: 'USD',
issuer: 'r9rp9MUFRJVCVLRm3MTmUvSPNBSL3BuEFx',
value: '99999999999'
},
TransactionType: 'OfferCreate',
TxnSignature: '304602210085C6AE945643150E6D450CF796E45D74FB24B4E03E964A29CC6AFFEB346C77C80221009BE1B6678CF6C2E61F8F2696144C75AFAF66DF4FC0733DF9118EDEFEEFE33243'
};
assert.throws(function() {
SerializedObject.from_json(input_json).to_json();
});
});
});
describe('#from_json(v).to_json() == v -- invalid amount, strict_mode = false', function() {
it('outputs same as passed to from_json', function() {
const input_json = {
Account: 'rUR9gTCcrUY9fMkz9rwcM9urPREh3LKXoW',
Fee: '10',
Flags: 0,
Sequence: 65,
SigningPubKey: '033D0B1FB932E0408C119107483190B61561DCE8529E29CB5D1C69128DA54DA715',
TakerGets: '2188313981504612096',
TakerPays: {
currency: 'USD',
issuer: 'r9rp9MUFRJVCVLRm3MTmUvSPNBSL3BuEFx',
value: '99999999999'
},
TransactionType: 'OfferCreate',
TxnSignature: 'FFFFFF210085C6AE945643150E6D450CF796E45D74FB24B4E03E964A29CC6AFFEB346C77C80221009BE1B6678CF6C2E61F8F2696144C75AFAF66DF4FC0733DF9118EDEFEEFE33243'
};
const strictMode = Amount.strict_mode;
Amount.strict_mode = false;
const output_json = SerializedObject.from_json(input_json).to_json();
assert.deepEqual(input_json, output_json);
Amount.strict_mode = strictMode;
});
});
describe('#from_json', function() {
it('understands TransactionType as a Number', function() {
const input_json = {
// no non required fields
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Amount: '274579388',
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Fee: '15',
Sequence: 351,
SigningPubKey: '02',// VL field ;)
TransactionType: 0 //
};
const output_json = SerializedObject.from_json(input_json).to_json();
assert.equal(0, input_json.TransactionType);
assert.equal('Payment', output_json.TransactionType);
});
it('understands LedgerEntryType as a Number', function() {
const input_json = {
// no, non required fields
LedgerEntryType: 100,
Flags: 0,
Indexes: [],
RootIndex: '000360186E008422E06B72D5B275E29EE3BE9D87A370F424E0E7BF613C465909'
};
const output_json = SerializedObject.from_json(input_json).to_json();
assert.equal(100, input_json.LedgerEntryType);
assert.equal('DirectoryNode', output_json.LedgerEntryType);
});
it('checks for missing required fields', function() {
const input_json = {
TransactionType: 'Payment',
// no non required fields
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Amount: '274579388',
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Fee: '15',
Sequence: 351,
SigningPubKey: '02'
};
Object.keys(input_json).slice(1).forEach(function(k) {
const bad_json = lodash.merge({}, input_json);
delete bad_json[k];
assert.strictEqual(bad_json[k], undefined);
assert.throws(function() {
SerializedObject.from_json(bad_json);
}, new RegExp('Payment is missing fields: \\["' + k + '"\\]'));
});
});
it('checks for unknown fields', function() {
const input_json = {
TransactionType: 'Payment',
// no non required fields
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Amount: '274579388',
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Fee: '15',
Sequence: 351,
SigningPubKey: '02'
};
Object.keys(input_json).slice(1).forEach(function(k) {
const bad_json = lodash.merge({}, input_json);
bad_json[k + 'z'] = bad_json[k];
assert.throws(function() {
SerializedObject.from_json(bad_json);
}, new RegExp('Payment has unknown fields: \\["' + k + 'z"\\]'));
});
});
describe('Format validation', function() {
// Peercover actually had a problem submitting transactions without a `Fee`
// and rippled was only informing of 'transaction is invalid'
it('should throw an Error when there is a missing field', function() {
const input_json = {
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Amount: '274579388',
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
Sequence: 351,
SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A',
TransactionType: 'Payment',
TxnSignature: '30450221009DA3A42DD25E3B22EC45AD8BA8FC7A954264264A816D300B2DF69F814D7D4DD2022072C9627F97EEC6DA13DE841E06E2CD985EF06A0FBB15DDBF0800D0730C8986BF'
};
assert.throws(
function() {
SerializedObject.from_json(input_json);
},
/Payment is missing fields: \["Fee"\]/
);
});
});
describe('Memos', function() {
let input_json;
beforeEach(function() {
input_json = {
'Flags': 2147483648,
'TransactionType': 'Payment',
'Account': 'rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrF',
'Amount': '1',
'Destination': 'radqi6ppXFxVhJdjzaATRBxdrPcVTf1Ung',
'Sequence': 281,
'SigningPubKey': '03D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C51',
'Fee': '12000',
'LastLedgerSequence': 10074214,
'TxnSignature': '304402201180636F2CE215CE97A29CD302618FAE60D63EBFC8903DE17A356E857A449C430220290F4A54F9DE4AC79034C8BEA5F1F8757F7505F1A6FF04D2E19B6D62E867256B'
};
});
it('should serialize and parse - full memo, all strings text/plain ', function() {
input_json.Memos = [
{
Memo: {
MemoType: '74657374',
MemoFormat: '74657874',
MemoData: '736F6D652064617461'
}
}
];
const so = SerializedObject.from_json(input_json).to_json();
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'text';
input_json.Memos[0].Memo.parsed_memo_data = 'some data';
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('text');
input_json.Memos[0].Memo.MemoData = convertStringToHex('some data');
assert.deepEqual(so, input_json);
});
it('should serialize and parse - full memo, all strings, invalid MemoFormat', function() {
input_json.Memos = [
{
'Memo':
{
MemoType: '74657374',
MemoFormat: '6170706C69636174696F6E2F6A736F6E',
MemoData: '736F6D652064617461'
}
}
];
const so = SerializedObject.from_json(input_json).to_json();
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'application/json';
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('application/json');
input_json.Memos[0].Memo.MemoData = convertStringToHex('some data');
assert.deepEqual(so, input_json);
assert.strictEqual(input_json.Memos[0].Memo.parsed_memo_data, undefined);
});
it('should serialize and parse - full memo, json data, valid MemoFormat, ignored field', function() {
input_json.Memos = [
{
Memo: {
MemoType: '74657374',
MemoFormat: '6A736F6E',
ignored: 'ignored',
MemoData: '7B22737472696E67223A22736F6D655F737472696E67222C22626F6F6C65616E223A747275657D'
}
}
];
const so = SerializedObject.from_json(input_json).to_json();
delete input_json.Memos[0].Memo.ignored;
input_json.Memos[0].Memo.parsed_memo_type = 'test';
input_json.Memos[0].Memo.parsed_memo_format = 'json';
input_json.Memos[0].Memo.parsed_memo_data = {
'string': 'some_string',
'boolean': true
};
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('json');
input_json.Memos[0].Memo.MemoData = convertStringToHex(JSON.stringify(
{
'string': 'some_string',
'boolean': true
}
));
assert.deepEqual(so, input_json);
});
it('should throw an error - invalid Memo field', function() {
input_json.Memos = [
{
Memo: {
MemoType: '74657374',
MemoField: '6A736F6E',
MemoData: '7B22737472696E67223A22736F6D655F737472696E67222C22626F6F6C65616E223A747275657D'
}
}
];
assert.throws(function() {
SerializedObject.from_json(input_json);
}, /^Error: JSON contains unknown field: "MemoField" \(Memo\) \(Memos\)/);
});
it('should serialize json with memo - match hex output', function() {
input_json = {
Flags: 2147483648,
TransactionType: 'Payment',
Account: 'rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrF',
Amount: '1',
Destination: 'radqi6ppXFxVhJdjzaATRBxdrPcVTf1Ung',
Memos: [
{
Memo: {
MemoType: '696D616765'
}
}
],
Sequence: 294,
SigningPubKey: '03D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C51',
Fee: '12000',
LastLedgerSequence: 10404607,
TxnSignature: '304402206B53EDFA6EFCF6FE5BA76C81BABB60A3B55E9DE8A1462DEDC5F387879575E498022015AE7B59AA49E735D7F2E252802C4406CD00689BCE5057C477FE979D38D2DAC9'
};
const serializedHex = '12000022800000002400000126201B009EC2FF614000000000000001684000000000002EE0732103D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C517446304402206B53EDFA6EFCF6FE5BA76C81BABB60A3B55E9DE8A1462DEDC5F387879575E498022015AE7B59AA49E735D7F2E252802C4406CD00689BCE5057C477FE979D38D2DAC9811426C4CFB3BD05A9AA23936F2E81634C66A9820C9483143DD06317D19C6110CAFF150AE528F58843BE2CA1F9EA7C05696D616765E1F1';
assert.strictEqual(SerializedObject.from_json(input_json).to_hex(), serializedHex);
});
});
});
});
// vim:sw=2:sts=2:ts=8:et

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,8 @@ const ws = require('ws');
const lodash = require('lodash');
const assert = require('assert-diff');
const Remote = require('ripple-lib').Remote;
const SerializedObject = require('ripple-lib').SerializedObject;
const binary = require('ripple-binary-codec');
const utils = require('ripple-lib').utils;
const Transaction = require('ripple-lib').Transaction;
const TransactionManager = require('ripple-lib')._test.TransactionManager;
@@ -335,11 +336,12 @@ describe('TransactionManager', function() {
const binaryTx = lodash.extend({}, ACCOUNT_TX_TRANSACTION, {
ledger_index: ACCOUNT_TX_TRANSACTION.tx.ledger_index,
tx_blob: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.tx).to_hex(),
meta: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.meta).to_hex()
tx_blob: binary.encode(ACCOUNT_TX_TRANSACTION.tx),
meta: binary.encode(ACCOUNT_TX_TRANSACTION.meta)
});
const hash = new SerializedObject(binaryTx.tx_blob).hash(0x54584E00).to_hex();
const prefix = (0x54584E00).toString(16);
const hash = utils.sha512half(new Buffer(prefix + binaryTx.tx_blob, 'hex'));
transaction.addId(hash);
@@ -364,8 +366,8 @@ describe('TransactionManager', function() {
const binaryTx = lodash.extend({}, ACCOUNT_TX_TRANSACTION, {
ledger_index: ACCOUNT_TX_TRANSACTION.tx.ledger_index,
tx_blob: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.tx).to_hex(),
meta: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.meta).to_hex()
tx_blob: binary.encode(ACCOUNT_TX_TRANSACTION.tx),
meta: binary.encode(ACCOUNT_TX_TRANSACTION.meta)
});
transactionManager._request = function() {
@@ -417,9 +419,8 @@ describe('TransactionManager', function() {
});
rippled.once('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
assert.strictEqual(transactionManager.getPending().length(), 1);
req.sendResponse(SUBMIT_RESPONSE, {id: m.id});
@@ -457,9 +458,8 @@ describe('TransactionManager', function() {
});
rippled.once('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
assert.strictEqual(transactionManager.getPending().length(), 1);
req.sendResponse(SUBMIT_TEC_RESPONSE, {id: m.id});
@@ -500,7 +500,7 @@ describe('TransactionManager', function() {
});
rippled.on('request_submit', function(m, req) {
const deserialized = new SerializedObject(m.tx_blob).to_json();
const deserialized = binary.decode(m.tx_blob);
switch (deserialized.TransactionType) {
case 'Payment':
@@ -570,8 +570,7 @@ describe('TransactionManager', function() {
});
rippled.on('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
assert.strictEqual(transactionManager.getPending().length(), 1);
req.sendResponse(SUBMIT_TEF_RESPONSE, {id: m.id});
});
@@ -630,9 +629,8 @@ describe('TransactionManager', function() {
});
rippled.on('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
assert.strictEqual(transactionManager.getPending().length(), 1);
req.sendResponse(SUBMIT_TEL_RESPONSE, {id: m.id});
@@ -737,9 +735,8 @@ describe('TransactionManager', function() {
});
rippled.on('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
assert.strictEqual(transactionManager.getPending().length(), 1);
@@ -797,9 +794,8 @@ describe('TransactionManager', function() {
});
rippled.on('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
assert.strictEqual(transactionManager.getPending().length(), 1);
req.sendResponse(SUBMIT_TEL_RESPONSE, {id: m.id});
@@ -867,9 +863,8 @@ describe('TransactionManager', function() {
/* eslint-disable no-unused-vars */
rippled.on('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
assert.strictEqual(transactionManager.getPending().length(), 1);

View File

@@ -1,11 +1,12 @@
var assert = require('assert');
var Transaction = require('ripple-lib').Transaction;
var TransactionQueue = require('ripple-lib').TransactionQueue;
'use strict';
const assert = require('assert');
const Transaction = require('ripple-lib').Transaction;
const TransactionQueue = require('ripple-lib')._test.TransactionQueue;
describe('Transaction queue', function() {
it('Push transaction', function() {
var queue = new TransactionQueue();
var tx = new Transaction();
const queue = new TransactionQueue();
const tx = new Transaction();
queue.push(tx);
@@ -13,8 +14,8 @@ describe('Transaction queue', function() {
});
it('Remove transaction', function() {
var queue = new TransactionQueue();
var tx = new Transaction();
const queue = new TransactionQueue();
const tx = new Transaction();
queue.push(tx);
queue.remove(tx);
@@ -23,8 +24,8 @@ describe('Transaction queue', function() {
});
it('Remove transaction by ID', function() {
var queue = new TransactionQueue();
var tx = new Transaction();
const queue = new TransactionQueue();
const tx = new Transaction();
queue.push(tx);
@@ -33,32 +34,38 @@ describe('Transaction queue', function() {
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'
];
queue.remove('3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
queue.remove(
'3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
assert.strictEqual(queue.length(), 1);
queue.remove('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
queue.remove(
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
assert.strictEqual(queue.length(), 0);
});
it('Add sequence', function() {
var queue = new TransactionQueue();
const queue = new TransactionQueue();
queue.addReceivedSequence(1);
assert(queue.hasSequence(1));
});
it('Add ID', function() {
var queue = new TransactionQueue();
var tx = new Transaction();
queue.addReceivedId('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B', tx);
assert.strictEqual(queue.getReceived('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), void(0));
assert.strictEqual(queue.getReceived('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
const queue = new TransactionQueue();
const tx = new Transaction();
queue.addReceivedId(
'1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B', tx);
assert.strictEqual(queue.getReceived(
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'),
undefined);
assert.strictEqual(queue.getReceived(
'1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
});
it('Get submission', function() {
var queue = new TransactionQueue();
var tx = new Transaction();
const queue = new TransactionQueue();
const tx = new Transaction();
queue.push(tx);
@@ -67,16 +74,20 @@ describe('Transaction queue', function() {
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'
];
assert.strictEqual(queue.getSubmission('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
assert.strictEqual(queue.getSubmission('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
assert.strictEqual(queue.getSubmission('3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), void(0));
assert.strictEqual(queue.getSubmission(
'1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
assert.strictEqual(queue.getSubmission(
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
assert.strictEqual(queue.getSubmission(
'3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'),
undefined);
});
it('Iterate over queue', function() {
var queue = new TransactionQueue();
var count = 10;
const queue = new TransactionQueue();
let count = 10;
for (var i=0; i<count; i++) {
for (let i = 0; i < count; i++) {
queue.push(new Transaction());
}

View File

@@ -5,11 +5,12 @@
const assert = require('assert-diff');
const lodash = require('lodash');
const addresses = require('./fixtures/addresses');
const ripple = require('ripple-lib');
const Transaction = require('ripple-lib').Transaction;
const TransactionQueue = require('ripple-lib').TransactionQueue;
const TransactionQueue = require('ripple-lib')._test.TransactionQueue;
const Remote = require('ripple-lib').Remote;
const Server = require('ripple-lib').Server;
const {decodeAddress} = require('ripple-address-codec');
const binary = require('ripple-binary-codec');
const transactionResult = {
engine_result: 'tesSUCCESS',
@@ -344,7 +345,7 @@ describe('Transaction', function() {
remote.setSecret(src, addresses.SECRET);
assert(transaction.complete());
const json = transaction.serialize().to_json();
const json = transaction.serialize();
assert.notStrictEqual(json.Fee, '66500000', 'Fee == 66500000, i.e. 66.5 XRP!');
});
@@ -583,7 +584,7 @@ describe('Transaction', function() {
SigningPubKey: '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED',
Account: 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ',
Flags: 0,
Fee: 10,
Fee: '10',
Sequence: 1,
TransactionType: 'AccountSet'
};
@@ -601,10 +602,9 @@ describe('Transaction', function() {
const tx = Transaction.from_json(tx_json);
const data = tx.signingData();
assert.strictEqual(data.hash().to_json(),
expectedSigningHash);
assert.strictEqual(tx.signingHash(), expectedSigningHash);
assert.strictEqual(data.to_hex(),
assert.strictEqual(data,
('535458001200032200000000240000000168400000000000000' +
'A7321021FED5FD081CE5C4356431267D04C6E2167E4112C897D' +
'5E10335D4E22B4DA49ED8114E0E6E281CA324AEE034B2BB8AC9' +
@@ -618,7 +618,7 @@ describe('Transaction', function() {
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
transaction.tx_json.Flags = 0;
transaction.tx_json.Fee = 10;
transaction.tx_json.Fee = '10';
transaction.tx_json.Sequence = 1;
transaction.tx_json.TransactionType = 'AccountSet';
@@ -633,29 +633,12 @@ describe('Transaction', function() {
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
transaction.tx_json.Flags = 0;
transaction.tx_json.Fee = 10;
transaction.tx_json.Fee = '10';
transaction.tx_json.Sequence = 1;
transaction.tx_json.TransactionType = 'AccountSet';
assert.strictEqual(transaction.hash('HASH_TX_SIGN'), 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
assert.strictEqual(transaction.hash('HASH_TX_SIGN_TESTNET'), '9FE7D27FC5B9891076B66591F99A683E01E0912986A629235459A3BD1961F341');
done();
});
it('Get hash - invalid prefix', function(done) {
const transaction = new Transaction();
transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij';
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
transaction.tx_json.Flags = 0;
transaction.tx_json.Fee = 10;
transaction.tx_json.Sequence = 1;
transaction.tx_json.TransactionType = 'AccountSet';
assert.throws(function() {
transaction.hash('HASH_TX_SIGNZ');
});
assert.strictEqual(transaction.signingHash(),
'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
done();
});
@@ -839,7 +822,7 @@ describe('Transaction', function() {
const transaction = Transaction.from_json(input_json);
assert.deepEqual(transaction.serialize().to_hex(), expected_hex);
assert.deepEqual(transaction.serialize(), expected_hex);
});
it('Sign transaction', function(done) {
@@ -848,7 +831,7 @@ describe('Transaction', function() {
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
transaction.tx_json.Flags = 0;
transaction.tx_json.Fee = 10;
transaction.tx_json.Fee = '10';
transaction.tx_json.Sequence = 1;
transaction.tx_json.TransactionType = 'AccountSet';
@@ -2255,12 +2238,15 @@ describe('Transaction', function() {
const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK';
const d1 = transaction.multiSigningData(a1);
const tbytes = ripple.SerializedObject.from_json(
lodash.merge(transaction.tx_json, {SigningPubKey: ''})).buffer;
const abytes = ripple.UInt160.from_json(a1).to_bytes();
const prefix = require('ripple-lib')._test.HashPrefixes.HASH_TX_MULTISIGN_BYTES;
const txHex = binary.encode(
lodash.merge(transaction.tx_json, {SigningPubKey: ''}));
const abytes = decodeAddress(a1);
const prefix = 0x534D5400;
const prefixHex = prefix.toString(16);
assert.deepEqual(d1.buffer, prefix.concat(tbytes, abytes));
assert.deepEqual(new Buffer(d1, 'hex'),
Buffer.concat([new Buffer(prefixHex, 'hex'), new Buffer(txHex, 'hex'),
new Buffer(abytes)]));
});
it('Multisign', function() {
@@ -2301,6 +2287,10 @@ describe('Transaction', function() {
{Signer: s2},
{Signer: s1}
]);
transaction.remote = new Remote();
assert(transaction.complete());
assert.strictEqual(transaction.tx_json.SigningPubKey, '');
});
it('Multisign -- missing LastLedgerSequence', function() {
@@ -2316,4 +2306,27 @@ describe('Transaction', function() {
transaction.getMultiSigningJson();
});
});
it('Multisign -- LastLedgerSequence autofill', function() {
const transaction = Transaction.from_json({
Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
Sequence: 1,
Fee: '100',
TransactionType: 'AccountSet',
Flags: 0
});
const sequence = 1;
transaction.remote = {
getLedgerSequenceSync: () => {
return sequence;
}
};
const mJson = transaction.getMultiSigningJson();
assert.strictEqual(mJson.LastLedgerSequence,
sequence + 1 + transaction._lastLedgerOffset);
assert.strictEqual(transaction.tx_json.LastLedgerSequence,
sequence + 1 + transaction._lastLedgerOffset);
});
});

View File

@@ -1,104 +0,0 @@
'use strict';
/* eslint-disable max-len */
const _ = require('lodash');
const assert = require('assert-diff');
const lodash = require('lodash');
const ripple = require('ripple-lib');
const fixtures = require('./fixtures/uint');
function resultError(test, result) {
function type(e) {
return Object.prototype.toString.call(e);
}
return `Expected ${type(test.input)}: ${test.input} to yield ${type(test.expected)}: ${test.expected === 'null' ? NaN : test.expected}. Actual: ${type(result)}: ${result}`;
}
function makeTests(uIntType) {
describe(uIntType, function() {
const rippleType = ripple[uIntType];
const tests = fixtures[uIntType];
it('from_json().to_json()', function() {
tests['from_json().to_json()'].forEach(function(test) {
let result = rippleType.from_json(test.input);
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null', `Validity check failed: ${test.input}`);
result = result.to_json();
if (test.expected === 'null') {
// XXX
// UInt160.to_json() returns NaN rather than null if input is invalid
assert.strictEqual(lodash.isNaN(result), true, resultError(test, result));
} else {
assert.strictEqual(result, test.expected, resultError(test, result));
}
});
});
it('from_json().to_bytes()', function() {
tests['from_json().to_bytes()'].forEach(function(test) {
const result = rippleType.from_json(test.input);
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null', `Validity check failed: ${test.input}`);
assert.deepEqual(result.to_bytes(), test.expected, resultError(test, result));
});
});
it('from_number().to_json()', function() {
tests['from_number().to_json()'].forEach(function(test) {
let result = rippleType.from_number(test.input);
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null', `Validity check failed: ${test.input}`);
result = result.to_json();
if (test.expected === 'null') {
// XXX
// UInt160.to_json() returns NaN rather than null if input is invalid
assert.strictEqual(lodash.isNaN(result), true, resultError(test, result));
} else {
assert.strictEqual(result, test.expected, resultError(test, result));
}
});
});
it('from_number().to_hex()', function() {
tests['from_number().to_hex()'].forEach(function(test) {
const result = rippleType.from_number(test.input);
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null', `Validity check failed: ${test.input}`);
assert.strictEqual(result.to_hex(), test.expected, resultError(test, result));
});
});
it('from_generic().to_*()', function() {
tests['from_generic().to_*()'].forEach(function(test) {
let result = rippleType.from_generic(test.input);
switch (test.input) {
// XXX
// from_generic() accepts these as "zero"
case 0:
case '0':
case undefined:
switch (test.outputMethod) {
case 'to_bytes':
test.expected = _.fill(Array(rippleType.width), 0);
break;
case 'to_json':
case 'to_hex':
test.expected = _.fill(Array(rippleType.width * 2), 0).join('');
break;
}
}
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null',
`Validity check failed: ${test.input} > ${test.expected}`);
result = result[test.outputMethod]();
if (test.expected === 'null') {
// XXX
// UInt160.to_json() returns NaN rather than null if input is invalid
assert.strictEqual(lodash.isNaN(result), true, resultError(test, result));
} else {
assert.deepEqual(result, test.expected, resultError(test, result));
}
});
});
});
}
['UInt128', 'UInt160', 'UInt256'].forEach(makeTests);