From 97747deed932e05781b43139d06d321cd4a92e02 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Tue, 27 Oct 2015 12:03:12 -0700 Subject: [PATCH] Delete core, move "api" directory up to "src", and remove unused dependencies --- .flowconfig | 8 +- npm-shrinkwrap.json | 38 +- package.json | 6 - scripts/ci.sh | 1 - src/api/index.js | 111 - src/{api => }/common/connection.js | 0 src/{api => }/common/constants.js | 0 src/{api => }/common/errors.js | 0 src/{api => }/common/index.js | 0 src/{api => }/common/rangeset.js | 0 src/{api => }/common/schema-validator.js | 0 src/{api => }/common/schemas/address.json | 0 src/{api => }/common/schemas/adjustment.json | 0 src/{api => }/common/schemas/amount.json | 0 src/{api => }/common/schemas/amountbase.json | 0 src/{api => }/common/schemas/api-options.json | 0 .../common/schemas/balance-sheet-options.json | 0 src/{api => }/common/schemas/balance.json | 0 src/{api => }/common/schemas/blob.json | 0 src/{api => }/common/schemas/currency.json | 0 .../common/schemas/destinationAdjustment.json | 0 .../common/schemas/get-account-info.json | 0 .../common/schemas/get-balance-sheet.json | 0 .../common/schemas/get-balances.json | 0 src/{api => }/common/schemas/get-ledger.json | 0 .../common/schemas/get-orderbook.json | 0 src/{api => }/common/schemas/get-orders.json | 0 src/{api => }/common/schemas/get-paths.json | 0 .../common/schemas/get-server-info.json | 0 .../common/schemas/get-settings.json | 0 .../common/schemas/get-transaction.json | 0 .../common/schemas/get-transactions.json | 0 .../common/schemas/get-trustlines.json | 0 src/{api => }/common/schemas/hash128.json | 0 src/{api => }/common/schemas/hash256.json | 0 .../common/schemas/instructions.json | 0 src/{api => }/common/schemas/issue.json | 0 src/{api => }/common/schemas/lax-amount.json | 0 .../common/schemas/lax-lax-amount.json | 0 .../common/schemas/ledger-closed.json | 0 .../common/schemas/ledger-options.json | 0 .../common/schemas/ledgerversion.json | 0 .../common/schemas/max-adjustment.json | 0 src/{api => }/common/schemas/memo.json | 0 .../common/schemas/min-adjustment.json | 0 .../order-cancellation-transaction.json | 0 .../common/schemas/order-cancellation.json | 0 .../common/schemas/order-change.json | 0 .../common/schemas/order-transaction.json | 0 src/{api => }/common/schemas/order.json | 0 .../common/schemas/orderbook-orders.json | 0 src/{api => }/common/schemas/orderbook.json | 0 .../common/schemas/orders-options.json | 0 src/{api => }/common/schemas/outcome.json | 0 src/{api => }/common/schemas/pathfind.json | 0 .../common/schemas/payment-transaction.json | 0 src/{api => }/common/schemas/payment.json | 0 src/{api => }/common/schemas/prepare.json | 0 src/{api => }/common/schemas/quality.json | 0 src/{api => }/common/schemas/sequence.json | 0 .../common/schemas/settings-options.json | 0 .../common/schemas/settings-transaction.json | 0 src/{api => }/common/schemas/settings.json | 0 src/{api => }/common/schemas/sign.json | 0 .../common/schemas/signed-value.json | 0 .../common/schemas/sourceAdjustment.json | 0 src/{api => }/common/schemas/submit.json | 0 .../suspended-payment-cancellation.json | 0 .../schemas/suspended-payment-creation.json | 0 .../schemas/suspended-payment-execution.json | 0 src/{api => }/common/schemas/tag.json | 0 src/{api => }/common/schemas/timestamp.json | 0 .../common/schemas/transaction-options.json | 0 .../common/schemas/transactions-options.json | 0 .../common/schemas/trustline-transaction.json | 0 src/{api => }/common/schemas/trustline.json | 0 .../common/schemas/trustlines-options.json | 0 src/{api => }/common/schemas/tx.json | 0 src/{api => }/common/schemas/uint32.json | 0 src/{api => }/common/schemas/value.json | 0 src/{api => }/common/serverinfo.js | 0 src/{api => }/common/txflags.js | 0 src/{api => }/common/types.js | 0 src/{api => }/common/utils.js | 0 src/{api => }/common/validate.js | 0 src/core/account.js | 388 --- src/core/amount.js | 937 ------- src/core/autobridgecalculator.js | 496 ---- src/core/binformat.js | 485 ---- src/core/constants.js | 8 - src/core/currency.js | 58 - src/core/index.js | 21 - src/core/log.js | 193 -- src/core/meta.js | 262 -- src/core/orderbook.js | 1411 ---------- src/core/orderbookutils.js | 153 - src/core/pathfind.js | 93 - src/core/rangeset.js | 61 - src/core/remote.js | 2473 ---------------- src/core/request.js | 633 ----- src/core/rippleerror.js | 71 - src/core/server.js | 992 ------- src/core/transaction.js | 1653 ----------- src/core/transactionmanager.js | 757 ----- src/core/transactionqueue.js | 164 -- src/core/utils.js | 184 -- src/index.js | 113 +- src/{api => }/ledger/accountinfo.js | 0 src/{api => }/ledger/balance-sheet.js | 0 src/{api => }/ledger/balances.js | 0 src/{api => }/ledger/ledger.js | 0 src/{api => }/ledger/orderbook.js | 0 src/{api => }/ledger/orders.js | 0 src/{api => }/ledger/parse/account-order.js | 0 .../ledger/parse/account-trustline.js | 6 +- src/{api => }/ledger/parse/amount.js | 0 src/{api => }/ledger/parse/cancellation.js | 0 src/{api => }/ledger/parse/fields.js | 0 src/{api => }/ledger/parse/flags.js | 0 src/{api => }/ledger/parse/ledger.js | 0 src/{api => }/ledger/parse/order.js | 0 src/{api => }/ledger/parse/orderbook-order.js | 0 src/{api => }/ledger/parse/pathfind.js | 0 src/{api => }/ledger/parse/payment.js | 0 src/{api => }/ledger/parse/settings.js | 0 .../parse/suspended-payment-cancellation.js | 0 .../parse/suspended-payment-creation.js | 0 .../parse/suspended-payment-execution.js | 8 +- src/{api => }/ledger/parse/transaction.js | 0 src/{api => }/ledger/parse/trustline.js | 0 src/{api => }/ledger/parse/utils.js | 3 +- src/{api => }/ledger/pathfind-types.js | 0 src/{api => }/ledger/pathfind.js | 0 src/{api => }/ledger/settings.js | 0 src/{api => }/ledger/transaction-types.js | 0 src/{api => }/ledger/transaction.js | 0 src/{api => }/ledger/transactions.js | 0 src/{api => }/ledger/trustlines-types.js | 0 src/{api => }/ledger/trustlines.js | 0 src/{api => }/ledger/types.js | 0 src/{api => }/ledger/utils.js | 0 src/{api => }/offline/ledgerhash.js | 0 src/{api => }/server/server.js | 0 src/{api => }/transaction/order.js | 0 .../transaction/ordercancellation.js | 0 src/{api => }/transaction/payment.js | 0 src/{api => }/transaction/settings-types.js | 0 src/{api => }/transaction/settings.js | 0 src/{api => }/transaction/sign.js | 0 src/{api => }/transaction/submit.js | 0 .../suspended-payment-cancellation.js | 0 .../transaction/suspended-payment-creation.js | 0 .../suspended-payment-execution.js | 0 src/{api => }/transaction/trustline.js | 0 src/{api => }/transaction/types.js | 0 src/{api => }/transaction/utils.js | 0 test/account-test.js | 205 -- test/amount-test-error.js | 46 - test/amount-test.js | 1190 -------- test/config-example.js | 46 - test/integration/connection-test.js | 2 +- test/integration/integration-test.js | 4 +- test/metadata-test.js | 36 - test/node_modules/ripple-lib | 1 - test/orderbook-autobridge-test.js | 856 ------ test/orderbook-test.js | 2477 ----------------- test/rangeset-test.js | 2 +- test/remote-test.js | 2222 --------------- test/request-test.js | 1182 -------- test/server-test.js | 1380 --------- test/testconfig.js | 48 - test/transaction-manager-test.js | 911 ------ test/transaction-queue-test.js | 102 - test/transaction-test.js | 2332 ---------------- test/utils-test.js | 24 - 175 files changed, 130 insertions(+), 24723 deletions(-) delete mode 100644 src/api/index.js rename src/{api => }/common/connection.js (100%) rename src/{api => }/common/constants.js (100%) rename src/{api => }/common/errors.js (100%) rename src/{api => }/common/index.js (100%) rename src/{api => }/common/rangeset.js (100%) rename src/{api => }/common/schema-validator.js (100%) rename src/{api => }/common/schemas/address.json (100%) rename src/{api => }/common/schemas/adjustment.json (100%) rename src/{api => }/common/schemas/amount.json (100%) rename src/{api => }/common/schemas/amountbase.json (100%) rename src/{api => }/common/schemas/api-options.json (100%) rename src/{api => }/common/schemas/balance-sheet-options.json (100%) rename src/{api => }/common/schemas/balance.json (100%) rename src/{api => }/common/schemas/blob.json (100%) rename src/{api => }/common/schemas/currency.json (100%) rename src/{api => }/common/schemas/destinationAdjustment.json (100%) rename src/{api => }/common/schemas/get-account-info.json (100%) rename src/{api => }/common/schemas/get-balance-sheet.json (100%) rename src/{api => }/common/schemas/get-balances.json (100%) rename src/{api => }/common/schemas/get-ledger.json (100%) rename src/{api => }/common/schemas/get-orderbook.json (100%) rename src/{api => }/common/schemas/get-orders.json (100%) rename src/{api => }/common/schemas/get-paths.json (100%) rename src/{api => }/common/schemas/get-server-info.json (100%) rename src/{api => }/common/schemas/get-settings.json (100%) rename src/{api => }/common/schemas/get-transaction.json (100%) rename src/{api => }/common/schemas/get-transactions.json (100%) rename src/{api => }/common/schemas/get-trustlines.json (100%) rename src/{api => }/common/schemas/hash128.json (100%) rename src/{api => }/common/schemas/hash256.json (100%) rename src/{api => }/common/schemas/instructions.json (100%) rename src/{api => }/common/schemas/issue.json (100%) rename src/{api => }/common/schemas/lax-amount.json (100%) rename src/{api => }/common/schemas/lax-lax-amount.json (100%) rename src/{api => }/common/schemas/ledger-closed.json (100%) rename src/{api => }/common/schemas/ledger-options.json (100%) rename src/{api => }/common/schemas/ledgerversion.json (100%) rename src/{api => }/common/schemas/max-adjustment.json (100%) rename src/{api => }/common/schemas/memo.json (100%) rename src/{api => }/common/schemas/min-adjustment.json (100%) rename src/{api => }/common/schemas/order-cancellation-transaction.json (100%) rename src/{api => }/common/schemas/order-cancellation.json (100%) rename src/{api => }/common/schemas/order-change.json (100%) rename src/{api => }/common/schemas/order-transaction.json (100%) rename src/{api => }/common/schemas/order.json (100%) rename src/{api => }/common/schemas/orderbook-orders.json (100%) rename src/{api => }/common/schemas/orderbook.json (100%) rename src/{api => }/common/schemas/orders-options.json (100%) rename src/{api => }/common/schemas/outcome.json (100%) rename src/{api => }/common/schemas/pathfind.json (100%) rename src/{api => }/common/schemas/payment-transaction.json (100%) rename src/{api => }/common/schemas/payment.json (100%) rename src/{api => }/common/schemas/prepare.json (100%) rename src/{api => }/common/schemas/quality.json (100%) rename src/{api => }/common/schemas/sequence.json (100%) rename src/{api => }/common/schemas/settings-options.json (100%) rename src/{api => }/common/schemas/settings-transaction.json (100%) rename src/{api => }/common/schemas/settings.json (100%) rename src/{api => }/common/schemas/sign.json (100%) rename src/{api => }/common/schemas/signed-value.json (100%) rename src/{api => }/common/schemas/sourceAdjustment.json (100%) rename src/{api => }/common/schemas/submit.json (100%) rename src/{api => }/common/schemas/suspended-payment-cancellation.json (100%) rename src/{api => }/common/schemas/suspended-payment-creation.json (100%) rename src/{api => }/common/schemas/suspended-payment-execution.json (100%) rename src/{api => }/common/schemas/tag.json (100%) rename src/{api => }/common/schemas/timestamp.json (100%) rename src/{api => }/common/schemas/transaction-options.json (100%) rename src/{api => }/common/schemas/transactions-options.json (100%) rename src/{api => }/common/schemas/trustline-transaction.json (100%) rename src/{api => }/common/schemas/trustline.json (100%) rename src/{api => }/common/schemas/trustlines-options.json (100%) rename src/{api => }/common/schemas/tx.json (100%) rename src/{api => }/common/schemas/uint32.json (100%) rename src/{api => }/common/schemas/value.json (100%) rename src/{api => }/common/serverinfo.js (100%) rename src/{api => }/common/txflags.js (100%) rename src/{api => }/common/types.js (100%) rename src/{api => }/common/utils.js (100%) rename src/{api => }/common/validate.js (100%) delete mode 100644 src/core/account.js delete mode 100644 src/core/amount.js delete mode 100644 src/core/autobridgecalculator.js delete mode 100644 src/core/binformat.js delete mode 100644 src/core/constants.js delete mode 100644 src/core/currency.js delete mode 100644 src/core/index.js delete mode 100644 src/core/log.js delete mode 100644 src/core/meta.js delete mode 100644 src/core/orderbook.js delete mode 100644 src/core/orderbookutils.js delete mode 100644 src/core/pathfind.js delete mode 100644 src/core/rangeset.js delete mode 100644 src/core/remote.js delete mode 100644 src/core/request.js delete mode 100644 src/core/rippleerror.js delete mode 100644 src/core/server.js delete mode 100644 src/core/transaction.js delete mode 100644 src/core/transactionmanager.js delete mode 100644 src/core/transactionqueue.js delete mode 100644 src/core/utils.js rename src/{api => }/ledger/accountinfo.js (100%) rename src/{api => }/ledger/balance-sheet.js (100%) rename src/{api => }/ledger/balances.js (100%) rename src/{api => }/ledger/ledger.js (100%) rename src/{api => }/ledger/orderbook.js (100%) rename src/{api => }/ledger/orders.js (100%) rename src/{api => }/ledger/parse/account-order.js (100%) rename src/{api => }/ledger/parse/account-trustline.js (87%) rename src/{api => }/ledger/parse/amount.js (100%) rename src/{api => }/ledger/parse/cancellation.js (100%) rename src/{api => }/ledger/parse/fields.js (100%) rename src/{api => }/ledger/parse/flags.js (100%) rename src/{api => }/ledger/parse/ledger.js (100%) rename src/{api => }/ledger/parse/order.js (100%) rename src/{api => }/ledger/parse/orderbook-order.js (100%) rename src/{api => }/ledger/parse/pathfind.js (100%) rename src/{api => }/ledger/parse/payment.js (100%) rename src/{api => }/ledger/parse/settings.js (100%) rename src/{api => }/ledger/parse/suspended-payment-cancellation.js (100%) rename src/{api => }/ledger/parse/suspended-payment-creation.js (100%) rename src/{api => }/ledger/parse/suspended-payment-execution.js (64%) rename src/{api => }/ledger/parse/transaction.js (100%) rename src/{api => }/ledger/parse/trustline.js (100%) rename src/{api => }/ledger/parse/utils.js (97%) rename src/{api => }/ledger/pathfind-types.js (100%) rename src/{api => }/ledger/pathfind.js (100%) rename src/{api => }/ledger/settings.js (100%) rename src/{api => }/ledger/transaction-types.js (100%) rename src/{api => }/ledger/transaction.js (100%) rename src/{api => }/ledger/transactions.js (100%) rename src/{api => }/ledger/trustlines-types.js (100%) rename src/{api => }/ledger/trustlines.js (100%) rename src/{api => }/ledger/types.js (100%) rename src/{api => }/ledger/utils.js (100%) rename src/{api => }/offline/ledgerhash.js (100%) rename src/{api => }/server/server.js (100%) rename src/{api => }/transaction/order.js (100%) rename src/{api => }/transaction/ordercancellation.js (100%) rename src/{api => }/transaction/payment.js (100%) rename src/{api => }/transaction/settings-types.js (100%) rename src/{api => }/transaction/settings.js (100%) rename src/{api => }/transaction/sign.js (100%) rename src/{api => }/transaction/submit.js (100%) rename src/{api => }/transaction/suspended-payment-cancellation.js (100%) rename src/{api => }/transaction/suspended-payment-creation.js (100%) rename src/{api => }/transaction/suspended-payment-execution.js (100%) rename src/{api => }/transaction/trustline.js (100%) rename src/{api => }/transaction/types.js (100%) rename src/{api => }/transaction/utils.js (100%) delete mode 100644 test/account-test.js delete mode 100644 test/amount-test-error.js delete mode 100644 test/amount-test.js delete mode 100644 test/config-example.js delete mode 100644 test/metadata-test.js delete mode 120000 test/node_modules/ripple-lib delete mode 100644 test/orderbook-autobridge-test.js delete mode 100644 test/orderbook-test.js delete mode 100644 test/remote-test.js delete mode 100644 test/request-test.js delete mode 100644 test/server-test.js delete mode 100644 test/testconfig.js delete mode 100644 test/transaction-manager-test.js delete mode 100644 test/transaction-queue-test.js delete mode 100644 test/transaction-test.js delete mode 100644 test/utils-test.js diff --git a/.flowconfig b/.flowconfig index 6fccd4db..39d10499 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,9 +1,9 @@ [ignore] -.*/src/api/.* -.*/src/core/.* -.*/dist/.* -.*/test/fixtures/.* +.*/ripple-lib/src/.* +.*/ripple-lib/dist/.* +.*/ripple-lib/test/fixtures/.* .*/node_modules/flow-bin/.* +.*/node_modules/webpack/.* [include] ./node_modules/ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 31b640ba..d2c1ad13 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -4,10 +4,6 @@ "npm-shrinkwrap-version": "5.4.0", "node-version": "v0.12.7", "dependencies": { - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" - }, "babel-runtime": { "version": "5.8.29", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.29.tgz", @@ -22,14 +18,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.0.tgz" }, - "bn.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.2.0.tgz" - }, - "extend": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz" - }, "hash.js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", @@ -102,10 +90,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" }, - "lru-cache": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz" - }, "ripple-address-codec": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz", @@ -126,6 +110,10 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.6.tgz", "dependencies": { + "bn.js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz" + }, "create-hash": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz", @@ -186,6 +174,10 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz", "dependencies": { + "bn.js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz" + }, "brorand": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz" @@ -206,20 +198,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.5.1.tgz" }, - "ripple-lib-value": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ripple-lib-value/-/ripple-lib-value-0.1.0.tgz", - "dependencies": { - "bignumber.js": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.8.tgz" - } - } - }, - "sjcl-codec": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/sjcl-codec/-/sjcl-codec-0.1.0.tgz" - }, "ws": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz", diff --git a/package.json b/package.json index 54592277..89d3fc9d 100644 --- a/package.json +++ b/package.json @@ -15,23 +15,17 @@ "test": "test" }, "dependencies": { - "async": "~0.9.0", "babel-runtime": "^5.5.4", "bignumber.js": "^2.0.3", - "bn.js": "^3.1.1", - "extend": "~1.2.1", "hash.js": "^1.0.3", "https-proxy-agent": "^1.0.0", "is-my-json-valid": "^2.12.2", "lodash": "^3.1.0", - "lru-cache": "~2.5.0", "ripple-address-codec": "^2.0.1", "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", "ws": "~0.7.1" }, "devDependencies": { diff --git a/scripts/ci.sh b/scripts/ci.sh index bb918f4d..4911f590 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -27,7 +27,6 @@ unittest() { babel -D --optional runtime --ignore "**/node_modules/**" -d test-compiled/ test/ echo "--reporter spec --timeout 5000 --slow 500" > test-compiled/mocha.opts mkdir -p test-compiled/node_modules - ln -nfs ../../dist/npm/core test-compiled/node_modules/ripple-lib ln -nfs ../../dist/npm test-compiled/node_modules/ripple-api mocha --opts test-compiled/mocha.opts test-compiled rm -rf test-compiled diff --git a/src/api/index.js b/src/api/index.js deleted file mode 100644 index e119808b..00000000 --- a/src/api/index.js +++ /dev/null @@ -1,111 +0,0 @@ -/* @flow */ - -'use strict'; -const _ = require('lodash'); -const EventEmitter = require('events').EventEmitter; -const common = require('./common'); -const server = require('./server/server'); -const connect = server.connect; -const disconnect = server.disconnect; -const getServerInfo = server.getServerInfo; -const getFee = server.getFee; -const isConnected = server.isConnected; -const getLedgerVersion = server.getLedgerVersion; -const getTransaction = require('./ledger/transaction'); -const getTransactions = require('./ledger/transactions'); -const getTrustlines = require('./ledger/trustlines'); -const getBalances = require('./ledger/balances'); -const getBalanceSheet = require('./ledger/balance-sheet'); -const getPaths = require('./ledger/pathfind'); -const getOrders = require('./ledger/orders'); -const getOrderbook = require('./ledger/orderbook'); -const getSettings = require('./ledger/settings'); -const getAccountInfo = require('./ledger/accountinfo'); -const preparePayment = require('./transaction/payment'); -const prepareTrustline = require('./transaction/trustline'); -const prepareOrder = require('./transaction/order'); -const prepareOrderCancellation = require('./transaction/ordercancellation'); -const prepareSuspendedPaymentCreation = - require('./transaction/suspended-payment-creation'); -const prepareSuspendedPaymentExecution = - require('./transaction/suspended-payment-execution'); -const prepareSuspendedPaymentCancellation = - require('./transaction/suspended-payment-cancellation'); -const prepareSettings = require('./transaction/settings'); -const sign = require('./transaction/sign'); -const submit = require('./transaction/submit'); -const errors = require('./common').errors; -const generateAddress = common.generateAddressAPI; -const computeLedgerHash = require('./offline/ledgerhash'); -const getLedger = require('./ledger/ledger'); - -type APIOptions = { - servers?: Array, - feeCushion?: number, - trace?: boolean, - proxy?: string -} - -class RippleAPI extends EventEmitter { - constructor(options: APIOptions = {}) { - common.validate.apiOptions(options); - super(); - if (options.servers !== undefined) { - const servers: Array = options.servers; - if (servers.length === 1) { - this._feeCushion = options.feeCushion || 1.2; - this.connection = new common.Connection(servers[0], options); - this.connection.on('ledgerClosed', message => { - this.emit('ledgerClosed', server.formatLedgerClose(message)); - }); - } else { - throw new errors.RippleError('Multi-server not implemented'); - } - } - } -} - -_.assign(RippleAPI.prototype, { - connect, - disconnect, - isConnected, - getServerInfo, - getFee, - getLedgerVersion, - - getTransaction, - getTransactions, - getTrustlines, - getBalances, - getBalanceSheet, - getPaths, - getOrders, - getOrderbook, - getSettings, - getAccountInfo, - getLedger, - - preparePayment, - prepareTrustline, - prepareOrder, - prepareOrderCancellation, - prepareSuspendedPaymentCreation, - prepareSuspendedPaymentExecution, - prepareSuspendedPaymentCancellation, - prepareSettings, - sign, - submit, - - generateAddress, - errors -}); - -// these are exposed only for use by unit tests; they are not part of the API -RippleAPI._PRIVATE = { - common, - computeLedgerHash, - ledgerUtils: require('./ledger/utils'), - schemaValidator: require('./common/schema-validator') -}; - -module.exports = RippleAPI; diff --git a/src/api/common/connection.js b/src/common/connection.js similarity index 100% rename from src/api/common/connection.js rename to src/common/connection.js diff --git a/src/api/common/constants.js b/src/common/constants.js similarity index 100% rename from src/api/common/constants.js rename to src/common/constants.js diff --git a/src/api/common/errors.js b/src/common/errors.js similarity index 100% rename from src/api/common/errors.js rename to src/common/errors.js diff --git a/src/api/common/index.js b/src/common/index.js similarity index 100% rename from src/api/common/index.js rename to src/common/index.js diff --git a/src/api/common/rangeset.js b/src/common/rangeset.js similarity index 100% rename from src/api/common/rangeset.js rename to src/common/rangeset.js diff --git a/src/api/common/schema-validator.js b/src/common/schema-validator.js similarity index 100% rename from src/api/common/schema-validator.js rename to src/common/schema-validator.js diff --git a/src/api/common/schemas/address.json b/src/common/schemas/address.json similarity index 100% rename from src/api/common/schemas/address.json rename to src/common/schemas/address.json diff --git a/src/api/common/schemas/adjustment.json b/src/common/schemas/adjustment.json similarity index 100% rename from src/api/common/schemas/adjustment.json rename to src/common/schemas/adjustment.json diff --git a/src/api/common/schemas/amount.json b/src/common/schemas/amount.json similarity index 100% rename from src/api/common/schemas/amount.json rename to src/common/schemas/amount.json diff --git a/src/api/common/schemas/amountbase.json b/src/common/schemas/amountbase.json similarity index 100% rename from src/api/common/schemas/amountbase.json rename to src/common/schemas/amountbase.json diff --git a/src/api/common/schemas/api-options.json b/src/common/schemas/api-options.json similarity index 100% rename from src/api/common/schemas/api-options.json rename to src/common/schemas/api-options.json diff --git a/src/api/common/schemas/balance-sheet-options.json b/src/common/schemas/balance-sheet-options.json similarity index 100% rename from src/api/common/schemas/balance-sheet-options.json rename to src/common/schemas/balance-sheet-options.json diff --git a/src/api/common/schemas/balance.json b/src/common/schemas/balance.json similarity index 100% rename from src/api/common/schemas/balance.json rename to src/common/schemas/balance.json diff --git a/src/api/common/schemas/blob.json b/src/common/schemas/blob.json similarity index 100% rename from src/api/common/schemas/blob.json rename to src/common/schemas/blob.json diff --git a/src/api/common/schemas/currency.json b/src/common/schemas/currency.json similarity index 100% rename from src/api/common/schemas/currency.json rename to src/common/schemas/currency.json diff --git a/src/api/common/schemas/destinationAdjustment.json b/src/common/schemas/destinationAdjustment.json similarity index 100% rename from src/api/common/schemas/destinationAdjustment.json rename to src/common/schemas/destinationAdjustment.json diff --git a/src/api/common/schemas/get-account-info.json b/src/common/schemas/get-account-info.json similarity index 100% rename from src/api/common/schemas/get-account-info.json rename to src/common/schemas/get-account-info.json diff --git a/src/api/common/schemas/get-balance-sheet.json b/src/common/schemas/get-balance-sheet.json similarity index 100% rename from src/api/common/schemas/get-balance-sheet.json rename to src/common/schemas/get-balance-sheet.json diff --git a/src/api/common/schemas/get-balances.json b/src/common/schemas/get-balances.json similarity index 100% rename from src/api/common/schemas/get-balances.json rename to src/common/schemas/get-balances.json diff --git a/src/api/common/schemas/get-ledger.json b/src/common/schemas/get-ledger.json similarity index 100% rename from src/api/common/schemas/get-ledger.json rename to src/common/schemas/get-ledger.json diff --git a/src/api/common/schemas/get-orderbook.json b/src/common/schemas/get-orderbook.json similarity index 100% rename from src/api/common/schemas/get-orderbook.json rename to src/common/schemas/get-orderbook.json diff --git a/src/api/common/schemas/get-orders.json b/src/common/schemas/get-orders.json similarity index 100% rename from src/api/common/schemas/get-orders.json rename to src/common/schemas/get-orders.json diff --git a/src/api/common/schemas/get-paths.json b/src/common/schemas/get-paths.json similarity index 100% rename from src/api/common/schemas/get-paths.json rename to src/common/schemas/get-paths.json diff --git a/src/api/common/schemas/get-server-info.json b/src/common/schemas/get-server-info.json similarity index 100% rename from src/api/common/schemas/get-server-info.json rename to src/common/schemas/get-server-info.json diff --git a/src/api/common/schemas/get-settings.json b/src/common/schemas/get-settings.json similarity index 100% rename from src/api/common/schemas/get-settings.json rename to src/common/schemas/get-settings.json diff --git a/src/api/common/schemas/get-transaction.json b/src/common/schemas/get-transaction.json similarity index 100% rename from src/api/common/schemas/get-transaction.json rename to src/common/schemas/get-transaction.json diff --git a/src/api/common/schemas/get-transactions.json b/src/common/schemas/get-transactions.json similarity index 100% rename from src/api/common/schemas/get-transactions.json rename to src/common/schemas/get-transactions.json diff --git a/src/api/common/schemas/get-trustlines.json b/src/common/schemas/get-trustlines.json similarity index 100% rename from src/api/common/schemas/get-trustlines.json rename to src/common/schemas/get-trustlines.json diff --git a/src/api/common/schemas/hash128.json b/src/common/schemas/hash128.json similarity index 100% rename from src/api/common/schemas/hash128.json rename to src/common/schemas/hash128.json diff --git a/src/api/common/schemas/hash256.json b/src/common/schemas/hash256.json similarity index 100% rename from src/api/common/schemas/hash256.json rename to src/common/schemas/hash256.json diff --git a/src/api/common/schemas/instructions.json b/src/common/schemas/instructions.json similarity index 100% rename from src/api/common/schemas/instructions.json rename to src/common/schemas/instructions.json diff --git a/src/api/common/schemas/issue.json b/src/common/schemas/issue.json similarity index 100% rename from src/api/common/schemas/issue.json rename to src/common/schemas/issue.json diff --git a/src/api/common/schemas/lax-amount.json b/src/common/schemas/lax-amount.json similarity index 100% rename from src/api/common/schemas/lax-amount.json rename to src/common/schemas/lax-amount.json diff --git a/src/api/common/schemas/lax-lax-amount.json b/src/common/schemas/lax-lax-amount.json similarity index 100% rename from src/api/common/schemas/lax-lax-amount.json rename to src/common/schemas/lax-lax-amount.json diff --git a/src/api/common/schemas/ledger-closed.json b/src/common/schemas/ledger-closed.json similarity index 100% rename from src/api/common/schemas/ledger-closed.json rename to src/common/schemas/ledger-closed.json diff --git a/src/api/common/schemas/ledger-options.json b/src/common/schemas/ledger-options.json similarity index 100% rename from src/api/common/schemas/ledger-options.json rename to src/common/schemas/ledger-options.json diff --git a/src/api/common/schemas/ledgerversion.json b/src/common/schemas/ledgerversion.json similarity index 100% rename from src/api/common/schemas/ledgerversion.json rename to src/common/schemas/ledgerversion.json diff --git a/src/api/common/schemas/max-adjustment.json b/src/common/schemas/max-adjustment.json similarity index 100% rename from src/api/common/schemas/max-adjustment.json rename to src/common/schemas/max-adjustment.json diff --git a/src/api/common/schemas/memo.json b/src/common/schemas/memo.json similarity index 100% rename from src/api/common/schemas/memo.json rename to src/common/schemas/memo.json diff --git a/src/api/common/schemas/min-adjustment.json b/src/common/schemas/min-adjustment.json similarity index 100% rename from src/api/common/schemas/min-adjustment.json rename to src/common/schemas/min-adjustment.json diff --git a/src/api/common/schemas/order-cancellation-transaction.json b/src/common/schemas/order-cancellation-transaction.json similarity index 100% rename from src/api/common/schemas/order-cancellation-transaction.json rename to src/common/schemas/order-cancellation-transaction.json diff --git a/src/api/common/schemas/order-cancellation.json b/src/common/schemas/order-cancellation.json similarity index 100% rename from src/api/common/schemas/order-cancellation.json rename to src/common/schemas/order-cancellation.json diff --git a/src/api/common/schemas/order-change.json b/src/common/schemas/order-change.json similarity index 100% rename from src/api/common/schemas/order-change.json rename to src/common/schemas/order-change.json diff --git a/src/api/common/schemas/order-transaction.json b/src/common/schemas/order-transaction.json similarity index 100% rename from src/api/common/schemas/order-transaction.json rename to src/common/schemas/order-transaction.json diff --git a/src/api/common/schemas/order.json b/src/common/schemas/order.json similarity index 100% rename from src/api/common/schemas/order.json rename to src/common/schemas/order.json diff --git a/src/api/common/schemas/orderbook-orders.json b/src/common/schemas/orderbook-orders.json similarity index 100% rename from src/api/common/schemas/orderbook-orders.json rename to src/common/schemas/orderbook-orders.json diff --git a/src/api/common/schemas/orderbook.json b/src/common/schemas/orderbook.json similarity index 100% rename from src/api/common/schemas/orderbook.json rename to src/common/schemas/orderbook.json diff --git a/src/api/common/schemas/orders-options.json b/src/common/schemas/orders-options.json similarity index 100% rename from src/api/common/schemas/orders-options.json rename to src/common/schemas/orders-options.json diff --git a/src/api/common/schemas/outcome.json b/src/common/schemas/outcome.json similarity index 100% rename from src/api/common/schemas/outcome.json rename to src/common/schemas/outcome.json diff --git a/src/api/common/schemas/pathfind.json b/src/common/schemas/pathfind.json similarity index 100% rename from src/api/common/schemas/pathfind.json rename to src/common/schemas/pathfind.json diff --git a/src/api/common/schemas/payment-transaction.json b/src/common/schemas/payment-transaction.json similarity index 100% rename from src/api/common/schemas/payment-transaction.json rename to src/common/schemas/payment-transaction.json diff --git a/src/api/common/schemas/payment.json b/src/common/schemas/payment.json similarity index 100% rename from src/api/common/schemas/payment.json rename to src/common/schemas/payment.json diff --git a/src/api/common/schemas/prepare.json b/src/common/schemas/prepare.json similarity index 100% rename from src/api/common/schemas/prepare.json rename to src/common/schemas/prepare.json diff --git a/src/api/common/schemas/quality.json b/src/common/schemas/quality.json similarity index 100% rename from src/api/common/schemas/quality.json rename to src/common/schemas/quality.json diff --git a/src/api/common/schemas/sequence.json b/src/common/schemas/sequence.json similarity index 100% rename from src/api/common/schemas/sequence.json rename to src/common/schemas/sequence.json diff --git a/src/api/common/schemas/settings-options.json b/src/common/schemas/settings-options.json similarity index 100% rename from src/api/common/schemas/settings-options.json rename to src/common/schemas/settings-options.json diff --git a/src/api/common/schemas/settings-transaction.json b/src/common/schemas/settings-transaction.json similarity index 100% rename from src/api/common/schemas/settings-transaction.json rename to src/common/schemas/settings-transaction.json diff --git a/src/api/common/schemas/settings.json b/src/common/schemas/settings.json similarity index 100% rename from src/api/common/schemas/settings.json rename to src/common/schemas/settings.json diff --git a/src/api/common/schemas/sign.json b/src/common/schemas/sign.json similarity index 100% rename from src/api/common/schemas/sign.json rename to src/common/schemas/sign.json diff --git a/src/api/common/schemas/signed-value.json b/src/common/schemas/signed-value.json similarity index 100% rename from src/api/common/schemas/signed-value.json rename to src/common/schemas/signed-value.json diff --git a/src/api/common/schemas/sourceAdjustment.json b/src/common/schemas/sourceAdjustment.json similarity index 100% rename from src/api/common/schemas/sourceAdjustment.json rename to src/common/schemas/sourceAdjustment.json diff --git a/src/api/common/schemas/submit.json b/src/common/schemas/submit.json similarity index 100% rename from src/api/common/schemas/submit.json rename to src/common/schemas/submit.json diff --git a/src/api/common/schemas/suspended-payment-cancellation.json b/src/common/schemas/suspended-payment-cancellation.json similarity index 100% rename from src/api/common/schemas/suspended-payment-cancellation.json rename to src/common/schemas/suspended-payment-cancellation.json diff --git a/src/api/common/schemas/suspended-payment-creation.json b/src/common/schemas/suspended-payment-creation.json similarity index 100% rename from src/api/common/schemas/suspended-payment-creation.json rename to src/common/schemas/suspended-payment-creation.json diff --git a/src/api/common/schemas/suspended-payment-execution.json b/src/common/schemas/suspended-payment-execution.json similarity index 100% rename from src/api/common/schemas/suspended-payment-execution.json rename to src/common/schemas/suspended-payment-execution.json diff --git a/src/api/common/schemas/tag.json b/src/common/schemas/tag.json similarity index 100% rename from src/api/common/schemas/tag.json rename to src/common/schemas/tag.json diff --git a/src/api/common/schemas/timestamp.json b/src/common/schemas/timestamp.json similarity index 100% rename from src/api/common/schemas/timestamp.json rename to src/common/schemas/timestamp.json diff --git a/src/api/common/schemas/transaction-options.json b/src/common/schemas/transaction-options.json similarity index 100% rename from src/api/common/schemas/transaction-options.json rename to src/common/schemas/transaction-options.json diff --git a/src/api/common/schemas/transactions-options.json b/src/common/schemas/transactions-options.json similarity index 100% rename from src/api/common/schemas/transactions-options.json rename to src/common/schemas/transactions-options.json diff --git a/src/api/common/schemas/trustline-transaction.json b/src/common/schemas/trustline-transaction.json similarity index 100% rename from src/api/common/schemas/trustline-transaction.json rename to src/common/schemas/trustline-transaction.json diff --git a/src/api/common/schemas/trustline.json b/src/common/schemas/trustline.json similarity index 100% rename from src/api/common/schemas/trustline.json rename to src/common/schemas/trustline.json diff --git a/src/api/common/schemas/trustlines-options.json b/src/common/schemas/trustlines-options.json similarity index 100% rename from src/api/common/schemas/trustlines-options.json rename to src/common/schemas/trustlines-options.json diff --git a/src/api/common/schemas/tx.json b/src/common/schemas/tx.json similarity index 100% rename from src/api/common/schemas/tx.json rename to src/common/schemas/tx.json diff --git a/src/api/common/schemas/uint32.json b/src/common/schemas/uint32.json similarity index 100% rename from src/api/common/schemas/uint32.json rename to src/common/schemas/uint32.json diff --git a/src/api/common/schemas/value.json b/src/common/schemas/value.json similarity index 100% rename from src/api/common/schemas/value.json rename to src/common/schemas/value.json diff --git a/src/api/common/serverinfo.js b/src/common/serverinfo.js similarity index 100% rename from src/api/common/serverinfo.js rename to src/common/serverinfo.js diff --git a/src/api/common/txflags.js b/src/common/txflags.js similarity index 100% rename from src/api/common/txflags.js rename to src/common/txflags.js diff --git a/src/api/common/types.js b/src/common/types.js similarity index 100% rename from src/api/common/types.js rename to src/common/types.js diff --git a/src/api/common/utils.js b/src/common/utils.js similarity index 100% rename from src/api/common/utils.js rename to src/common/utils.js diff --git a/src/api/common/validate.js b/src/common/validate.js similarity index 100% rename from src/api/common/validate.js rename to src/common/validate.js diff --git a/src/core/account.js b/src/core/account.js deleted file mode 100644 index 1af60f19..00000000 --- a/src/core/account.js +++ /dev/null @@ -1,388 +0,0 @@ -'use strict'; -// Routines for working with an account. -// -// You should not instantiate this class yourself, instead use Remote#account. -// -// Events: -// wallet_clean : True, iff the wallet has been updated. -// wallet_dirty : True, iff the wallet needs to be updated. -// balance: The current stamp balance. -// balance_proposed -// - -const _ = require('lodash'); -const async = require('async'); -const extend = require('extend'); -const util = require('util'); -const {deriveAddress} = require('ripple-keypairs'); -const {EventEmitter} = require('events'); -const {TransactionManager} = require('./transactionmanager'); -const {isValidAddress} = require('ripple-address-codec'); - -/** - * @constructor Account - * @param {Remote} remote - * @param {String} account - */ - -function Account(remote, address) { - EventEmitter.call(this); - - const self = this; - - this._remote = remote; - this._address = address; - this._subs = 0; - - // Ledger entry object - // Important: This must never be overwritten, only extend()-ed - this._entry = { }; - - function listenerAdded(type) { - if (_.includes(Account.subscribeEvents, type)) { - if (!self._subs && self._remote._connected) { - self._remote.requestSubscribe() - .addAccount(self._address) - .broadcast().request(); - } - self._subs += 1; - } - } - - this.on('newListener', listenerAdded); - - function listenerRemoved(type) { - if (_.includes(Account.subscribeEvents, type)) { - self._subs -= 1; - if (!self._subs && self._remote._connected) { - self._remote.requestUnsubscribe() - .addAccount(self._address) - .broadcast().request(); - } - } - } - - this.on('removeListener', listenerRemoved); - - function attachAccount(request) { - if (isValidAddress(self._address) && self._subs) { - request.addAccount(self._address); - } - } - - this._remote.on('prepare_subscribe', attachAccount); - - function handleTransaction(transaction) { - if (!transaction.mmeta) { - return; - } - - let changed = false; - - transaction.mmeta.each(function(an) { - const isAccount = an.fields.Account === self._address; - const isAccountRoot = isAccount && (an.entryType === 'AccountRoot'); - - if (isAccountRoot) { - extend(self._entry, an.fieldsNew, an.fieldsFinal); - changed = true; - } - }); - - if (changed) { - self.emit('entry', self._entry); - } - } - - this.on('transaction', handleTransaction); - - this._transactionManager = new TransactionManager(this); - - return this; -} - -util.inherits(Account, EventEmitter); - -/** - * List of events that require a remote subscription to the account. - */ - -Account.subscribeEvents = ['transaction', 'entry']; - -Account.prototype.toJson = function() { - return this._address; -}; - -/** - * Whether the AccountId is valid. - * - * Note: This does not tell you whether the account exists in the ledger. - */ - -Account.prototype.isValid = function() { - return isValidAddress(this._address); -}; - -/** - * Request account info - * - * @param {Function} callback - */ - -Account.prototype.getInfo = function(callback) { - return this._remote.requestAccountInfo({account: this._address}, callback); -}; - -/** - * Retrieve the current AccountRoot entry. - * - * To keep up-to-date with changes to the AccountRoot entry, subscribe to the - * 'entry' event. - * - * @param {Function} callback - */ - -Account.prototype.entry = function(callback_) { - const self = this; - const callback = typeof callback_ === 'function' ? callback_ : _.noop; - - function accountInfo(err, info) { - if (err) { - callback(err); - } else { - extend(self._entry, info.account_data); - self.emit('entry', self._entry); - callback(null, info); - } - } - - this.getInfo(accountInfo); - - return this; -}; - -Account.prototype.getNextSequence = function(callback_) { - const callback = typeof callback_ === 'function' ? callback_ : _.noop; - - function isNotFound(err) { - return err && typeof err === 'object' - && typeof err.remote === 'object' - && err.remote.error === 'actNotFound'; - } - - function accountInfo(err, info) { - if (isNotFound(err)) { - // New accounts will start out as sequence one - callback(null, 1); - } else if (err) { - callback(err); - } else { - callback(null, info.account_data.Sequence); - } - } - - this.getInfo(accountInfo); - - return this; -}; - -/** - * Retrieve this account's Ripple trust lines. - * - * To keep up-to-date with changes to the AccountRoot entry, subscribe to the - * 'lines' event. (Not yet implemented.) - * - * @param {function(err, lines)} callback Called with the result - */ - -Account.prototype.lines = function(callback_) { - const self = this; - const callback = typeof callback_ === 'function' ? callback_ : _.noop; - - function accountLines(err, res) { - if (err) { - callback(err); - } else { - self._lines = res.lines; - self.emit('lines', self._lines); - callback(null, res); - } - } - - this._remote.requestAccountLines({account: this._address}, accountLines); - - return this; -}; - -/** - * Retrieve this account's single trust line. - * - * @param {string} currency Currency - * @param {string} address Ripple address - * @param {function(err, line)} callback Called with the result - * @returns {Account} - */ - -Account.prototype.line = function(currency, address, callback_) { - const self = this; - const callback = typeof callback_ === 'function' ? callback_ : _.noop; - - self.lines(function(err, data) { - if (err) { - return callback(err); - } - - let line; - - for (let i = 0; i < data.lines.length; i++) { - const l = data.lines[i]; - if (l.account === address && l.currency === currency) { - line = l; - break; - } - } - - callback(null, line); - }); - - return this; -}; - -/** - * Notify object of a relevant transaction. - * - * This is only meant to be called by the Remote class. You should never have to - * call this yourself. - * - * @param {Object} message - */ - -Account.prototype.notify = -Account.prototype.notifyTx = function(transaction) { - // Only trigger the event if the account object is actually - // subscribed - this prevents some weird phantom events from - // occurring. - if (!this._subs) { - return; - } - - this.emit('transaction', transaction); - - const account = transaction.transaction.Account; - - if (!account) { - return; - } - - const isThisAccount = (account === this._address); - - this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound', - transaction); -}; - -/** - * Submit a transaction to an account's - * transaction manager - * - * @param {Transaction} transaction - */ - -Account.prototype.submit = function(transaction) { - this._transactionManager.submit(transaction); -}; - - -/** - * Check whether the given public key is valid for this account - * - * @param {Hex-encoded_String|RippleAddress} public_key Public key - * @param {Function} callback Is a callback - * @returns {void} - * - * @callback - * param {Error} err - * param {Boolean} true if the public key is valid and active, false otherwise - */ -Account.prototype.publicKeyIsActive = function(public_key, callback) { - const self = this; - let public_key_as_uint160; - - try { - public_key_as_uint160 = Account._publicKeyToAddress(public_key); - } catch (err) { - return callback(err); - } - - function getAccountInfo(async_callback) { - self.getInfo(function(err, account_info_res) { - - // If the remote responds with an Account Not Found error then the account - // is unfunded and thus we can assume that the master key is active - if (err && err.remote && err.remote.error === 'actNotFound') { - async_callback(null, null); - } else { - async_callback(err, account_info_res); - } - }); - } - - function publicKeyIsValid(account_info_res, async_callback) { - // Catch the case of unfunded accounts - if (!account_info_res) { - - if (public_key_as_uint160 === self._address) { - async_callback(null, true); - } else { - async_callback(null, false); - } - - return; - } - - const account_info = account_info_res.account_data; - - // Respond with true if the RegularKey is set and matches the given - // public key or if the public key matches the account address and - // the lsfDisableMaster is not set - if (account_info.RegularKey && - account_info.RegularKey === public_key_as_uint160) { - async_callback(null, true); - } else if (account_info.Account === public_key_as_uint160 && - ((account_info.Flags & 0x00100000) === 0)) { - async_callback(null, true); - } else { - async_callback(null, false); - } - } - - const steps = [ - getAccountInfo, - publicKeyIsValid - ]; - - async.waterfall(steps, callback); -}; - -/** - * Convert a hex-encoded public key to a Ripple Address - * - * @static - * - * @param {Hex-encoded_string|RippleAddress} public_key Public key - * @returns {RippleAddress} Ripple Address - */ -Account._publicKeyToAddress = function(public_key) { - if (isValidAddress(public_key)) { - return public_key; - } else if (/^[0-9a-fA-F]+$/.test(public_key)) { - return deriveAddress(public_key); - } else { // eslint-disable-line no-else-return - throw new Error('Public key is invalid. Must be a UInt160 or a hex string'); - } -}; - -module.exports = { - Account -}; - -// vim:sw=2:sts=2:ts=8:et diff --git a/src/core/amount.js b/src/core/amount.js deleted file mode 100644 index 622e836d..00000000 --- a/src/core/amount.js +++ /dev/null @@ -1,937 +0,0 @@ -'use strict'; - -// Represent Ripple amounts and currencies. -// - Numbers in hex are big-endian. - -const assert = require('assert'); -const extend = require('extend'); -const utils = require('./utils'); -const normalizeCurrency = require('./currency').normalizeCurrency; -const {XRPValue, IOUValue} = require('ripple-lib-value'); -const {isValidAddress} = require('ripple-address-codec'); -const {ACCOUNT_ONE, ACCOUNT_ZERO, CURRENCY_ONE} - = require('./constants'); - -type Value = XRPValue | IOUValue; - -function Amount(value = new XRPValue(NaN)) { - // Json format: - // integer : XRP - // { 'value' : ..., 'currency' : ..., 'issuer' : ...} - assert(value instanceof XRPValue || value instanceof IOUValue); - - this._value = value; - this._is_native = true; // Default to XRP. Only valid if value is not NaN. - this._currency = null; - this._issuer = 'NaN'; -} - -/** - * Set strict_mode = false to disable amount range checking - */ - -Amount.strict_mode = true; - -const consts = { - currency_xns: 0, - currency_one: 1, - xns_precision: 6, - - // bi_ prefix refers to "big integer" - // man refers to mantissa - bi_man_max_value: '9999999999999999', - bi_man_min_value: Number(1e15).toString(), - bi_xns_max: Number(1e17).toString(), - bi_xns_min: Number(-1e17).toString(), - - cMinOffset: -96, - cMaxOffset: 80, - - // Maximum possible amount for non-XRP currencies using the maximum mantissa - // with maximum exponent. Corresponds to hex 0xEC6386F26FC0FFFF. - max_value: '9999999999999999e80', - // Minimum nonzero absolute value for non-XRP currencies. - min_value: '1000000000000000e-96' -}; - -const MAX_XRP_VALUE = new XRPValue(1e11); -const MAX_IOU_VALUE = new IOUValue(consts.max_value); -const MIN_IOU_VALUE = new IOUValue(consts.min_value); - -const bi_xns_unit = new IOUValue(1e6); - -// Add constants to Amount class -extend(Amount, consts); - -// DEPRECATED: Use Amount instead, e.g. Amount.currency_xns -exports.consts = consts; - -// Given '100/USD/ISSUER' return the a string with ISSUER remapped. -Amount.text_full_rewrite = function(j) { - return Amount.from_json(j).to_text_full(); -}; - -// Given '100/USD/ISSUER' return the json. -Amount.json_rewrite = function(j) { - return Amount.from_json(j).to_json(); -}; - -Amount.from_number = function(n) { - return (new Amount()).parse_number(n); -}; - -Amount.from_json = function(j) { - return (new Amount()).parse_json(j); -}; - -Amount.from_quality = function(quality, currency, issuer, opts) { - return (new Amount()).parse_quality(quality, currency, issuer, opts); -}; - -Amount.from_human = function(j, opts) { - return (new Amount()).parse_human(j, opts); -}; - -Amount.is_valid = function(j) { - return Amount.from_json(j).is_valid(); -}; - -Amount.is_valid_full = function(j) { - return Amount.from_json(j).is_valid_full(); -}; - -Amount.NaN = function() { - const result = new Amount(); - result._value = new IOUValue(NaN); // should have no effect - return result; // but let's be careful -}; - -Amount.from_components_unsafe = function(value: Value, currency: string, - issuer: string, isNative: boolean -) { - const result = new Amount(value); - result._is_native = isNative; - result._currency = currency; - result._issuer = issuer; - - result._value = value.isZero() && value.isNegative() ? - value.negate() : value; - return result; -}; - -// be sure that _is_native is set properly BEFORE calling _set_value -Amount.prototype._set_value = function(value: Value) { - this._value = value.isZero() && value.isNegative() ? - value.negate() : value; - this._check_limits(); -}; - -// Returns a new value which is the absolute value of this. -Amount.prototype.abs = function() { - return this._copy(this._value.abs()); -}; - -Amount.prototype.add = function(addend) { - const addendAmount = addend instanceof Amount ? - addend : Amount.from_json(addend); - - if (!this.is_comparable(addendAmount)) { - return new Amount(); - } - - return this._copy(this._value.add(addendAmount._value)); -}; - -Amount.prototype.subtract = function(subtrahend) { - // Correctness over speed, less code has less bugs, reuse add code. - const subsAmount = subtrahend instanceof Amount ? - subtrahend : Amount.from_json(subtrahend); - return this.add(subsAmount.negate()); -}; - -// XXX Diverges from cpp. -Amount.prototype.multiply = function(multiplicand) { - const multiplicandValue = multiplicand instanceof Amount ? - multiplicand._value : - Amount.from_json(multiplicand)._value; - - return this._copy(this._value.multiply(multiplicandValue)); - -}; - -Amount.prototype.scale = function(scaleFactor) { - return this.multiply(scaleFactor); -}; - -Amount.prototype.divide = function(divisor) { - const divisorValue = divisor instanceof Amount ? - divisor._value : - Amount.from_json(divisor)._value; - - return this._copy(this._value.divide(divisorValue)); -}; - -/** - * This function calculates a ratio - such as a price - between two Amount - * objects. - * - * The return value will have the same type (currency) as the numerator. This is - * a simplification, which should be sane in most cases. For example, a USD/XRP - * price would be rendered as USD. - * - * @example - * const price = buy_amount.ratio_human(sell_amount); - * - * @this {Amount} The numerator (top half) of the fraction. - * @param {Amount} denominator The denominator (bottom half) of the fraction. - * @param opts Options for the calculation. - * @param opts.reference_date {Date|Number} Date based on which - * demurrage/interest should be applied. Can be given as JavaScript Date or int - * for Ripple epoch. - * @return {Amount} The resulting ratio. Unit will be the same as numerator. - */ - -Amount.prototype.ratio_human = function(denom) { - const numerator = this.clone(); - const denominator = Amount.from_json(denom); - - // If either operand is NaN, the result is NaN. - if (!numerator.is_valid() || !denominator.is_valid()) { - return new Amount(NaN); - } - - if (denominator.is_zero()) { - return new Amount(NaN); - } - - // Special case: The denominator is a native (XRP) amount. - // - // In that case, it's going to be expressed as base units (1 XRP = - // 10^xns_precision base units). - // - // However, the unit of the denominator is lost, so when the resulting ratio - // is printed, the ratio is going to be too small by a factor of - // 10^xns_precision. - // - // To compensate, we multiply the numerator by 10^xns_precision. - if (denominator._is_native) { - numerator._set_value(numerator._value.multiply(bi_xns_unit)); - } - - return numerator.divide(denominator); -}; - -/** - * Calculate a product of two amounts. - * - * This function allows you to calculate a product between two amounts which - * retains XRPs human/external interpretation (i.e. 1 XRP = 1,000,000 base - * units). - * - * Intended use is to calculate something like: 10 USD * 10 XRP/USD = 100 XRP - * - * @example - * let sell_amount = buy_amount.product_human(price); - * - * @see Amount#ratio_human - * - * @param {Amount} factor The second factor of the product. - * @param {Object} opts Options for the calculation. - * @param {Date|Number} opts.reference_date Date based on which - * demurrage/interest should be applied. Can be given as JavaScript Date or int - * for Ripple epoch. - * @return {Amount} The product. Unit will be the same as the first 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(); - } - - const product = this.multiply(fac); - - // Special case: The second factor is a native (XRP) amount expressed as base - // units (1 XRP = 10^xns_precision base units). - // - // See also Amount#ratio_human. - if (fac._is_native) { - const quotient = product.divide(bi_xns_unit.toString()); - product._set_value(quotient._value); - } - - return product; -}; - -/** - * Turn this amount into its inverse. - * - * @return {Amount} self - * @private - */ -Amount.prototype._invert = function() { - this._set_value(this._value.invert()); - return this; -}; - -/** - * Return the inverse of this amount. - * - * @return {Amount} New Amount object with same currency and issuer, but the - * inverse of the value. - */ -Amount.prototype.invert = function() { - return this.clone()._invert(); -}; - -/** - * Canonicalize amount value is now taken care of in the Value classes - * - * Mirrors rippled's internal Amount representation - * From https://github.com/ripple/rippled/blob/develop/src/ripple/data - * /protocol/STAmount.h#L31-L40 - * - * Internal form: - * 1: If amount is zero, then value is zero and offset is -100 - * 2: Otherwise: - * legal offset range is -96 to +80 inclusive - * value range is 10^15 to (10^16 - 1) inclusive - * amount = value * [10 ^ offset] - * - * ------------------- - * - * The amount can be epxresses as A x 10^B - * Where: - * - A must be an integer between 10^15 and (10^16)-1 inclusive - * - B must be between -96 and 80 inclusive - * - * This results - * - minumum: 10^15 x 10^-96 -> 10^-81 -> -1e-81 - * - maximum: (10^16)-1 x 10^80 -> 9999999999999999e80 - * - * @returns {Amount} - * @throws {Error} if offset exceeds legal ranges, meaning the amount value is - * bigger than supported - */ - -Amount.prototype._check_limits = function() { - if (!Amount.strict_mode) { - return this; - } - if (this._value.isNaN() || this._value.isZero()) { - return this; - } - const absval = this._value.abs(); - if (this._is_native) { - if (absval.greaterThan(MAX_XRP_VALUE)) { - throw new Error('Exceeding max value of ' + MAX_XRP_VALUE.toString()); - } - } else { - if (absval.lessThan(MIN_IOU_VALUE)) { - throw new Error('Exceeding min value of ' + MIN_IOU_VALUE.toString()); - } - if (absval.greaterThan(MAX_IOU_VALUE)) { - throw new Error('Exceeding max value of ' + MAX_IOU_VALUE.toString()); - } - } - return this; -}; - -Amount.prototype.clone = function(negate) { - return this.copyTo(new Amount(this._value), negate); -}; - -Amount.prototype._copy = function(value) { - const copy = this.clone(); - copy._set_value(value); - return copy; -}; - -Amount.prototype.compareTo = function(to) { - const toAmount = to instanceof Amount ? to : Amount.from_json(to); - - if (!this.is_comparable(toAmount)) { - throw new Error('Not comparable'); - } - return this._value.comparedTo(toAmount._value); -}; - -// Make d a copy of this. Returns d. -// Modification of objects internally refered to is not allowed. -Amount.prototype.copyTo = function(d, negate) { - d._value = negate ? this._value.negate() : this._value; - d._is_native = this._is_native; - d._currency = this._currency; - d._issuer = this._issuer; - return d; -}; - -Amount.prototype.currency = function() { - return this._currency; -}; - -Amount.prototype.equals = function(d, ignore_issuer) { - if (!(d instanceof Amount)) { - return this.equals(Amount.from_json(d)); - } - - return this.is_valid() && d.is_valid() - && this._is_native === d._is_native - && this._value.equals(d._value) - && (this._is_native || ((this._currency === d._currency) - && (ignore_issuer || this._issuer === d._issuer))); -}; - -// True if Amounts are valid and both native or non-native. -Amount.prototype.is_comparable = function(v) { - return this.is_valid() && v.is_valid() && this._is_native === v._is_native; -}; - -Amount.prototype.is_native = function() { - return this._is_native; -}; - -Amount.prototype.is_negative = function() { - return this._value.isNegative(); -}; - -Amount.prototype.is_positive = function() { - return !this.is_zero() && !this.is_negative(); -}; - -// Only checks the value. Not the currency and issuer. -Amount.prototype.is_valid = function() { - return !this._value.isNaN(); -}; - -Amount.prototype.is_valid_full = function() { - return this.is_valid() && this._currency !== null - && isValidAddress(this._issuer) && this._issuer !== ACCOUNT_ZERO; -}; - -Amount.prototype.is_zero = function() { - return this._value.isZero(); -}; - -Amount.prototype.issuer = function() { - return this._issuer; -}; - -// Return a new value. -Amount.prototype.negate = function() { - return this.clone('NEGATE'); -}; - -/** - * Tries to correctly interpret an amount as entered by a user. - * - * Examples: - * - * XRP 250 => 250000000/XRP - * 25.2 XRP => 25200000/XRP - * USD 100.40 => 100.4/USD/? - * 100 => 100000000/XRP - * - * - * The regular expression below matches above cases, broken down for better - * understanding: - * - * // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. - * // See ISO 4217 - * ([A-z]{3}|[0-9]{3}) - * - * // end of string - * $ - */ - -Amount.prototype.parse_human = function(j) { - const hex_RE = /^[a-fA-F0-9]{40}$/; - const currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/; - - let value; - let currency; - - const words = j.split(' ').filter(function(word) { - return word !== ''; - }); - - function isNumber(s) { - return isFinite(s) && s !== '' && s !== null; - } - - if (words.length === 1) { - if (isNumber(words[0])) { - value = words[0]; - currency = 'XRP'; - } else { - value = words[0].slice(0, -3); - currency = words[0].slice(-3); - if (!(isNumber(value) && currency.match(currency_RE))) { - return new Amount(); - } - } - } else if (words.length === 2) { - if (isNumber(words[0]) && words[1].match(hex_RE)) { - value = words[0]; - currency = words[1]; - } else if (words[0].match(currency_RE) && isNumber(words[1])) { - value = words[1]; - currency = words[0]; - } else if (isNumber(words[0]) && words[1].match(currency_RE)) { - value = words[0]; - currency = words[1]; - } else { - return new Amount(); - } - } else { - return new Amount(); - } - - currency = currency.toUpperCase(); - this.set_currency(currency); - this._is_native = (currency === 'XRP'); - const newValue = - (this._is_native ? new XRPValue(value) : - new IOUValue(value)); - this._set_value(newValue); - return this; -}; - -Amount.prototype.parse_issuer = function(issuer) { - this._issuer = issuer; - return this; -}; - -/** - * Decode a price from a BookDirectory index. - * - * BookDirectory ledger entries each encode the offer price in their index. This - * method can decode that information and populate an Amount object with it. - * - * It is possible not to provide a currency or issuer, but be aware that Amount - * objects behave differently based on the currency, so you may get incorrect - * results. - * - * Prices involving demurraging currencies are tricky, since they depend on the - * base and counter currencies. - * - * @param {String} quality 8 hex bytes quality or 32 hex bytes BookDirectory - * index. - * @param {Currency|String} counterCurrency currency of the resulting Amount - * object. - * @param {Issuer|String} counterIssuer Issuer of the resulting Amount object. - * @param {Object} opts Additional options - * @param {Boolean} opts.inverse If true, return the inverse of the price - * encoded in the quality. - * @param {Currency|String} opts.base_currency The other currency. This plays a - * role with interest-bearing or demurrage currencies. In that case the - * demurrage has to be applied when the quality is decoded, otherwise the - * price will be false. - * @param {Date|Number} opts.reference_date Date based on which - * demurrage/interest should be applied. Can be given as JavaScript Date or int - * for Ripple epoch. - * @param {Boolean} opts.xrp_as_drops Whether XRP amount should be treated as - * drops. When the base currency is XRP, the quality is calculated in drops. - * For human use however, we want to think of 1000000 drops as 1 XRP and - * prices as per-XRP instead of per-drop. - * @return {Amount} self - */ -Amount.prototype.parse_quality = -function(quality, counterCurrency, counterIssuer, opts) { - const options = opts || {}; - - const baseCurrency = options.base_currency; - - const mantissa_hex = quality.substring(quality.length - 14); - const offset_hex = quality.substring( - quality.length - 16, quality.length - 14); - const mantissa = new IOUValue(mantissa_hex, null, 16); - const offset = parseInt(offset_hex, 16) - 100; - - this._currency = normalizeCurrency(counterCurrency); - this._issuer = counterIssuer; - this._is_native = (this._currency === 'XRP'); - - if (this._is_native && baseCurrency === 'XRP') { - throw new Error('XRP/XRP quality is not allowed'); - } - - /* - The quality, as stored in the last 64 bits of a directory index, is stored as - the quotient of TakerPays/TakerGets. - - When `opts.inverse` is true we are looking at a quality used for determining a - `bid` price and it must first be inverted, before our declared base/counter - currencies are in line with the price. - - For example: - - quality as stored : 5 USD / 3000000 drops - inverted : 3000000 drops / 5 USD - */ - const valueStr = mantissa.toString() + 'e' + offset.toString(); - let nativeAdjusted = new IOUValue(valueStr); - nativeAdjusted = options.inverse ? nativeAdjusted.invert() : nativeAdjusted; - - if (!options.xrp_as_drops) { - // `In a currency exchange, the exchange rate is quoted as the units of the - // counter currency in terms of a single unit of a base currency`. A - // quality is how much taker must `pay` to get ONE `gets` unit thus: - // pays ~= counterCurrency - // gets ~= baseCurrency. - if (this._is_native) { - // pay:$price drops get:1 X - // pay:($price / 1,000,000) XRP get:1 X - nativeAdjusted = nativeAdjusted.divide(bi_xns_unit); - } 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); - } - } - if (this._is_native) { - this._set_value( - new XRPValue(nativeAdjusted.round(6, XRPValue.getBNRoundDown()) - .toString())); - } else { - this._set_value(nativeAdjusted); - } - - return this; -}; - -Amount.prototype.parse_number = function(n) { - this._is_native = false; - this._currency = CURRENCY_ONE; - this._issuer = ACCOUNT_ONE; - this._set_value(new IOUValue(n)); - return this; -}; - -// <-> j -Amount.prototype.parse_json = function(j) { - switch (typeof j) { - case 'string': - // .../.../... notation is not a wire format. But allowed for easier - // testing. - const m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/); - - if (m) { - this._currency = normalizeCurrency(m[2]); - if (m[3]) { - this._issuer = m[3]; - } else { - this._issuer = 'NaN'; - } - this.parse_value(m[1]); - } else { - this.parse_native(j); - this._currency = 'XRP'; - this._issuer = ACCOUNT_ZERO; - } - break; - - case 'number': - this.parse_json(String(j)); - break; - - case 'object': - if (j === null) { - break; - } - - if (j instanceof Amount) { - j.copyTo(this); - } else if (j.hasOwnProperty('value')) { - this._currency = normalizeCurrency(j.currency); - - if (typeof j.issuer !== 'string') { - throw new Error('issuer must be a string'); - } - this._issuer = j.issuer; - - this.parse_value(j.value); - } - break; - - default: - this._set_value(new IOUValue(NaN)); - } - - return this; -}; - -// Parse a XRP value from untrusted input. -// - integer = raw units -// - float = with precision 6 -// XXX Improvements: disallow leading zeros. -Amount.prototype.parse_native = function(j) { - if (j && typeof j === 'string' && !isNaN(j)) { - if (j.indexOf('.') >= 0) { - throw new Error('Native amounts must be specified in integer drops'); - } - const value = new XRPValue(j); - this._is_native = true; - this._set_value(value.divide(bi_xns_unit)); - } else { - this._set_value(new IOUValue(NaN)); - } - - return this; -}; - -// Parse a non-native value for the json wire format. -// Requires _currency to be set! -Amount.prototype.parse_value = function(j) { - this._is_native = false; - const newValue = new IOUValue(j, IOUValue.getBNRoundDown()); - this._set_value(newValue); - return this; -}; - -Amount.prototype.set_currency = function(c) { - this._currency = normalizeCurrency(c); - this._is_native = (c === 'XRP'); - return this; -}; - -Amount.prototype.set_issuer = function(issuer) { - this._issuer = issuer; - return this; -}; - -Amount.prototype.to_number = function() { - return Number(this.to_text()); -}; - - -// this one is needed because Value.abs creates new BigNumber, -// and BigNumber constructor is very slow, so we want to -// call it only if absolutely necessary -function absValue(value: Value): Value { - return value.isNegative() ? value.abs() : value; -} - -// Convert only value to JSON wire format. -Amount.prototype.to_text = function() { - if (!this.is_valid()) { - return 'NaN'; - } - - if (this._is_native) { - return this._value.multiply(bi_xns_unit).toString(); - } - - // not native - const offset = this._value.getExponent() - 15; - const sign = this._value.isNegative() ? '-' : ''; - const mantissa = - utils.getMantissa16FromString(absValue(this._value).toString()); - if (offset !== 0 && (offset < -25 || offset > -4)) { - // Use e notation. - // XXX Clamp output. - return sign + mantissa.toString() + 'e' + offset.toString(); - } - - const val = '000000000000000000000000000' - + mantissa.toString() - + '00000000000000000000000'; - const pre = val.substring(0, offset + 43); - const post = val.substring(offset + 43); - const s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros. - const s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros. - - return sign + (s_pre ? s_pre[0] : '0') - + (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : ''); -}; - -/** - * Format only value in a human-readable format. - * - * @example - * let pretty = amount.to_human({precision: 2}); - * - * @param {Object} options Options for formatter. - * @param {Number} options.precision Max. number of digits after decimal point. - * @param {Number} options.min_precision Min. number of digits after dec. point. - * @param {Boolean} options.skip_empty_fraction Don't show fraction if it - * is zero, even if min_precision is set. - * @param {Number} options.max_sig_digits Maximum number of significant digits. - * Will cut fractional part, but never integer part. - * @param {Boolean|String} options.group_sep Whether to show a separator every n - * digits, if a string, that value will be used as the separator. Default: ',' - * @param {Number} options.group_width How many numbers will be grouped - * together, default: 3. - * @param {Boolean|String} options.signed Whether negative numbers will have a - * prefix. If String, that string will be used as the prefix. Default: '-' - * @param {Date|Number} options.reference_date Date based on which - * demurrage/interest should be applied. Can be given as JavaScript Date or int - * for Ripple epoch. - * @return {String} amount string - */ -Amount.prototype.to_human = function(options) { - const opts = options || {}; - - if (!this.is_valid()) { - return 'NaN'; - } - - /* eslint-disable consistent-this */ - // Apply demurrage/interest - const ref = this; - /* eslint-enable consistent-this */ - - const isNegative = ref._value.isNegative(); - const valueString = ref._value.abs().toFixed(); - const parts = valueString.split('.'); - let int_part = parts[0]; - let fraction_part = parts.length === 2 ? parts[1] : ''; - - int_part = int_part.replace(/^0*/, ''); - fraction_part = fraction_part.replace(/0*$/, ''); - - if (fraction_part.length || !opts.skip_empty_fraction) { - // Enforce the maximum number of decimal digits (precision) - if (typeof opts.precision === 'number') { - let precision = Math.max(0, opts.precision); - precision = Math.min(precision, fraction_part.length); - const rounded = Number('0.' + fraction_part).toFixed(precision); - - if (rounded < 1) { - fraction_part = rounded.substring(2); - } else { - int_part = (Number(int_part) + 1).toString(); - fraction_part = ''; - } - - while (fraction_part.length < precision) { - fraction_part = '0' + fraction_part; - } - } - - // Limit the number of significant digits (max_sig_digits) - if (typeof opts.max_sig_digits === 'number') { - // First, we count the significant digits we have. - // A zero in the integer part does not count. - const int_is_zero = Number(int_part) === 0; - let digits = int_is_zero ? 0 : int_part.length; - - // Don't count leading zeros in the fractional part if the integer part is - // zero. - const sig_frac = int_is_zero - ? fraction_part.replace(/^0*/, '') - : fraction_part; - digits += sig_frac.length; - - // Now we calculate where we are compared to the maximum - let rounding = digits - opts.max_sig_digits; - - // If we're under the maximum we want to cut no (=0) digits - rounding = Math.max(rounding, 0); - - // If we're over the maximum we still only want to cut digits from the - // fractional part, not from the integer part. - rounding = Math.min(rounding, fraction_part.length); - - // Now we cut `rounding` digits off the right. - if (rounding > 0) { - fraction_part = fraction_part.slice(0, -rounding); - } - } - - // Enforce the minimum number of decimal digits (min_precision) - if (typeof opts.min_precision === 'number') { - opts.min_precision = Math.max(0, opts.min_precision); - while (fraction_part.length < opts.min_precision) { - fraction_part += '0'; - } - } - } - - if (opts.group_sep !== false) { - const sep = (typeof opts.group_sep === 'string') ? opts.group_sep : ','; - const groups = utils.chunkString(int_part, opts.group_width || 3, true); - int_part = groups.join(sep); - } - - let formatted = ''; - if (isNegative && opts.signed !== false) { - formatted += '-'; - } - - formatted += int_part.length ? int_part : '0'; - formatted += fraction_part.length ? '.' + fraction_part : ''; - - return formatted; -}; - -Amount.prototype.to_human_full = function(options) { - const opts = options || {}; - const value = this.to_human(opts); - const currency = this._currency; - const issuer = this._issuer; - const base = value + '/' + currency; - return this.is_native() ? base : (base + '/' + issuer); -}; - -Amount.prototype.to_json = function() { - if (this._is_native) { - return this.to_text(); - } - - const amount_json = { - value: this.to_text(), - currency: this._currency - }; - - if (isValidAddress(this._issuer)) { - amount_json.issuer = this._issuer; - } - - return amount_json; -}; - -Amount.prototype.to_text_full = function() { - if (!this.is_valid()) { - return 'NaN'; - } - return this._is_native - ? this.to_human() + '/XRP' - : this.to_text() + '/' + this._currency + '/' + this._issuer; -}; - -// For debugging. -Amount.prototype.not_equals_why = function(d, ignore_issuer) { - if (typeof d === 'string') { - return this.not_equals_why(Amount.from_json(d)); - } - if (!(d instanceof Amount)) { - return 'Not an Amount'; - } - if (!this.is_valid() || !d.is_valid()) { - return 'Invalid amount.'; - } - if (this._is_native !== d._is_native) { - return 'Native mismatch.'; - } - - const type = this._is_native ? 'XRP' : 'Non-XRP'; - if (!this._value.isZero() && this._value.negate().equals(d._value)) { - return type + ' sign differs.'; - } - if (!this._value.equals(d._value)) { - return type + ' value differs.'; - } - if (!this._is_native) { - if (this._currency !== d._currency) { - return 'Non-XRP currency differs.'; - } - if (!ignore_issuer && this._issuer !== d._issuer) { - return 'Non-XRP issuer differs: ' + d._issuer + '/' + this._issuer; - } - } -}; - -exports.Amount = Amount; -// vim:sw=2:sts=2:ts=8:et diff --git a/src/core/autobridgecalculator.js b/src/core/autobridgecalculator.js deleted file mode 100644 index 97c7fb88..00000000 --- a/src/core/autobridgecalculator.js +++ /dev/null @@ -1,496 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const assert = require('assert'); -const Amount = require('./amount').Amount; -const Utils = require('./orderbookutils'); -const {toHexCurrency} = require('./currency'); - -function assertValidNumber(number, message) { - assert(!_.isNull(number) && !isNaN(number), message); -} - -function assertValidLegOneOffer(legOneOffer, message) { - assert(legOneOffer); - assert.strictEqual(typeof legOneOffer, 'object', message); - assert.strictEqual(typeof legOneOffer.TakerPays, 'object', message); - assertValidNumber(legOneOffer.TakerGets, message); -} - -function AutobridgeCalculator(currencyGets, currencyPays, - legOneOffers, legTwoOffers, issuerGets, issuerPays -) { - this._currencyGets = currencyGets; - this._currencyPays = currencyPays; - this._currencyGetsHex = toHexCurrency(currencyGets); - this._currencyPaysHex = toHexCurrency(currencyPays); - this._issuerGets = issuerGets; - this._issuerPays = issuerPays; - this.legOneOffers = _.cloneDeep(legOneOffers); - this.legTwoOffers = _.cloneDeep(legTwoOffers); - - this._ownerFundsLeftover = {}; -} - -const NULL_AMOUNT = Utils.normalizeAmount('0'); - -/** - * Calculates an ordered array of autobridged offers by quality - * - * @return {Array} - */ - -AutobridgeCalculator.prototype.calculate = function(callback) { - - const legOnePointer = 0; - const legTwoPointer = 0; - - const offersAutobridged = []; - - this.clearOwnerFundsLeftover(); - - this._calculateInternal(legOnePointer, legTwoPointer, offersAutobridged, - callback); -}; - -AutobridgeCalculator.prototype._calculateInternal = function( - legOnePointer_, legTwoPointer_, offersAutobridged, callback -) { - - // Amount class is calling _check_limits after each operation in strict mode, - // and _check_limits is very computationally expensive, so we turning it off - // whle doing calculations - this._oldMode = Amount.strict_mode; - Amount.strict_mode = false; - - let legOnePointer = legOnePointer_; - let legTwoPointer = legTwoPointer_; - - const startTime = Date.now(); - - while (this.legOneOffers[legOnePointer] && this.legTwoOffers[legTwoPointer]) { - // manually implement cooperative multitasking that yields after 30ms - // of execution so user's browser stays responsive - const lasted = (Date.now() - startTime); - if (lasted > 30) { - setTimeout(this._calculateInternal.bind(this, legOnePointer, - legTwoPointer, offersAutobridged, callback), 0); - - Amount.strict_mode = this._oldMode; - return; - } - - const legOneOffer = this.legOneOffers[legOnePointer]; - const legTwoOffer = this.legTwoOffers[legTwoPointer]; - const leftoverFunds = this.getLeftoverOwnerFunds(legOneOffer.Account); - let autobridgedOffer; - - if (legOneOffer.Account === legTwoOffer.Account) { - this.unclampLegOneOwnerFunds(legOneOffer); - } else if (!legOneOffer.is_fully_funded && !leftoverFunds.is_zero()) { - this.adjustLegOneFundedAmount(legOneOffer); - } - - const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer, - this._currencyPays, this._issuerPays); - const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer, - this._currencyGets, this._issuerGets); - - if (legOneTakerGetsFunded.is_zero()) { - legOnePointer++; - - continue; - } - - if (legTwoTakerPaysFunded.is_zero()) { - legTwoPointer++; - - continue; - } - - // using private fields for speed - if (legOneTakerGetsFunded._value.comparedTo( - legTwoTakerPaysFunded._value) > 0) { - autobridgedOffer = this.getAutobridgedOfferWithClampedLegOne( - legOneOffer, - legTwoOffer - ); - - legTwoPointer++; - } else if (legTwoTakerPaysFunded._value.comparedTo( - legOneTakerGetsFunded._value) > 0) { - autobridgedOffer = this.getAutobridgedOfferWithClampedLegTwo( - legOneOffer, - legTwoOffer - ); - - legOnePointer++; - } else { - autobridgedOffer = this.getAutobridgedOfferWithoutClamps( - legOneOffer, - legTwoOffer - ); - - legOnePointer++; - legTwoPointer++; - } - - offersAutobridged.push(autobridgedOffer); - } - - Amount.strict_mode = this._oldMode; - callback(offersAutobridged); -}; - -/** - * In this case, the output from leg one is greater than the input to leg two. - * Therefore, we must effectively clamp leg one output to leg two input. - * - * @param {Object} legOneOffer - * @param {Object} legTwoOffer - * - * @return {Object} - */ - -AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegOne = -function(legOneOffer, legTwoOffer) { - const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer, - this._currencyPays, this._issuerPays); - const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer, - this._currencyGets, this._issuerGets); - const legOneQuality = Utils.getOfferQuality(legOneOffer, - this._currencyPays, this._issuerPays); - - const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer, - this._currencyGets, this._issuerGets); - const autobridgedTakerPays = legTwoTakerPaysFunded.multiply(legOneQuality); - - if (legOneOffer.Account === legTwoOffer.Account) { - const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer, - this._currencyPays, this._issuerPays); - const updatedTakerGets = legOneTakerGets.subtract(legTwoTakerPaysFunded); - - this.setLegOneTakerGets(legOneOffer, updatedTakerGets); - - this.clampLegOneOwnerFunds(legOneOffer); - } else { - // Update funded amount since leg one offer was not completely consumed - const updatedTakerGetsFunded = legOneTakerGetsFunded - .subtract(legTwoTakerPaysFunded); - - this.setLegOneTakerGetsFunded(legOneOffer, updatedTakerGetsFunded); - } - - return this.formatAutobridgedOffer( - autobridgedTakerGets, - autobridgedTakerPays - ); -}; - -/** - * In this case, the input from leg two is greater than the output to leg one. - * Therefore, we must effectively clamp leg two input to leg one output. - * - * @param {Object} legOneOffer - * @param {Object} legTwoOffer - * - * @return {Object} - */ - -AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegTwo = -function(legOneOffer, legTwoOffer) { - const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer, - this._currencyPays, this._issuerPays); - const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer, - 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._issuerPays); - - // Update funded amount since leg two offer was not completely consumed - legTwoOffer.taker_gets_funded = Utils.getOfferTakerGetsFunded(legTwoOffer, - this._currencyGets, this._issuerGets) - .subtract(autobridgedTakerGets) - .to_text(); - legTwoOffer.taker_pays_funded = legTwoTakerPaysFunded - .subtract(legOneTakerGetsFunded) - .to_text(); - - return this.formatAutobridgedOffer( - autobridgedTakerGets, - autobridgedTakerPays - ); -}; - -/** - * In this case, the output from leg one and the input to leg two are the same. - * We do not need to clamp either. - * @param {Object} legOneOffer - * @param {Object} legTwoOffer - * - * @return {Object} - */ - -AutobridgeCalculator.prototype.getAutobridgedOfferWithoutClamps = -function(legOneOffer, legTwoOffer) { - const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer, - this._currencyGets, this._issuerGets); - const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer, - this._currencyPays, this._issuerPays); - - return this.formatAutobridgedOffer( - autobridgedTakerGets, - autobridgedTakerPays - ); -}; - -/** - * Clear owner funds leftovers - */ - -AutobridgeCalculator.prototype.clearOwnerFundsLeftover = function() { - this._ownerFundsLeftover = {}; -}; - -/** - * Reset owner funds leftovers for an account to 0 - * - * @param {String} account - * - * @return {Amount} - */ - -AutobridgeCalculator.prototype.resetOwnerFundsLeftover = function(account) { - this._ownerFundsLeftover[account] = NULL_AMOUNT.clone(); - - return this._ownerFundsLeftover[account]; -}; - -/** - * Retrieve leftover funds found after clamping leg one by account - * - * @param {String} account - * - * @return {Amount} - */ - -AutobridgeCalculator.prototype.getLeftoverOwnerFunds = function(account) { - let amount = this._ownerFundsLeftover[account]; - - if (!amount) { - amount = NULL_AMOUNT.clone(); - } - - return amount; -}; - -/** - * Add funds to account's leftover funds - * - * @param {String} account - * @param {Amount} amount - * - * @return {Amount} - */ - -AutobridgeCalculator.prototype.addLeftoverOwnerFunds = -function(account, amount) { - assert(amount instanceof Amount, 'Amount is invalid'); - - this._ownerFundsLeftover[account] = this.getLeftoverOwnerFunds(account) - .add(amount); - - return this._ownerFundsLeftover[account]; -}; - -/** - * Set account's leftover funds - * - * @param {String} account - * @param {Amount} amount - */ - -AutobridgeCalculator.prototype.setLeftoverOwnerFunds = -function(account, amount) { - assert(amount instanceof Amount, 'Amount is invalid'); - - this._ownerFundsLeftover[account] = amount; -}; - -/** - * Format an autobridged offer and compute synthetic values (e.g. quality) - * - * @param {Amount} takerGets - * @param {Amount} takerPays - * - * @return {Object} - */ - -AutobridgeCalculator.prototype.formatAutobridgedOffer = -function(takerGets, takerPays) { - assert(takerGets instanceof Amount, 'Autobridged taker gets is invalid'); - assert(takerPays instanceof Amount, 'Autobridged taker pays is invalid'); - - const autobridgedOffer = {}; - const quality = takerPays.divide(takerGets); - - autobridgedOffer.TakerGets = { - value: takerGets.to_text(), - currency: this._currencyGetsHex, - issuer: this._issuerGets - }; - - autobridgedOffer.TakerPays = { - value: takerPays.to_text(), - currency: this._currencyPaysHex, - issuer: this._issuerPays - }; - - autobridgedOffer.quality = quality.to_text(); - - autobridgedOffer.taker_gets_funded = autobridgedOffer.TakerGets.value; - autobridgedOffer.taker_pays_funded = autobridgedOffer.TakerPays.value; - - autobridgedOffer.autobridged = true; - - autobridgedOffer.BookDirectory = - Utils.convertOfferQualityToHexFromText(autobridgedOffer.quality); - autobridgedOffer.qualityHex = autobridgedOffer.BookDirectory; - - return autobridgedOffer; -}; - -/** - * Remove funds clamp on leg one offer. This is necessary when the two offers - * are owned by the same account. In this case, it doesn't matter if offer one - * is not fully funded. Leg one out goes to leg two in and since its the same - * account, an infinite amount can flow. - * - * @param {Object} legOneOffer - IOU:XRP offer - */ - -AutobridgeCalculator.prototype.unclampLegOneOwnerFunds = function(legOneOffer) { - assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); - - legOneOffer.initTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer, - this._currencyPays, this._issuerPays); - - this.setLegOneTakerGetsFunded( - legOneOffer, - Utils.getOfferTakerGets(legOneOffer, this._currencyPays, - this._issuerPays) - ); -}; - -/** - * Apply clamp back on leg one offer after a round of autobridge calculation - * completes. We must reapply clamps that have been removed because we cannot - * guarantee that the next offer from leg two will also be from the same - * account. - * - * When we reapply, it could happen that the amount of TakerGets left after - * the autobridge calculation is less than the original funded amount. In this - * case, we have extra funds we can use towards unfunded offers with worse - * quality by the same owner. - * - * @param {Object} legOneOffer - IOU:XRP offer - */ - -AutobridgeCalculator.prototype.clampLegOneOwnerFunds = function(legOneOffer) { - assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); - - const takerGets = Utils.getOfferTakerGets(legOneOffer, this._currencyPays, - this._issuerPays); - - if (takerGets.compareTo(legOneOffer.initTakerGetsFunded) > 0) { - // After clamping, TakerGets is still greater than initial funded amount - this.setLegOneTakerGetsFunded(legOneOffer, legOneOffer.initTakerGetsFunded); - } else { - const updatedLeftover = legOneOffer.initTakerGetsFunded.subtract(takerGets); - - this.setLegOneTakerGetsFunded(legOneOffer, takerGets); - this.addLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover); - } -}; - -/** - * Increase leg one offer funded amount with extra funds found after applying - * clamp. - * - * @param {Object} legOneOffer - IOU:XRP offer - */ - -AutobridgeCalculator.prototype.adjustLegOneFundedAmount = -function(legOneOffer) { - assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); - assert(!legOneOffer.is_fully_funded, 'Leg one offer cannot be fully funded'); - - const fundedSum = Utils.getOfferTakerGetsFunded(legOneOffer, - this._currencyPays, this._issuerPays) - .add(this.getLeftoverOwnerFunds(legOneOffer.Account)); - - if (fundedSum.compareTo(Utils.getOfferTakerGets(legOneOffer, - this._currencyPays, this._issuerPays)) >= 0 - ) { - // There are enough extra funds to fully fund the offer - const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer, - this._currencyPays, this._issuerPays); - const updatedLeftover = fundedSum.subtract(legOneTakerGets); - - this.setLegOneTakerGetsFunded(legOneOffer, legOneTakerGets); - this.setLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover); - } else { - // There are not enough extra funds to fully fund the offer - this.setLegOneTakerGetsFunded(legOneOffer, fundedSum); - this.resetOwnerFundsLeftover(legOneOffer.Account); - } -}; - -/** - * Set taker gets funded amount for a IOU:XRP offer. Also calculates taker - * pays funded using offer quality and updates is_fully_funded flag - * - * @param {Object} legOneOffer - IOU:XRP offer - * @param {Amount} takerGetsFunded - */ - -AutobridgeCalculator.prototype.setLegOneTakerGetsFunded = -function setLegOneTakerGetsFunded(legOneOffer, takerGetsFunded) { - assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid'); - assert(takerGetsFunded instanceof Amount, 'Taker gets funded is invalid'); - - legOneOffer.taker_gets_funded = takerGetsFunded.to_text(); - legOneOffer.taker_pays_funded = takerGetsFunded - .multiply(Utils.getOfferQuality(legOneOffer, - this._currencyPays, this._issuerPays)) - .to_text(); - - if (legOneOffer.taker_gets_funded === legOneOffer.TakerGets.value) { - legOneOffer.is_fully_funded = true; - } -}; - -/** - * Set taker gets amount for a IOU:XRP offer. Also calculates taker pays - * using offer quality - * - * @param {Object} legOneOffer - IOU:XRP offer - * @param {Amount} takerGets - */ - -AutobridgeCalculator.prototype.setLegOneTakerGets = -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._currencyPays, this._issuerPays); - - legOneOffer.TakerGets = takerGets.to_text(); - legOneOffer.TakerPays = takerGets.multiply(legOneQuality).to_json(); -}; - -module.exports = AutobridgeCalculator; diff --git a/src/core/binformat.js b/src/core/binformat.js deleted file mode 100644 index 51929613..00000000 --- a/src/core/binformat.js +++ /dev/null @@ -1,485 +0,0 @@ -'use strict'; - -/*eslint-disable max-len,spaced-comment,array-bracket-spacing,key-spacing*/ -/*eslint-disable no-multi-spaces,comma-spacing*/ -/*eslint-disable no-multi-spaces:0,space-in-brackets:0,key-spacing:0,comma-spacing:0*/ - -/** - * Data type map. - * - * Mapping of type ids to data types. The type id is specified by the high - * - * For reference, see rippled's definition: - * https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol - * /SField.cpp - */ - -exports.types = [ - undefined, - - // Common - 'Int16', // 1 - 'Int32', // 2 - 'Int64', // 3 - 'Hash128', // 4 - 'Hash256', // 5 - 'Amount', // 6 - 'VL', // 7 - 'Account', // 8 - - // 9-13 reserved - undefined, // 9 - undefined, // 10 - undefined, // 11 - undefined, // 12 - undefined, // 13 - - 'Object', // 14 - 'Array', // 15 - - // Uncommon - 'Int8', // 16 - 'Hash160', // 17 - 'PathSet', // 18 - 'Vector256' // 19 -]; - -/** - * Field type map. - * - * Mapping of field type id to field type name. - */ - -const FIELDS_MAP = exports.fields = { - // Common types - 1: { // Int16 - 1: 'LedgerEntryType', - 2: 'TransactionType', - 3: 'SignerWeight' - }, - 2: { // Int32 - 2: 'Flags', - 3: 'SourceTag', - 4: 'Sequence', - 5: 'PreviousTxnLgrSeq', - 6: 'LedgerSequence', - 7: 'CloseTime', - 8: 'ParentCloseTime', - 9: 'SigningTime', - 10: 'Expiration', - 11: 'TransferRate', - 12: 'WalletSize', - 13: 'OwnerCount', - 14: 'DestinationTag', - // Skip 15 - 16: 'HighQualityIn', - 17: 'HighQualityOut', - 18: 'LowQualityIn', - 19: 'LowQualityOut', - 20: 'QualityIn', - 21: 'QualityOut', - 22: 'StampEscrow', - 23: 'BondAmount', - 24: 'LoadFee', - 25: 'OfferSequence', - 26: 'FirstLedgerSequence', - 27: 'LastLedgerSequence', - 28: 'TransactionIndex', - 29: 'OperationLimit', - 30: 'ReferenceFeeUnits', - 31: 'ReserveBase', - 32: 'ReserveIncrement', - 33: 'SetFlag', - 34: 'ClearFlag', - 35: 'SignerQuorum', - 36: 'CancelAfter', - 37: 'FinishAfter', - 38: 'SignerListID' - }, - 3: { // Int64 - 1: 'IndexNext', - 2: 'IndexPrevious', - 3: 'BookNode', - 4: 'OwnerNode', - 5: 'BaseFee', - 6: 'ExchangeRate', - 7: 'LowNode', - 8: 'HighNode' - }, - 4: { // Hash128 - 1: 'EmailHash' - }, - 5: { // Hash256 - 1: 'LedgerHash', - 2: 'ParentHash', - 3: 'TransactionHash', - 4: 'AccountHash', - 5: 'PreviousTxnID', - 6: 'LedgerIndex', - 7: 'WalletLocator', - 8: 'RootIndex', - 9: 'AccountTxnID', - 16: 'BookDirectory', - 17: 'InvoiceID', - 18: 'Nickname', - 19: 'Amendment', - 20: 'TicketID', - 21: 'Digest' - }, - 6: { // Amount - 1: 'Amount', - 2: 'Balance', - 3: 'LimitAmount', - 4: 'TakerPays', - 5: 'TakerGets', - 6: 'LowLimit', - 7: 'HighLimit', - 8: 'Fee', - 9: 'SendMax', - 16: 'MinimumOffer', - 17: 'RippleEscrow', - 18: 'DeliveredAmount' - }, - 7: { // VL - 1: 'PublicKey', - 2: 'MessageKey', - 3: 'SigningPubKey', - 4: 'TxnSignature', - 5: 'Generator', - 6: 'Signature', - 7: 'Domain', - 8: 'FundCode', - 9: 'RemoveCode', - 10: 'ExpireCode', - 11: 'CreateCode', - 12: 'MemoType', - 13: 'MemoData', - 14: 'MemoFormat', - 17: 'Proof' - }, - 8: { // Account - 1: 'Account', - 2: 'Owner', - 3: 'Destination', - 4: 'Issuer', - 7: 'Target', - 8: 'RegularKey' - }, - 14: { // Object - 1: undefined, // end of Object - 2: 'TransactionMetaData', - 3: 'CreatedNode', - 4: 'DeletedNode', - 5: 'ModifiedNode', - 6: 'PreviousFields', - 7: 'FinalFields', - 8: 'NewFields', - 9: 'TemplateEntry', - 10: 'Memo', - 11: 'SignerEntry', - 16: 'Signer' - }, - 15: { // Array - 1: undefined, // end of Array - 2: 'SigningAccounts', - 3: 'Signers', - 4: 'SignerEntries', - 5: 'Template', - 6: 'Necessary', - 7: 'Sufficient', - 8: 'AffectedNodes', - 9: 'Memos' - }, - - // Uncommon types - 16: { // Int8 - 1: 'CloseResolution', - 2: 'Method', - 3: 'TransactionResult' - }, - 17: { // Hash160 - 1: 'TakerPaysCurrency', - 2: 'TakerPaysIssuer', - 3: 'TakerGetsCurrency', - 4: 'TakerGetsIssuer' - }, - 18: { // PathSet - 1: 'Paths' - }, - 19: { // Vector256 - 1: 'Indexes', - 2: 'Hashes', - 3: 'Amendments' - } -}; - -const INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { }; - -Object.keys(FIELDS_MAP).forEach(function(k1) { - Object.keys(FIELDS_MAP[k1]).forEach(function(k2) { - INVERSE_FIELDS_MAP[FIELDS_MAP[k1][k2]] = [Number(k1), Number(k2)]; - }); -}); - -const REQUIRED = exports.REQUIRED = 0; -const OPTIONAL = exports.OPTIONAL = 1; -const DEFAULT = exports.DEFAULT = 2; - -const base = [ - [ 'TransactionType' , REQUIRED ], - [ 'Flags' , OPTIONAL ], - [ 'SourceTag' , OPTIONAL ], - [ 'LastLedgerSequence' , OPTIONAL ], - [ 'Account' , REQUIRED ], - [ 'Sequence' , REQUIRED ], - [ 'Fee' , REQUIRED ], - [ 'OperationLimit' , OPTIONAL ], - [ 'SigningPubKey' , REQUIRED ], - [ 'TxnSignature' , OPTIONAL ], - [ 'AccountTxnID' , OPTIONAL ], - [ 'Memos' , OPTIONAL ], - [ 'Signers' , OPTIONAL ] -]; - -exports.tx = { - AccountSet: [3].concat(base, [ - ['EmailHash' , OPTIONAL], - ['WalletLocator' , OPTIONAL], - ['WalletSize' , OPTIONAL], - ['MessageKey' , OPTIONAL], - ['Domain' , OPTIONAL], - ['TransferRate' , OPTIONAL], - ['SetFlag' , OPTIONAL], - ['ClearFlag' , OPTIONAL] - ]), - TrustSet: [20].concat(base, [ - ['LimitAmount' , OPTIONAL], - ['QualityIn' , OPTIONAL], - ['QualityOut' , OPTIONAL] - ]), - OfferCreate: [7].concat(base, [ - ['TakerPays' , REQUIRED], - ['TakerGets' , REQUIRED], - ['Expiration' , OPTIONAL], - ['OfferSequence' , OPTIONAL] - ]), - OfferCancel: [8].concat(base, [ - ['OfferSequence' , REQUIRED] - ]), - SetRegularKey: [5].concat(base, [ - ['RegularKey' , OPTIONAL] - ]), - Payment: [0].concat(base, [ - ['Destination' , REQUIRED], - ['Amount' , REQUIRED], - ['SendMax' , OPTIONAL], - ['Paths' , DEFAULT], - ['InvoiceID' , OPTIONAL], - ['DestinationTag' , OPTIONAL] - ]), - Contract: [9].concat(base, [ - ['Expiration' , REQUIRED], - ['BondAmount' , REQUIRED], - ['StampEscrow' , REQUIRED], - ['RippleEscrow' , REQUIRED], - ['CreateCode' , OPTIONAL], - ['FundCode' , OPTIONAL], - ['RemoveCode' , OPTIONAL], - ['ExpireCode' , OPTIONAL] - ]), - RemoveContract: [10].concat(base, [ - ['Target' , REQUIRED] - ]), - EnableFeature: [100].concat(base, [ - ['Feature' , REQUIRED] - ]), - EnableAmendment: [100].concat(base, [ - ['Amendment' , REQUIRED] - ]), - SetFee: [101].concat(base, [ - ['BaseFee' , REQUIRED], - ['ReferenceFeeUnits' , REQUIRED], - ['ReserveBase' , REQUIRED], - ['ReserveIncrement' , REQUIRED] - ]), - TicketCreate: [10].concat(base, [ - ['Target' , OPTIONAL], - ['Expiration' , OPTIONAL] - ]), - TicketCancel: [11].concat(base, [ - ['TicketID' , REQUIRED] - ]), - SignerListSet: [12].concat(base, [ - ['SignerQuorum', REQUIRED], - ['SignerEntries', OPTIONAL] - ]), - SuspendedPaymentCreate: [1].concat(base, [ - ['Destination' , REQUIRED], - ['Amount' , REQUIRED], - ['Digest' , OPTIONAL], - ['CancelAfter' , OPTIONAL], - ['FinishAfter' , OPTIONAL], - ['DestinationTag' , OPTIONAL] - ]), - SuspendedPaymentFinish: [2].concat(base, [ - ['Owner' , REQUIRED], - ['OfferSequence' , REQUIRED], - ['Method' , OPTIONAL], - ['Digest' , OPTIONAL], - ['Proof' , OPTIONAL] - ]), - SuspendedPaymentCancel: [4].concat(base, [ - ['Owner' , REQUIRED], - ['OfferSequence' , REQUIRED] - ]) -}; - -const sleBase = [ - ['LedgerIndex', OPTIONAL], - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED] -]; - -exports.ledger = { - AccountRoot: [97].concat(sleBase,[ - ['Sequence', REQUIRED], - ['PreviousTxnLgrSeq', REQUIRED], - ['TransferRate', OPTIONAL], - ['WalletSize', OPTIONAL], - ['OwnerCount', REQUIRED], - ['EmailHash', OPTIONAL], - ['PreviousTxnID', REQUIRED], - ['AccountTxnID', OPTIONAL], - ['WalletLocator', OPTIONAL], - ['Balance', REQUIRED], - ['MessageKey', OPTIONAL], - ['Domain', OPTIONAL], - ['Account', REQUIRED], - ['RegularKey', OPTIONAL]]), - Contract: [99].concat(sleBase,[ - ['PreviousTxnLgrSeq', REQUIRED], - ['Expiration', REQUIRED], - ['BondAmount', REQUIRED], - ['PreviousTxnID', REQUIRED], - ['Balance', REQUIRED], - ['FundCode', OPTIONAL], - ['RemoveCode', OPTIONAL], - ['ExpireCode', OPTIONAL], - ['CreateCode', OPTIONAL], - ['Account', REQUIRED], - ['Owner', REQUIRED], - ['Issuer', REQUIRED]]), - DirectoryNode: [100].concat(sleBase,[ - ['IndexNext', OPTIONAL], - ['IndexPrevious', OPTIONAL], - ['ExchangeRate', OPTIONAL], - ['RootIndex', REQUIRED], - ['Owner', OPTIONAL], - ['TakerPaysCurrency', OPTIONAL], - ['TakerPaysIssuer', OPTIONAL], - ['TakerGetsCurrency', OPTIONAL], - ['TakerGetsIssuer', OPTIONAL], - ['Indexes', REQUIRED]]), - EnabledFeatures: [102].concat(sleBase,[ - ['Features', REQUIRED]]), - FeeSettings: [115].concat(sleBase,[ - ['ReferenceFeeUnits', REQUIRED], - ['ReserveBase', REQUIRED], - ['ReserveIncrement', REQUIRED], - ['BaseFee', REQUIRED], - ['LedgerIndex', OPTIONAL]]), - GeneratorMap: [103].concat(sleBase,[ - ['Generator', REQUIRED]]), - LedgerHashes: [104].concat(sleBase,[ - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['FirstLedgerSequence', OPTIONAL], - ['LastLedgerSequence', OPTIONAL], - ['LedgerIndex', OPTIONAL], - ['Hashes', REQUIRED]]), - Nickname: [110].concat(sleBase,[ - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['MinimumOffer', OPTIONAL], - ['Account', REQUIRED]]), - Offer: [111].concat(sleBase,[ - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['Sequence', REQUIRED], - ['PreviousTxnLgrSeq', REQUIRED], - ['Expiration', OPTIONAL], - ['BookNode', REQUIRED], - ['OwnerNode', REQUIRED], - ['PreviousTxnID', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['BookDirectory', REQUIRED], - ['TakerPays', REQUIRED], - ['TakerGets', REQUIRED], - ['Account', REQUIRED]]), - RippleState: [114].concat(sleBase,[ - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['PreviousTxnLgrSeq', REQUIRED], - ['HighQualityIn', OPTIONAL], - ['HighQualityOut', OPTIONAL], - ['LowQualityIn', OPTIONAL], - ['LowQualityOut', OPTIONAL], - ['LowNode', OPTIONAL], - ['HighNode', OPTIONAL], - ['PreviousTxnID', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['Balance', REQUIRED], - ['LowLimit', REQUIRED], - ['HighLimit', REQUIRED]]), - SignerList: [83].concat(sleBase,[ - ['OwnerNode', REQUIRED], - ['SignerQuorum', REQUIRED], - ['SignerEntries', REQUIRED], - ['SignerListID', REQUIRED], - ['PreviousTxnID', REQUIRED], - ['PreviousTxnLgrSeq', REQUIRED] - ]) -}; - -exports.metadata = [ - ['DeliveredAmount' , OPTIONAL], - ['TransactionIndex' , REQUIRED], - ['TransactionResult' , REQUIRED], - ['AffectedNodes' , REQUIRED] -]; - -exports.ter = { - tesSUCCESS : 0, - tecCLAIM : 100, - tecPATH_PARTIAL : 101, - tecUNFUNDED_ADD : 102, - tecUNFUNDED_OFFER : 103, - tecUNFUNDED_PAYMENT : 104, - tecFAILED_PROCESSING : 105, - tecDIR_FULL : 121, - tecINSUF_RESERVE_LINE : 122, - tecINSUF_RESERVE_OFFER : 123, - tecNO_DST : 124, - tecNO_DST_INSUF_XRP : 125, - tecNO_LINE_INSUF_RESERVE : 126, - tecNO_LINE_REDUNDANT : 127, - tecPATH_DRY : 128, - tecUNFUNDED : 129, // Deprecated, old ambiguous unfunded. - tecNO_ALTERNATIVE_KEY : 130, - tecNO_REGULAR_KEY : 131, - tecOWNERS : 132, - tecNO_ISSUER : 133, - tecNO_AUTH : 134, - tecNO_LINE : 135, - tecINSUFF_FEE : 136, - tecFROZEN : 137, - tecNO_TARGET : 138, - tecNO_PERMISSION : 139, - tecNO_ENTRY : 140, - tecINSUFFICIENT_RESERVE : 141, - tecNEED_MASTER_KEY : 142, - tecDST_TAG_NEEDED : 143, - tecINTERNAL : 144, - tecOVERSIZE : 145 -}; diff --git a/src/core/constants.js b/src/core/constants.js deleted file mode 100644 index 9258f476..00000000 --- a/src/core/constants.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -module.exports = { - ACCOUNT_ZERO: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp', - ACCOUNT_ONE: 'rrrrrrrrrrrrrrrrrrrrBZbvji', - CURRENCY_ZERO: '0000000000000000000000000000000000000000', - CURRENCY_ONE: '0000000000000000000000000000000000000001' -}; diff --git a/src/core/currency.js b/src/core/currency.js deleted file mode 100644 index c278e886..00000000 --- a/src/core/currency.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; -const _ = require('lodash'); - -function isISOCode(currency) { - return /^[A-Z0-9]{3}$/.test(currency); -} - -function isHexCurrency(currency) { - return /[A-Fa-f0-9]{40}/.test(currency); -} - -function getISOCode(hexCurrency) { - const bytes = new Buffer(hexCurrency, 'hex'); - if (_.every(bytes, octet => octet === 0)) { - return 'XRP'; - } - 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; -} - -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'); -} - -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'); -} - -function isValidCurrency(currency) { - return isISOCode(currency.toUpperCase()) || isHexCurrency(currency); -} - -exports.normalizeCurrency = normalizeCurrency; -exports.isValidCurrency = isValidCurrency; -exports.toHexCurrency = toHexCurrency; diff --git a/src/core/index.js b/src/core/index.js deleted file mode 100644 index 2fc17dde..00000000 --- a/src/core/index.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; -exports.Remote = require('./remote').Remote; -exports.Request = require('./request').Request; -exports.Amount = require('./amount').Amount; -exports.Account = require('./account').Account; -exports.Transaction = require('./transaction').Transaction; -exports.Currency = require('./currency').Currency; -exports.Meta = require('./meta').Meta; -exports.RippleError = require('./rippleerror').RippleError; -exports.utils = require('./utils'); -exports.Server = require('./server').Server; - -exports._test = { - Log: require('./log'), - PathFind: require('./pathfind').PathFind, - TransactionManager: require('./transactionmanager').TransactionManager, - TransactionQueue: require('./transactionqueue').TransactionQueue, - RangeSet: require('./rangeset').RangeSet, - OrderbookUtils: require('./orderbookutils'), - constants: require('./constants') -}; diff --git a/src/core/log.js b/src/core/log.js deleted file mode 100644 index 5cbd1a6d..00000000 --- a/src/core/log.js +++ /dev/null @@ -1,193 +0,0 @@ -'use strict'; - -const assert = require('assert'); - -/** - * Logging functionality for ripple-lib and any applications built on it. - * - * @param {String} namespace logging prefix - * @return {Void} this function does not return... - */ -function Log(namespace) { - if (!namespace) { - this._namespace = []; - } else if (Array.isArray(namespace)) { - this._namespace = namespace; - } else { - this._namespace = [String(namespace)]; - } - - this._prefix = this._namespace.concat(['']).join(': '); -} - -/** - * Create a sub-logger. - * - * You can have a hierarchy of loggers. - * - * @example - * - * var log = require('ripple').log.sub('server'); - * - * log.info('connection successful'); - * // prints: 'server: connection successful' - * - * @param {String} namespace logging prefix - * @return {Log} sub logger - */ -Log.prototype.sub = function(namespace) { - const subNamespace = this._namespace.slice(); - - if (namespace && typeof namespace === 'string') { - subNamespace.push(namespace); - } - - const subLogger = new Log(subNamespace); - subLogger._setParent(this); - return subLogger; -}; - -Log.prototype._setParent = function(parentLogger) { - this._parent = parentLogger; -}; - -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(1)])); - }; -}; - -Log.prototype.debug = Log.makeLevel(1); -Log.prototype.info = Log.makeLevel(2); -Log.prototype.warn = Log.makeLevel(3); -Log.prototype.error = Log.makeLevel(4); - -/** - * @param {String} message - * @param {Array} details - * @return {Array} prepared log info - */ - -function getLogInfo(message, args) { - const stack = new Error().stack; - - return [ - // Timestamp - '[' + new Date().toISOString() + ']', - message, - '--', - // Location - (typeof stack === 'string') ? stack.split('\n')[4].replace(/^\s+/, '') : '', - '\n' - ].concat(args); -} - -/** - * @param {Number} log level - * @param {Array} log info - */ - -function logMessage(logLevel, args) { - switch (logLevel) { - case 1: - case 2: - console.log.apply(console, args); - break; - case 3: - console.warn.apply(console, args); - break; - case 4: - console.error.apply(console, args); - break; - } -} - -const engines = {}; - -/** - * Basic logging connector. - * - * This engine has no formatting and works with the most basic of 'console.log' - * implementations. This is the logging engine used in Node.js. - */ -engines.basic = { - logObject: function logObject(level, message, args_) { - const args = args_.map(function(arg) { - return JSON.stringify(arg, null, 2); - }); - - logMessage(level, getLogInfo(message, args)); - } -}; - -/** - * Log engine for browser consoles. - * - * Browsers tend to have better consoles that support nicely formatted - * JavaScript objects. This connector passes objects through to the logging - * function without any stringification. - */ -engines.interactive = { - logObject: function(level, message, args_) { - const args = args_.map(function(arg) { - return /MSIE/.test(navigator.userAgent) - ? JSON.stringify(arg, null, 2) - : arg; - }); - - logMessage(level, getLogInfo(message, args)); - } -}; - -/** - * Null logging connector. - * - * This engine simply swallows all messages. Used when console.log is not - * available. - */ -engines.none = { - logObject: function() {} -}; - -Log.getEngine = Log.prototype.getEngine = function() { - return Log.engine; -}; - -Log.setEngine = Log.prototype.setEngine = function(engine) { - assert.strictEqual(typeof engine, 'object'); - assert.strictEqual(typeof engine.logObject, 'function'); - Log.engine = engine; -}; - -if (typeof window !== 'undefined' && typeof console !== 'undefined') { - Log.setEngine(engines.interactive); -} else if (typeof console !== 'undefined' && console.log) { - Log.setEngine(engines.basic); -} else { - Log.setEngine(engines.none); -} - -/** - * Provide a root logger as our main export. - * - * This means you can use the logger easily on the fly: - * ripple.log.debug('My object is', myObj); - */ -module.exports = new Log(); - -/** - * This is the logger for ripple-lib internally. - */ -module.exports.internal = module.exports.sub(); - -/** - * Expose the class as well. - */ -module.exports.Log = Log; - -/** - * Expose log engines - */ -module.exports.engines = engines; diff --git a/src/core/meta.js b/src/core/meta.js deleted file mode 100644 index 5db4d3e4..00000000 --- a/src/core/meta.js +++ /dev/null @@ -1,262 +0,0 @@ -'use strict'; -const extend = require('extend'); -const utils = require('./utils'); -const Amount = require('./amount').Amount; -const ACCOUNT_ZERO = require('./constants').ACCOUNT_ZERO; -const {isValidAddress} = require('ripple-address-codec'); - -/** - * Meta data processing facility - * - * @constructor - * @param {Object} transaction metadata - */ - -function Meta(data) { - this.nodes = [ ]; - - if (typeof data !== 'object') { - throw new TypeError('Missing metadata'); - } - - if (!Array.isArray(data.AffectedNodes)) { - throw new TypeError('Metadata missing AffectedNodes'); - } - - data.AffectedNodes.forEach(this.addNode, this); -} - -Meta.NODE_TYPES = [ - 'CreatedNode', - 'ModifiedNode', - 'DeletedNode' -]; - -Meta.AMOUNT_FIELDS_AFFECTING_ISSUER = [ - 'LowLimit', - 'HighLimit', - 'TakerPays', - 'TakerGets' -]; - -Meta.ACCOUNT_FIELDS = [ - 'Account', - 'Owner', - 'Destination', - 'Issuer', - 'Target' -]; - -/** - * @param {Object} node - * @api private - */ - -Meta.prototype.getNodeType = function(node) { - let result = null; - - for (let i = 0; i < Meta.NODE_TYPES.length; i++) { - const type = Meta.NODE_TYPES[i]; - if (node.hasOwnProperty(type)) { - result = type; - break; - } - } - - return result; -}; - -/** - * @param {String} field - * @api private - */ - -Meta.prototype.isAccountField = function(field) { - return Meta.ACCOUNT_FIELDS.indexOf(field) !== -1; -}; - -/** - * Add node to metadata - * - * @param {Object} node - * @api private - */ - -Meta.prototype.addNode = function(node) { - this._affectedAccounts = undefined; - this._affectedBooks = undefined; - - const result = { }; - - result.nodeType = this.getNodeType(node); - if (result.nodeType) { - const _node = node[result.nodeType]; - result.diffType = result.nodeType; - result.entryType = _node.LedgerEntryType; - result.ledgerIndex = _node.LedgerIndex; - result.fields = extend({ }, _node.PreviousFields, - _node.NewFields, _node.FinalFields); - result.fieldsPrev = _node.PreviousFields || { }; - result.fieldsNew = _node.NewFields || { }; - result.fieldsFinal = _node.FinalFields || { }; - - // getAffectedBooks will set this - // result.bookKey = undefined; - - this.nodes.push(result); - } -}; - -/** - * Get affected nodes array - * - * @param {Object} filter options - * @return {Array} nodes - */ - -Meta.prototype.getNodes = function(options) { - if (typeof options === 'object') { - return this.nodes.filter(function(node) { - if (options.nodeType && options.nodeType !== node.nodeType) { - return false; - } - if (options.entryType && options.entryType !== node.entryType) { - return false; - } - if (options.bookKey && options.bookKey !== node.bookKey) { - return false; - } - return true; - }); - } - return this.nodes; -}; - -Meta.prototype.getAffectedAccounts = function() { - if (this._affectedAccounts) { - return this._affectedAccounts; - } - - const accounts = [ ]; - - // This code should match the behavior of the C++ method: - // TransactionMetaSet::getAffectedAccounts - for (let i = 0; i < this.nodes.length; i++) { - const node = this.nodes[i]; - const fields = (node.nodeType === 'CreatedNode') - ? node.fieldsNew - : node.fieldsFinal; - - for (const fieldName in fields) { - const field = fields[fieldName]; - - if (this.isAccountField(fieldName) && isValidAddress(field)) { - accounts.push(field); - } else if ( - Meta.AMOUNT_FIELDS_AFFECTING_ISSUER.indexOf(fieldName) !== -1) { - const amount = Amount.from_json(field); - const issuer = amount.issuer(); - if (isValidAddress(issuer) && issuer !== ACCOUNT_ZERO) { - accounts.push(issuer); - } - } - } - } - - this._affectedAccounts = utils.arrayUnique(accounts); - - return this._affectedAccounts; -}; - -Meta.prototype.getAffectedBooks = function() { - if (this._affectedBooks) { - return this._affectedBooks; - } - - const books = [ ]; - - for (let i = 0; i < this.nodes.length; i++) { - const node = this.nodes[i]; - - if (node.entryType !== 'Offer') { - continue; - } - - const gets = Amount.from_json(node.fields.TakerGets); - const pays = Amount.from_json(node.fields.TakerPays); - let getsKey = gets.currency(); - let paysKey = pays.currency(); - - if (getsKey !== 'XRP') { - getsKey += '/' + gets.issuer(); - } - - if (paysKey !== 'XRP') { - paysKey += '/' + pays.issuer(); - } - - const key = getsKey + ':' + paysKey; - - // Hell of a lot of work, so we are going to cache this. We can use this - // later to good effect in OrderBook.notify to make sure we only process - // pertinent offers. - node.bookKey = key; - - books.push(key); - } - - this._affectedBooks = utils.arrayUnique(books); - - return this._affectedBooks; -}; - - -/** - * Execute a function on each affected node. - * - * The callback is passed two parameters. The first is a node object which looks - * like this: - * - * { - * // Type of diff, e.g. CreatedNode, ModifiedNode - * nodeType: 'CreatedNode' - * - * // Type of node affected, e.g. RippleState, AccountRoot - * entryType: 'RippleState', - * - * // Index of the ledger this change occurred in - * ledgerIndex: '01AB01AB...', - * - * // Contains all fields with later versions taking precedence - * // - * // This is a shorthand for doing things like checking which account - * // this affected without having to check the nodeType. - * fields: {...}, - * - * // Old fields (before the change) - * fieldsPrev: {...}, - * - * // New fields (that have been added) - * fieldsNew: {...}, - * - * // Changed fields - * fieldsFinal: {...} - * } - */ - -[ - 'forEach', - 'map', - 'filter', - 'every', - 'some', - 'reduce' -].forEach(function(fn) { - Meta.prototype[fn] = function() { - return Array.prototype[fn].apply(this.nodes, arguments); - }; -}); - -Meta.prototype.each = Meta.prototype.forEach; - -exports.Meta = Meta; diff --git a/src/core/orderbook.js b/src/core/orderbook.js deleted file mode 100644 index 42d2dd27..00000000 --- a/src/core/orderbook.js +++ /dev/null @@ -1,1411 +0,0 @@ -// Routines for working with an orderbook. -// -// One OrderBook object represents one half of an order book. (i.e. bids OR -// asks) Which one depends on the ordering of the parameters. -// -// Events: -// - model -// - trade -// - transaction - -'use strict'; - -const _ = require('lodash'); -const util = require('util'); -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 AutobridgeCalculator = require('./autobridgecalculator'); -const OrderBookUtils = require('./orderbookutils'); -const log = require('./log').internal.sub('orderbook'); -const {IOUValue} = require('ripple-lib-value'); -const RippleError = require('./rippleerror').RippleError; -const {normalizeCurrency, isValidCurrency} = require('./currency'); - -function _sortOffersQuick(a, b) { - return a.qualityHex.localeCompare(b.qualityHex); -} - -/** - * @constructor OrderBook - * @param {Remote} remote - * @param {String} ask currency - * @param {String} ask issuer - * @param {String} bid currency - * @param {String} bid issuer - * @param {String} orderbook key - * @param {Boolean} fire 'model' event after receiving transaction - only once in 10 seconds - */ - -function OrderBook(remote, - currencyGets, issuerGets, currencyPays, issuerPays, key -) { - EventEmitter.call(this); - - const self = this; - - this._remote = remote; - this._currencyGets = normalizeCurrency(currencyGets); - this._issuerGets = issuerGets; - this._currencyPays = normalizeCurrency(currencyPays); - this._issuerPays = issuerPays; - this._key = key; - this._subscribed = false; - this._shouldSubscribe = true; - this._listeners = 0; - this._offers = []; - this._offersAutobridged = []; - this._mergedOffers = []; - this._offerCounts = {}; - this._ownerFundsUnadjusted = {}; - this._ownerFunds = {}; - this._ownerOffersTotal = {}; - this._validAccounts = {}; - this._validAccountsCount = 0; - - // We consider ourselves synced if we have a current - // copy of the offers, we are online and subscribed to updates - this._synced = false; - - // Transfer rate of the taker gets currency issuer - this._issuerTransferRate = null; - - // When orderbook is IOU/IOU, there will be IOU/XRP and XRP/IOU - // books that we must keep track of to compute autobridged offers - this._legOneBook = null; - this._legTwoBook = null; - this._gotOffersFromLegOne = false; - this._gotOffersFromLegTwo = false; - - this._waitingForOffers = false; - this._lastUpdateLedgerSequence = 0; - this._transactionsLeft = 0; - this._calculatorRunning = false; - - - this.sortOffers = _sortOffersQuick; - - this._isAutobridgeable = this._currencyGets !== 'XRP' - && this._currencyPays !== 'XRP'; - - function computeAutobridgedOffersWrapperOne() { - if (!self._gotOffersFromLegOne) { - self._gotOffersFromLegOne = true; - self.computeAutobridgedOffersWrapper(); - } - } - - function computeAutobridgedOffersWrapperTwo() { - if (!self._gotOffersFromLegTwo) { - self._gotOffersFromLegTwo = true; - self.computeAutobridgedOffersWrapper(); - } - } - - function onDisconnect() { - self.resetCache(); - self._gotOffersFromLegOne = false; - self._gotOffersFromLegTwo = false; - if (!self._destroyed) { - self._remote.once('disconnect', onDisconnect); - self._remote.once('connect', function() { - self.subscribe(); - }); - } - } - - if (this._isAutobridgeable) { - this._legOneBook = remote.createOrderBook({ - currency_gets: 'XRP', - currency_pays: currencyPays, - issuer_pays: issuerPays - }); - - this._legTwoBook = remote.createOrderBook({ - currency_gets: currencyGets, - issuer_gets: issuerGets, - currency_pays: 'XRP' - }); - } - - function onTransactionWrapper(transaction) { - self.onTransaction(transaction); - } - - function onLedgerClosedWrapper(message) { - self.onLedgerClosed(message); - } - - function listenersModified(action, event) { - // Automatically subscribe and unsubscribe to orderbook - // on the basis of existing event listeners - if (_.contains(OrderBook.EVENTS, event)) { - switch (action) { - case 'add': - if (++self._listeners === 1) { - self._shouldSubscribe = true; - self.subscribe(); - - self._remote.on('transaction', onTransactionWrapper); - self._remote.on('ledger_closed', onLedgerClosedWrapper); - self._remote.once('disconnect', onDisconnect); - - if (self._isAutobridgeable) { - self._legOneBook.on('model', computeAutobridgedOffersWrapperOne); - self._legTwoBook.on('model', computeAutobridgedOffersWrapperTwo); - } - } - break; - case 'remove': - if (--self._listeners === 0) { - self.unsubscribe(); - } - break; - } - } - } - - this.on('newListener', function(event) { - listenersModified('add', event); - }); - - this.on('removeListener', function(event) { - listenersModified('remove', event); - }); - - this.on('unsubscribe', function() { - self.resetCache(); - - self._remote.removeListener('transaction', onTransactionWrapper); - self._remote.removeListener('ledger_closed', onLedgerClosedWrapper); - self._remote.removeListener('disconnect', onDisconnect); - - self._gotOffersFromLegOne = false; - self._gotOffersFromLegTwo = false; - - if (self._isAutobridgeable) { - self._legOneBook.removeListener('model', - computeAutobridgedOffersWrapperOne); - self._legTwoBook.removeListener('model', - computeAutobridgedOffersWrapperTwo); - } - }); - - return this; -} - -util.inherits(OrderBook, EventEmitter); - -/** - * Events emitted from OrderBook - */ - -OrderBook.EVENTS = [ - 'transaction', 'model', 'trade', - 'offer_added', 'offer_removed', - 'offer_changed', 'offer_funds_changed' -]; - -OrderBook.DEFAULT_TRANSFER_RATE = new IOUValue(1000000000); - -OrderBook.ZERO_NATIVE_AMOUNT = Amount.from_json('0'); - -OrderBook.ZERO_NORMALIZED_AMOUNT = OrderBookUtils.normalizeAmount('0'); - -/** - * Normalize offers from book_offers and transaction stream - * - * @param {Object} offer - * @return {Object} normalized - */ - -OrderBook.offerRewrite = function(offer) { - const result = {}; - const keys = Object.keys(offer); - - for (let i = 0, l = keys.length; i < l; i++) { - const key = keys[i]; - switch (key) { - case 'PreviousTxnID': - case 'PreviousTxnLgrSeq': - break; - default: - result[key] = offer[key]; - } - } - - result.Flags = result.Flags || 0; - result.OwnerNode = result.OwnerNode || new Array(16 + 1).join('0'); - result.BookNode = result.BookNode || new Array(16 + 1).join('0'); - result.qualityHex = result.BookDirectory.slice(-16); - - return result; -}; - -/** - * 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() { - const self = this; - - if (!this._shouldSubscribe || this._destroyed) { - return; - } - - if (this._remote.trace) { - log.info('subscribing', this._key); - } - - const steps = [ - function(callback) { - self.requestTransferRate(callback); - }, - function(callback) { - self.requestOffers(callback, true); - }, - function(callback) { - self.subscribeTransactions(callback); - } - ]; - - async.series(steps); -}; - -/** - * Unhook event listeners and prevent ripple-lib from further work on this - * orderbook. There is no more orderbook stream, so "unsubscribe" is nominal - * @api private - */ - -OrderBook.prototype.unsubscribe = function() { - const self = this; - - if (this._remote.trace) { - log.info('unsubscribing', this._key); - } - - this._subscribed = false; - this._shouldSubscribe = false; - - OrderBook.EVENTS.forEach(function(event) { - if (self.listeners(event).length > 0) { - self.removeAllListeners(event); - } - }); - - this.emit('unsubscribe'); -}; - -/** - * After that you can't use this object. - */ - -OrderBook.prototype.destroy = function() { - this._destroyed = true; - if (this._subscribed) { - this.unsubscribe(); - } - - if (this._remote._books.hasOwnProperty(this._key)) { - delete this._remote._books[this._key]; - } - - if (this._isAutobridgeable) { - this._legOneBook.destroy(); - this._legTwoBook.destroy(); - } -}; - -/** - * 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() && !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 RippleError('Should not request offers')); - return undefined; - } - - if (this._remote.trace) { - log.info('requesting offers', this._key); - } - - this._synced = false; - - if (this._isAutobridgeable && !internal) { - this._gotOffersFromLegOne = false; - this._gotOffersFromLegTwo = false; - - this._legOneBook.requestOffers(); - this._legTwoBook.requestOffers(); - } - - - function handleOffers(res) { - if (self._destroyed) { - return; - } - - self._waitingForOffers = false; - - if (!Array.isArray(res.offers)) { - // XXX What now? - callback(new RippleError('Invalid response')); - self.emit('model', []); - return; - } - - if (self._remote.trace) { - log.info('requested offers', self._key, 'offers: ' + res.offers.length); - } - self.setOffers(res.offers); - - if (self._isAutobridgeable) { - self.computeAutobridgedOffersWrapper(); - } else { - self.emit('model', self._offers); - } - - callback(null, self._offers); - } - - function handleError(err) { - // XXX What now? - if (self._remote.trace) { - log.info('failed to request offers', self._key, err); - } - - self._waitingForOffers = false; - callback(err); - } - - this._waitingForOffers = true; - - const requestOptions = _.merge({}, this.toJSON(), {ledger: 'validated'}); - const request = this._remote.requestBookOffers(requestOptions); - request.once('success', handleOffers); - request.once('error', handleError); - request.request(); - - return request; -}; - -/** - * Request transfer rate for this orderbook's issuer - * - * @param {Function} callback - */ - -OrderBook.prototype.requestTransferRate = function(callback) { - assert.strictEqual(typeof callback, 'function'); - - const self = this; - - if (this._currencyGets === 'XRP') { - // Transfer rate is default for the native currency - this._issuerTransferRate = OrderBook.DEFAULT_TRANSFER_RATE; - - return callback(null, OrderBook.DEFAULT_TRANSFER_RATE); - } - - if (this._issuerTransferRate) { - // Transfer rate has already been cached - return callback(null, this._issuerTransferRate); - } - - function handleAccountInfo(err, info) { - if (err) { - return callback(err); - } - - // When transfer rate is not explicitly set on account, it implies the - // default transfer rate - self._issuerTransferRate = - info.account_data.TransferRate ? - new IOUValue(info.account_data.TransferRate) : - OrderBook.DEFAULT_TRANSFER_RATE; - - callback(null, self._issuerTransferRate); - } - - this._remote.requestAccountInfo( - {account: this._issuerGets}, - handleAccountInfo - ); -}; - -/** - * Subscribe to transactions stream - * - * @param {Function} callback - */ - -OrderBook.prototype.subscribeTransactions = function(callback) { - const self = this; - - if (!this._shouldSubscribe) { - return callback('Should not subscribe'); - } - - if (this._remote.trace) { - log.info('subscribing to transactions'); - } - - function handleSubscribed(res) { - if (self._remote.trace) { - log.info('subscribed to transactions'); - } - - self._subscribed = true; - - callback(null, res); - } - - function handleError(err) { - if (self._remote.trace) { - log.info('failed to subscribe to transactions', self._key, err); - } - - callback(err); - } - - const request = this._remote.requestSubscribe(); - request.addStream('transactions'); - request.once('success', handleSubscribed); - request.once('error', handleError); - request.request(); - - return request; -}; - - -/** - * Reset cached owner's funds, offer counts, and offer sums - */ - -OrderBook.prototype.resetCache = function() { - this._ownerFunds = {}; - this._ownerOffersTotal = {}; - this._offerCounts = {}; - this._synced = false; - this._offers = []; - - if (this._validAccountsCount > 3000) { - this._validAccounts = {}; - this._validAccountsCount = 0; - } -}; - -/** - * Check whether owner's funds have been cached - * - * @param {String} account - owner's account address - */ - -OrderBook.prototype.hasOwnerFunds = function(account) { - return this._ownerFunds[account] !== undefined; -}; - -/** - * Set owner's, transfer rate adjusted, funds in cache - * - * @param {String} account - owner's account address - * @param {String} fundedAmount - */ - -OrderBook.prototype.setOwnerFunds = function(account, fundedAmount) { - assert(!isNaN(fundedAmount), 'Funded amount is invalid'); - - this._ownerFundsUnadjusted[account] = fundedAmount; - this._ownerFunds[account] = this.applyTransferRate(fundedAmount); -}; - -/** - * Compute adjusted balance that would be left after issuer's transfer fee is - * deducted - * - * @param {String} balance - * @return {String} - */ - -OrderBook.prototype.applyTransferRate = function(balance) { - assert(!isNaN(balance), 'Balance is invalid'); - - const adjustedBalance = (new IOUValue(balance)) - .divide(this._issuerTransferRate) - .multiply(OrderBook.DEFAULT_TRANSFER_RATE).toString(); - - return adjustedBalance; -}; - -/** - * Get owner's cached, transfer rate adjusted, funds - * - * @param {String} account - owner's account address - * @return {Amount} - */ - -OrderBook.prototype.getOwnerFunds = function(account) { - if (this.hasOwnerFunds(account)) { - if (this._currencyGets === 'XRP') { - return Amount.from_json(this._ownerFunds[account]); - } - return OrderBookUtils.normalizeAmount(this._ownerFunds[account]); - } -}; - -/** - * Get owner's cached unadjusted funds - * - * @param {String} account - owner's account address - * @return {String} - */ - -OrderBook.prototype.getUnadjustedOwnerFunds = function(account) { - return this._ownerFundsUnadjusted[account]; -}; - -/** - * Remove cached owner's funds - * - * @param {String} account - owner's account address - */ - -OrderBook.prototype.deleteOwnerFunds = function(account) { - this._ownerFunds[account] = undefined; -}; - -/** - * Get offer count for owner - * - * @param {String} account - owner's account address - * @return {Number} - */ - -OrderBook.prototype.getOwnerOfferCount = function(account) { - return this._offerCounts[account] || 0; -}; - -/** - * Increment offer count for owner - * - * @param {String} account - owner's account address - * @return {Number} - */ - -OrderBook.prototype.incrementOwnerOfferCount = function(account) { - const result = (this._offerCounts[account] || 0) + 1; - this._offerCounts[account] = result; - return result; -}; - -/** - * Decrement offer count for owner - * When an account has no more orders, we also stop tracking their account funds - * - * @param {String} account - owner's account address - * @return {Number} - */ - -OrderBook.prototype.decrementOwnerOfferCount = function(account) { - const result = (this._offerCounts[account] || 1) - 1; - this._offerCounts[account] = result; - - if (result < 1) { - this.deleteOwnerFunds(account); - } - - return result; -}; - -/** - * Add amount sum being offered for owner - * - * @param {String} account - owner's account address - * @param {Object|String} amount - offer amount as native string or IOU - * currency format - * @return {Amount} - */ - -OrderBook.prototype.addOwnerOfferTotal = function(account, amount) { - const previousAmount = this.getOwnerOfferTotal(account); - const currentAmount = previousAmount.add(Amount.from_json(amount)); - - this._ownerOffersTotal[account] = currentAmount; - - return currentAmount; -}; - -/** - * Subtract amount sum being offered for owner - * - * @param {String} account - owner's account address - * @param {Object|String} amount - offer amount as native string or IOU - * currency format - * @return {Amount} - */ - -OrderBook.prototype.subtractOwnerOfferTotal = function(account, amount) { - const previousAmount = this.getOwnerOfferTotal(account); - const newAmount = previousAmount.subtract(Amount.from_json(amount)); - - this._ownerOffersTotal[account] = newAmount; - - assert(!newAmount.is_negative(), 'Offer total cannot be negative'); - return newAmount; -}; - -/** - * Get offers amount sum for owner - * - * @param {String} account - owner's account address - * @return {Amount} - */ - -OrderBook.prototype.getOwnerOfferTotal = function(account) { - const amount = this._ownerOffersTotal[account]; - if (amount) { - return amount; - } - if (this._currencyGets === 'XRP') { - return OrderBook.ZERO_NATIVE_AMOUNT.clone(); - } - return OrderBook.ZERO_NORMALIZED_AMOUNT.clone(); -}; - -/** - * Reset offers amount sum for owner to 0 - * - * @param {String} account - owner's account address - * @return {Amount} - */ - -OrderBook.prototype.resetOwnerOfferTotal = function(account) { - if (this._currencyGets === 'XRP') { - this._ownerOffersTotal[account] = OrderBook.ZERO_NATIVE_AMOUNT.clone(); - } else { - this._ownerOffersTotal[account] = OrderBook.ZERO_NORMALIZED_AMOUNT.clone(); - } -}; - -/** - * Set funded amount on offer with its owner's cached funds - * - * is_fully_funded indicates if these funds are sufficient for the offer placed. - * taker_gets_funded indicates the amount this account can afford to offer. - * taker_pays_funded indicates adjusted TakerPays for partially funded offer. - * - * @param {Object} offer - * @return offer - */ - -OrderBook.prototype.setOfferFundedAmount = function(offer) { - assert.strictEqual(typeof offer, 'object', 'Offer is invalid'); - - const takerGets = Amount.from_json(offer.TakerGets); - const fundedAmount = this.getOwnerFunds(offer.Account); - const previousOfferSum = this.getOwnerOfferTotal(offer.Account); - const currentOfferSum = previousOfferSum.add(takerGets); - - offer.owner_funds = this.getUnadjustedOwnerFunds(offer.Account); - - offer.is_fully_funded = fundedAmount.is_comparable(currentOfferSum) && - fundedAmount.compareTo(currentOfferSum) >= 0; - - if (offer.is_fully_funded) { - offer.taker_gets_funded = takerGets.to_text(); - offer.taker_pays_funded = Amount.from_json(offer.TakerPays).to_text(); - } else if (previousOfferSum.compareTo(fundedAmount) < 0) { - offer.taker_gets_funded = fundedAmount.subtract(previousOfferSum).to_text(); - - const quality = OrderBookUtils.getOfferQuality(offer); - const takerPaysFunded = quality.multiply( - OrderBookUtils.getOfferTakerGetsFunded(offer) - ); - - offer.taker_pays_funded = (this._currencyPays === 'XRP') - ? String(Math.floor(takerPaysFunded.to_number())) - : takerPaysFunded.to_json().value; - } else { - offer.taker_gets_funded = '0'; - offer.taker_pays_funded = '0'; - } - - return offer; -}; - -/** - * Get account and final balance of a meta node - * - * @param {Object} node - RippleState or AccountRoot meta node - * @return {Object} - */ - -OrderBook.prototype.parseAccountBalanceFromNode = function(node) { - const result = { - account: undefined, - balance: undefined - }; - - switch (node.entryType) { - case 'AccountRoot': - result.account = node.fields.Account; - result.balance = node.fieldsFinal.Balance; - break; - - case 'RippleState': - if (node.fields.HighLimit.issuer === this._issuerGets) { - result.account = node.fields.LowLimit.issuer; - result.balance = node.fieldsFinal.Balance.value; - } else if (node.fields.LowLimit.issuer === this._issuerGets) { - result.account = node.fields.HighLimit.issuer; - - // Negate balance on the trust line - result.balance = Amount.from_json( - node.fieldsFinal.Balance - ).negate().to_json().value; - } - break; - } - - assert(!isNaN(result.balance), 'node has an invalid balance'); - if (this._validAccounts[result.Account] === undefined) { - assert(isValidAddress(result.account), 'node has an invalid account'); - this._validAccounts[result.Account] = true; - this._validAccountsCount++; - } - - return result; -}; - -/** - * Check that affected meta node represents a balance change - * - * @param {Object} node - RippleState or AccountRoot meta node - * @return {Boolean} - */ - -OrderBook.prototype.isBalanceChangeNode = function(node) { - // Check meta node has balance, previous balance, and final balance - if (!(node.fields && node.fields.Balance - && node.fieldsPrev && node.fieldsFinal - && node.fieldsPrev.Balance && node.fieldsFinal.Balance)) { - return false; - } - - // Check if taker gets currency is native and balance is not a number - 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) { - return false; - } - - // Check if trustline does not refer to the taker gets currency issuer - if (!(node.fields.HighLimit.issuer === this._issuerGets - || node.fields.LowLimit.issuer === this._issuerGets)) { - return false; - } - - return true; -}; - -OrderBook.prototype._canRunAutobridgeCalc = function(): boolean { - return !this._calculatorRunning; -}; - -OrderBook.prototype.onTransaction = function(transaction) { - this.updateFundedAmounts(transaction); - - - if (--this._transactionsLeft === 0 && !this._waitingForOffers) { - const lastClosedLedger = this._remote.getLedgerSequenceSync(); - if (this._isAutobridgeable) { - if (this._canRunAutobridgeCalc()) { - if (this._legOneBook._lastUpdateLedgerSequence === lastClosedLedger || - this._legTwoBook._lastUpdateLedgerSequence === lastClosedLedger - ) { - this.computeAutobridgedOffersWrapper(); - } else if (this._lastUpdateLedgerSequence === lastClosedLedger) { - this.mergeDirectAndAutobridgedBooks(); - } - } - } else if (this._lastUpdateLedgerSequence === lastClosedLedger) { - this.emit('model', this._offers); - } - } -}; - -/** - * Updates funded amounts/balances using modified balance nodes - * - * Update owner funds using modified AccountRoot and RippleState nodes - * Update funded amounts for offers in the orderbook using owner funds - * - * @param {Object} transaction - transaction that holds meta nodes - */ - -OrderBook.prototype.updateFundedAmounts = function(transaction) { - const self = this; - - if (this._currencyGets !== 'XRP' && !this._issuerTransferRate) { - if (this._remote.trace) { - log.info('waiting for transfer rate'); - } - - this.requestTransferRate(function(err) { - if (err) { - log.error( - 'Failed to request transfer rate, will not update funded amounts: ' - + err.toString()); - } else { - // Defer until transfer rate is requested - self.updateFundedAmounts(transaction); - } - }); - return; - } - - const affectedNodes = transaction.mmeta.getNodes({ - nodeType: 'ModifiedNode', - entryType: this._currencyGets === 'XRP' ? 'AccountRoot' : 'RippleState' - }); - - _.each(affectedNodes, function(node) { - if (self.isBalanceChangeNode(node)) { - const result = self.parseAccountBalanceFromNode(node); - - if (self.hasOwnerFunds(result.account)) { - // We are only updating owner funds that are already cached - self.setOwnerFunds(result.account, result.balance); - - self.updateOwnerOffersFundedAmount(result.account); - } - } - }); -}; - - -/** - * Update offers' funded amount with their owner's funds - * - * @param {String} account - owner's account address - */ - -OrderBook.prototype.updateOwnerOffersFundedAmount = function(account) { - const self = this; - - if (!this.hasOwnerFunds(account)) { - // We are only updating owner funds that are already cached - return; - } - - if (this._remote.trace) { - const ownerFunds = this.getOwnerFunds(account); - log.info('updating offer funds', this._key, account, - ownerFunds ? ownerFunds.to_text() : 'undefined'); - } - - this.resetOwnerOfferTotal(account); - - _.each(this._offers, function(offer) { - if (offer.Account !== account) { - return; - } - - // Save a copy of the old offer so we can show how the offer has changed - const previousOffer = extend({}, offer); - let previousFundedGets; - - if (_.isString(offer.taker_gets_funded)) { - // Offer is not new, so we should consider it for offer_changed and - // offer_funds_changed events - previousFundedGets = OrderBookUtils.getOfferTakerGetsFunded(offer); - } - - self.setOfferFundedAmount(offer); - self.addOwnerOfferTotal(offer.Account, offer.TakerGets); - - const takerGetsFunded = OrderBookUtils.getOfferTakerGetsFunded(offer); - const areFundsChanged = previousFundedGets - && !takerGetsFunded.equals(previousFundedGets); - - if (areFundsChanged) { - self.emit('offer_changed', previousOffer, offer); - self.emit('offer_funds_changed', - offer, - previousOffer.taker_gets_funded, - offer.taker_gets_funded - ); - } - }); -}; - - -OrderBook.prototype.onLedgerClosed = function(message) { - if (!message || (message && !_.isNumber(message.txn_count)) || - !this._subscribed || this._destroyed || this._waitingForOffers - ) { - return; - } - this._transactionsLeft = message.txn_count; -}; - -/** - * Notify orderbook of a relevant transaction - * - * @param {Object} transaction - * @api private - */ - -OrderBook.prototype.notify = function(transaction) { - const self = this; - - if (!(this._subscribed && this._synced) || this._destroyed) { - return; - } - - if (this._remote.trace) { - log.info('notifying', this._key, transaction.transaction.hash); - } - - const affectedNodes = transaction.mmeta.getNodes({ - entryType: 'Offer', - bookKey: this._key - }); - - if (affectedNodes.length < 1) { - return; - } - - let takerGetsTotal = Amount.from_json( - '0' + (this._currencyGets === 'XRP' - ? '' - : ('/' + this._currencyGets + '/' + this._issuerGets)) - ); - - let takerPaysTotal = Amount.from_json( - '0' + (this._currencyPays === 'XRP' ? '' - : ('/' + this._currencyPays + '/' + this._issuerPays)) - ); - - const isOfferCancel = - transaction.transaction.TransactionType === 'OfferCancel'; - const transactionOwnerFunds = transaction.transaction.owner_funds; - - function handleNode(node) { - switch (node.nodeType) { - case 'DeletedNode': - if (self._validAccounts[node.fields.Account] === undefined) { - assert(isValidAddress(node.fields.Account), - 'node has an invalid account'); - self._validAccounts[node.fields.Account] = true; - self._validAccountsCount++; - } - self.deleteOffer(node, isOfferCancel); - - // We don't want to count an OfferCancel as a trade - if (!isOfferCancel) { - takerGetsTotal = takerGetsTotal.add(node.fieldsFinal.TakerGets); - takerPaysTotal = takerPaysTotal.add(node.fieldsFinal.TakerPays); - } - break; - - case 'ModifiedNode': - if (self._validAccounts[node.fields.Account] === undefined) { - assert(isValidAddress(node.fields.Account), - 'node has an invalid account'); - self._validAccounts[node.fields.Account] = true; - self._validAccountsCount++; - } - self.modifyOffer(node); - - takerGetsTotal = takerGetsTotal - .add(node.fieldsPrev.TakerGets) - .subtract(node.fieldsFinal.TakerGets); - - takerPaysTotal = takerPaysTotal - .add(node.fieldsPrev.TakerPays) - .subtract(node.fieldsFinal.TakerPays); - break; - - case 'CreatedNode': - if (self._validAccounts[node.fields.Account] === undefined) { - assert(isValidAddress(node.fields.Account), - 'node has an invalid account'); - self._validAccounts[node.fields.Account] = true; - self._validAccountsCount++; - } - // rippled does not set owner_funds if the order maker is the issuer - // because the value would be infinite - const fundedAmount = transactionOwnerFunds !== undefined ? - transactionOwnerFunds : Infinity; - self.setOwnerFunds(node.fields.Account, fundedAmount); - self.insertOffer(node); - break; - } - } - - _.each(affectedNodes, handleNode); - - this.emit('transaction', transaction); - - this._lastUpdateLedgerSequence = this._remote.getLedgerSequenceSync(); - - if (!takerGetsTotal.is_zero()) { - this.emit('trade', takerPaysTotal, takerGetsTotal); - } -}; - -/** - * Insert an offer into the orderbook - * - * NOTE: We *MUST* update offers' funded amounts when a new offer is placed - * because funds go to the highest quality offers first. - * - * @param {Object} node - Offer node - */ - -OrderBook.prototype.insertOffer = function(node) { - if (this._remote.trace) { - log.info('inserting offer', this._key, node.fields); - } - - const offer = OrderBook.offerRewrite(node.fields); - const takerGets = this.normalizeAmount(this._currencyGets, offer.TakerGets); - const takerPays = this.normalizeAmount(this._currencyPays, offer.TakerPays); - - offer.LedgerEntryType = node.entryType; - offer.index = node.ledgerIndex; - - // We're safe to calculate quality for newly created offers - offer.quality = takerPays.divide(takerGets).to_text(); - - const originalLength = this._offers.length; - - for (let i = 0; i < originalLength; i++) { - if (offer.qualityHex <= this._offers[i].qualityHex) { - this._offers.splice(i, 0, offer); - break; - } - } - - if (this._offers.length === originalLength) { - this._offers.push(offer); - } - - this.incrementOwnerOfferCount(offer.Account); - - this.updateOwnerOffersFundedAmount(offer.Account); - - this.emit('offer_added', offer); -}; - -/** - * Convert any amount into default IOU - * - * NOTE: This is necessary in some places because Amount.js arithmetic - * does not deal with native and non-native amounts well. - * - * @param {Currency} currency - * @param {Object} amountObj - */ - -OrderBook.prototype.normalizeAmount = function(currency, amountObj) { - const value = (currency === 'XRP') ? amountObj : amountObj.value; - return OrderBookUtils.normalizeAmount(value); -}; - -/** - * Modify an existing offer in the orderbook - * - * @param {Object} node - Offer node - */ - -OrderBook.prototype.modifyOffer = function(node) { - if (this._remote.trace) { - log.info('modifying offer', this._key, node.fields); - } - - for (let i = 0; i < this._offers.length; i++) { - const offer = this._offers[i]; - - if (offer.index === node.ledgerIndex) { - // TODO: This assumes no fields are deleted, which is - // probably a safe assumption, but should be checked. - extend(offer, node.fieldsFinal); - - break; - } - } - - this.updateOwnerOffersFundedAmount(node.fields.Account); -}; - -/** - * Delete an existing offer in the orderbook - * - * NOTE: We only update funded amounts when the node comes from an OfferCancel - * transaction because when offers are deleted, it frees up funds to fund - * other existing offers in the book - * - * @param {Object} node - Offer node - * @param {Boolean} isOfferCancel - whether node came from an OfferCancel - */ - -OrderBook.prototype.deleteOffer = function(node, isOfferCancel) { - if (this._remote.trace) { - log.info('deleting offer', this._key, node.fields); - } - - for (let i = 0; i < this._offers.length; i++) { - const offer = this._offers[i]; - - if (offer.index === node.ledgerIndex) { - // Remove offer amount from sum for account - this.subtractOwnerOfferTotal(offer.Account, offer.TakerGets); - - this._offers.splice(i, 1); - this.decrementOwnerOfferCount(offer.Account); - - this.emit('offer_removed', offer); - - break; - } - } - - if (isOfferCancel) { - this.updateOwnerOffersFundedAmount(node.fields.Account); - } -}; - -/** - * Reset internal offers cache from book_offers request - * - * @param {Array} offers - * @api private - */ - -OrderBook.prototype.setOffers = function(offers) { - assert(Array.isArray(offers), 'Offers is not an array'); - - this.resetCache(); - - let i = -1; - let offer; - const l = offers.length; - - while (++i < l) { - offer = OrderBook.offerRewrite(offers[i]); - - if (this._validAccounts[offer.Account] === undefined) { - assert(isValidAddress(offer.Account), 'Account is invalid'); - this._validAccounts[offer.Account] = true; - this._validAccountsCount++; - } - if (offer.owner_funds !== undefined) { - // The first offer of each owner from book_offers contains owner balance - // of offer's output - this.setOwnerFunds(offer.Account, offer.owner_funds); - } - - this.incrementOwnerOfferCount(offer.Account); - - this.setOfferFundedAmount(offer); - this.addOwnerOfferTotal(offer.Account, offer.TakerGets); - offers[i] = offer; - } - - this._offers = offers; - this._synced = true; -}; - -/** - * Get offers model asynchronously - * - * This function takes a callback and calls it with an array containing the - * current set of offers in this order book. - * - * If the data is available immediately, the callback may be called - * synchronously - * - * @param {Function} callback - */ - -OrderBook.prototype.offers = -OrderBook.prototype.getOffers = function(callback) { - assert.strictEqual(typeof callback, 'function', 'Callback missing'); - - if (this._synced) { - callback(null, this._offers); - } else { - this.once('model', function(m) { - callback(null, m); - }); - } - - return this; -}; - -/** - * Return latest known offers - * - * Usually, this will just be an empty array if the order book hasn't been - * loaded yet. But this accessor may be convenient in some circumstances. - * - * @return {Array} offers - */ - -OrderBook.prototype.offersSync = -OrderBook.prototype.getOffersSync = function() { - return this._offers; -}; - -/** - * Get request-representation of orderbook - * - * @return {Object} json - */ - -OrderBook.prototype.toJSON = -OrderBook.prototype.to_json = function() { - const json = { - taker_gets: { - currency: this._currencyGets - }, - taker_pays: { - currency: this._currencyPays - } - }; - - if (this._currencyGets !== 'XRP') { - json.taker_gets.issuer = this._issuerGets; - } - - if (this._currencyPays !== 'XRP') { - json.taker_pays.issuer = this._issuerPays; - } - - return json; -}; - -/** - * Whether the OrderBook is valid - * - * Note: This only checks whether the parameters (currencies and issuer) are - * syntactically valid. It does not check anything against the ledger. - * - * @return {Boolean} is valid - */ - -OrderBook.prototype.isValid = -OrderBook.prototype.is_valid = function() { - // XXX Should check for same currency (non-native) && same issuer - return ( - 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') - ); -}; - -/** - * Compute autobridged offers for an IOU:IOU orderbook by merging offers from - * IOU:XRP and XRP:IOU books - */ - -OrderBook.prototype.computeAutobridgedOffers = function(callback = function() {} -) { - assert(this._currencyGets !== 'XRP' && this._currencyPays !== 'XRP', - 'Autobridging is only for IOU:IOU orderbooks'); - - - if (this._destroyed) { - return; - } - - const autobridgeCalculator = new AutobridgeCalculator( - this._currencyGets, - this._currencyPays, - this._legOneBook.getOffersSync(), - this._legTwoBook.getOffersSync(), - this._issuerGets, - this._issuerPays - ); - - autobridgeCalculator.calculate((autobridgedOffers) => { - this._offersAutobridged = autobridgedOffers; - callback(); - }); -}; - -OrderBook.prototype.computeAutobridgedOffersWrapper = function() { - if (!this._gotOffersFromLegOne || !this._gotOffersFromLegTwo || - !this._synced || this._destroyed || this._calculatorRunning - ) { - return; - } - - this._calculatorRunning = true; - this.computeAutobridgedOffers(() => { - this.mergeDirectAndAutobridgedBooks(); - this._calculatorRunning = false; - }); -}; - -/** - * Merge direct and autobridged offers into a combined orderbook - * - * @return [Array] - */ - -OrderBook.prototype.mergeDirectAndAutobridgedBooks = function() { - - if (this._destroyed) { - return; - } - - if (_.isEmpty(this._offers) && _.isEmpty(this._offersAutobridged)) { - if (this._synced && this._gotOffersFromLegOne && - this._gotOffersFromLegTwo) { - // emit empty model to indicate to listeners that we've got offers, - // just there was no one - this.emit('model', []); - } - return; - } - - this._mergedOffers = this._offers - .concat(this._offersAutobridged) - .sort(this.sortOffers); - - this.emit('model', this._mergedOffers); -}; - -exports.OrderBook = OrderBook; diff --git a/src/core/orderbookutils.js b/src/core/orderbookutils.js deleted file mode 100644 index 9a0e7118..00000000 --- a/src/core/orderbookutils.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const assert = require('assert'); -const constants = require('./constants'); -const Amount = require('./amount').Amount; -const {IOUValue} = require('ripple-lib-value'); -const binary = require('ripple-binary-codec'); -const OrderBookUtils = {}; - -function assertValidNumber(number, message) { - assert(!_.isNull(number) && !isNaN(number), message); -} - -/** -* Creates a new Amount from a JSON amount object using -* passed parameters for value, currency and counterparty -* -* @param amount of value, currency, counterparty -* @return JSON amount object -*/ - -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); -} - -/** -* Gets currency for getOfferTaker(Gets/Pays)Funded -* @param offer -* @return currency -*/ - -function getCurrencyFromOffer(offer) { - return offer.TakerPays.currency || offer.TakerGets.currency; -} - -/** -* Gets issuer for getOfferTaker(Gets/Pays)Funded -* @param offer -* @return issuer -*/ - -function getIssuerFromOffer(offer) { - return offer.TakerPays.issuer || offer.TakerGets.issuer; -} - -/** - * Casts and returns offer's taker gets funded amount as a default IOU amount - * - * @param {Object} offer - * @return {Amount} - */ - -OrderBookUtils.getOfferTakerGetsFunded = function(offer, currency_, issuer_) { - assertValidNumber(offer.taker_gets_funded, 'Taker gets funded is invalid'); - - const currency = currency_ || getCurrencyFromOffer(offer); - const issuer = issuer_ || getIssuerFromOffer(offer); - - return createAmount(offer.taker_gets_funded, currency, issuer); -}; - -/** - * Casts and returns offer's taker pays funded amount as a default IOU amount - * - * @param {Object} offer - * @return {Amount} - */ - -OrderBookUtils.getOfferTakerPaysFunded = function(offer, currency_, issuer_) { - assertValidNumber(offer.taker_pays_funded, 'Taker gets funded is invalid'); - - const currency = currency_ || getCurrencyFromOffer(offer); - const issuer = issuer_ || getIssuerFromOffer(offer); - - return createAmount(offer.taker_pays_funded, currency, issuer); -}; - -/** - * Get offer taker gets amount - * - * @param {Object} offer - * - * @return {Amount} - */ - -OrderBookUtils.getOfferTakerGets = function(offer, currency_, issuer_) { - assert(typeof offer, 'object', 'Offer is invalid'); - - const currency = currency_ || offer.TakerPays.currency; - const issuer = issuer_ || offer.TakerPays.issuer; - - return createAmount(offer.TakerGets, currency, issuer); -}; - -/** - * Retrieve offer quality - * - * @param {Object} offer - * @param {Currency} currencyGets - */ - -OrderBookUtils.getOfferQuality = function(offer, currency_, issuer_) { - const currency = currency_ || getCurrencyFromOffer(offer); - const issuer = issuer_ || getIssuerFromOffer(offer); - return createAmount(offer.quality, currency, issuer); -}; - -/** - * Formats an offer quality amount to a hex that can be parsed by - * Amount.parse_quality - * - * @param {Amount} quality - * - * @return {String} - */ - -OrderBookUtils.convertOfferQualityToHex = function(quality) { - assert(quality instanceof Amount, 'Quality is not an amount'); - return OrderBookUtils.convertOfferQualityToHex(quality.to_text()); -}; - -/** - * Formats an offer quality amount to a hex that can be parsed by - * Amount.parse_quality - * - * @param {String} quality - * - * @return {String} - */ - -OrderBookUtils.convertOfferQualityToHexFromText = function(quality) { - return binary.encodeQuality(quality); -}; - -OrderBookUtils.CURRENCY_ONE = constants.CURRENCY_ONE; - -OrderBookUtils.ISSUER_ONE = constants.ACCOUNT_ONE; - -/** - * - */ - -OrderBookUtils.normalizeAmount = function(value) { - return Amount.from_components_unsafe(new IOUValue(value), - OrderBookUtils.CURRENCY_ONE, OrderBookUtils.ISSUER_ONE, false); -}; - -module.exports = OrderBookUtils; diff --git a/src/core/pathfind.js b/src/core/pathfind.js deleted file mode 100644 index 1d646f51..00000000 --- a/src/core/pathfind.js +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const Amount = require('./amount').Amount; - -/** - * Represents a persistent path finding request. - * - * Only one path find request is allowed per connection, so when another path - * find request is triggered it will supercede the existing one, making it emit - * the 'end' and 'superceded' events. - */ - -function PathFind(remote, src_account, dst_account, dst_amount, - src_currencies, src_amount -) { - EventEmitter.call(this); - - this.remote = remote; - - this.src_account = src_account; - this.dst_account = dst_account; - this.dst_amount = dst_amount; - this.src_currencies = src_currencies; - this.src_amount = src_amount; -} - -util.inherits(PathFind, EventEmitter); - -/** - * Submits a path_find_create request to the network. - * - * This starts a path find request, superceding all previous path finds. - * - * This will be called automatically by Remote when this object is instantiated, - * so you should only have to call it if the path find was closed or superceded - * and you wish to restart it. - */ - -PathFind.prototype.create = function() { - const self = this; - - const req = this.remote.requestPathFindCreate({ - source_account: this.src_account, - destination_account: this.dst_account, - destination_amount: this.dst_amount, - source_currencies: this.src_currencies, - send_max: this.src_amount - }); - - req.once('error', function(err) { - self.emit('error', err); - }); - req.once('success', function(msg) { - self.notify_update(msg); - }); - - // XXX We should add ourselves to prepare_subscribe or a similar mechanism so - // that we can resubscribe after a reconnection. - - req.broadcast().request(); -}; - -PathFind.prototype.close = function() { - this.removeAllListeners('update'); - this.remote.requestPathFindClose().broadcast().request(); - this.emit('end'); - this.emit('close'); -}; - -PathFind.prototype.notify_update = function(message) { - const src_account = message.source_account; - const dst_account = message.destination_account; - const dst_amount = Amount.from_json(message.destination_amount); - - // Only pass the event along if this path find response matches what we were - // looking for. - if (this.src_account === src_account && - this.dst_account === dst_account && - dst_amount.equals(this.dst_amount)) { - this.emit('update', message); - } -}; - -PathFind.prototype.notify_superceded = function() { - // XXX If we're set to re-subscribe whenever we connect to a new server, then - // we should cancel that behavior here. See PathFind#create. - - this.emit('end'); - this.emit('superceded'); -}; - -exports.PathFind = PathFind; diff --git a/src/core/rangeset.js b/src/core/rangeset.js deleted file mode 100644 index d2141af6..00000000 --- a/src/core/rangeset.js +++ /dev/null @@ -1,61 +0,0 @@ -/* @flow */ -'use strict'; -const _ = require('lodash'); -const assert = require('assert'); -const ranges = Symbol(); - -function mergeIntervals(intervals: Array<[number, number]>) { - const stack = [[-Infinity, -Infinity]]; - _.forEach(_.sortBy(intervals, x => x[0]), interval => { - const lastInterval = stack.pop(); - if (interval[0] <= lastInterval[1] + 1) { - stack.push([lastInterval[0], Math.max(interval[1], lastInterval[1])]); - } else { - stack.push(lastInterval); - stack.push(interval); - } - }); - return stack.slice(1); -} - -class RangeSet { - constructor() { - this.reset(); - } - - reset() { - this[ranges] = []; - } - - serialize() { - return this[ranges].map(range => - range[0].toString() + '-' + range[1].toString()).join(','); - } - - addRange(start: number, end: number) { - assert(start <= end, 'invalid range'); - this[ranges] = mergeIntervals(this[ranges].concat([[start, end]])); - } - - addValue(value: number) { - this.addRange(value, value); - } - - parseAndAddRanges(rangesString: string) { - const rangeStrings = rangesString.split(','); - _.forEach(rangeStrings, rangeString => { - const range = rangeString.split('-').map(Number); - this.addRange(range[0], range.length === 1 ? range[0] : range[1]); - }); - } - - containsRange(start: number, end: number) { - return _.some(this[ranges], range => range[0] <= start && range[1] >= end); - } - - containsValue(value: number) { - return this.containsRange(value, value); - } -} - -exports.RangeSet = RangeSet; diff --git a/src/core/remote.js b/src/core/remote.js deleted file mode 100644 index 32603dd0..00000000 --- a/src/core/remote.js +++ /dev/null @@ -1,2473 +0,0 @@ -'use strict'; - -// Interface to manage connections to rippled servers -// -// - We never send binary data. -// - We use the W3C interface for node and browser compatibility: -// http://www.w3.org/TR/websockets/#the-websocket-interface -// -// This class is intended for both browser and Node.js use. -// -// This class is designed to work via peer protocol via either the public or -// private WebSocket interfaces. The JavaScript class for the peer protocol -// has not yet been implemented. However, this class has been designed for it -// to be a very simple drop option. - -const util = require('util'); -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 {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 RippleError = require('./rippleerror').RippleError; -const utils = require('./utils'); -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; - -/** - * Interface to manage connections to rippled servers - * - * @param {Object} Options - */ - -function Remote(options = {}) { - EventEmitter.call(this); - - const self = this; - - _.merge(this, _.defaults(options, Remote.DEFAULTS)); - - this.state = 'offline'; // 'online', 'offline' - this._server_fatal = false; // server exited - this._stand_alone = undefined; - this._testnet = undefined; - - this._ledger_current_index = undefined; - this._ledger_hash = undefined; - this._ledger_time = undefined; - - this._connection_count = 0; - this._connected = false; - this._should_connect = true; - - this._transaction_listeners = 0; - this._received_tx = new LRU({max: 100}); - this._cur_path_find = null; - this._queued_path_finds = []; - - if (this.local_signing) { - // Local signing implies local fees and sequences - this.local_sequence = true; - this.local_fee = true; - } - - this._servers = []; - this._primary_server = undefined; - - // Cache information for accounts. - // DEPRECATED, will be removed - // Consider sequence numbers stable if you know you're not generating bad - // transactions. - // Otherwise, clear it to have it automatically refreshed from the network. - // account : { seq : __ } - this.accounts = { }; - - // Account objects by AccountId. - this._accounts = { }; - - // OrderBook objects - this._books = { }; - - // Secrets that we know about. - // Secrets can be set by calling setSecret(account, secret). - // account : secret - this.secrets = { }; - - // Cache for various ledgers. - // XXX Clear when ledger advances. - this.ledgers = { - current: { - account_root: { } - } - }; - - if (typeof this.trusted !== 'boolean') { - throw new TypeError('trusted must be a boolean'); - } - if (typeof this.trace !== 'boolean') { - throw new TypeError('trace must be a boolean'); - } - if (typeof this.allow_partial_history !== 'boolean') { - throw new TypeError('allow_partial_history must be a boolean'); - } - if (typeof this.max_fee !== 'number') { - throw new TypeError('max_fee must be a number'); - } - if (typeof this.max_attempts !== 'number') { - throw new TypeError('max_attempts must be a number'); - } - if (typeof this.fee_cushion !== 'number') { - throw new TypeError('fee_cushion must be a number'); - } - if (typeof this.local_signing !== 'boolean') { - throw new TypeError('local_signing must be a boolean'); - } - if (typeof this.local_fee !== 'boolean') { - throw new TypeError('local_fee must be a boolean'); - } - if (typeof this.local_sequence !== 'boolean') { - throw new TypeError('local_sequence must be a boolean'); - } - if (typeof this.canonical_signing !== 'boolean') { - throw new TypeError('canonical_signing must be a boolean'); - } - 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'); - } - if (typeof this.last_ledger_offset !== 'number') { - throw new TypeError('last_ledger_offset must be a number'); - } - if (!Array.isArray(this.servers)) { - throw new TypeError('servers must be an array'); - } - if (!(_.isUndefined(this.basic_auth) || _.isString(this.basic_auth))) { - throw new TypeError('basic_auth must be a string'); - } - - this.setMaxListeners(this.max_listeners); - - this.servers.forEach(function(serverOptions) { - const server = self.addServer(serverOptions); - server.setMaxListeners(self.max_listeners); - }); - - function listenersModified(action, event) { - // Automatically subscribe and unsubscribe to orderbook - // on the basis of existing event listeners - if (_.contains(Remote.TRANSACTION_EVENTS, event)) { - switch (action) { - case 'add': - if (++self._transaction_listeners === 1) { - self.requestSubscribe('transactions').request(); - } - break; - case 'remove': - if (--self._transaction_listeners === 0) { - self.requestUnsubscribe('transactions').request(); - } - break; - } - } - } - - this.on('newListener', function(event) { - listenersModified('add', event); - }); - - this.on('removeListener', function(event) { - listenersModified('remove', event); - }); -} - -util.inherits(Remote, EventEmitter); - -Remote.DEFAULTS = { - trusted: false, - trace: false, - allow_partial_history: true, - local_sequence: true, - local_fee: true, - local_signing: true, - canonical_signing: true, - fee_cushion: 1.2, - max_fee: 1000000, // 1 XRP - max_attempts: 10, - submission_timeout: 1000 * 20, - pathfind_timeout: 1000 * 10, - automatic_resubmission: true, - last_ledger_offset: 3, - servers: [], - max_listeners: 0 // remove Node EventEmitter warnings -}; - -Remote.TRANSACTION_EVENTS = [ - 'transaction', - 'transaction_all' -]; - -// Flags for ledger entries. In support of accountRoot(). -Remote.flags = { - // AccountRoot - account_root: { - PasswordSpent: 0x00010000, // password set fee is spent - RequireDestTag: 0x00020000, // require a DestinationTag for payments - RequireAuth: 0x00040000, // require a authorization to hold IOUs - DisallowXRP: 0x00080000, // disallow sending XRP - DisableMaster: 0x00100000, // force regular key - NoFreeze: 0x00200000, // permanently disallowed freezing trustlines - GlobalFreeze: 0x00400000, // trustlines globally frozen - DefaultRipple: 0x00800000 - }, - // Offer - offer: { - Passive: 0x00010000, - Sell: 0x00020000 // offer was placed as a sell - }, - // Ripple state - state: { - LowReserve: 0x00010000, // entry counts toward reserve - HighReserve: 0x00020000, - LowAuth: 0x00040000, - HighAuth: 0x00080000, - LowNoRipple: 0x00100000, - HighNoRipple: 0x00200000, - LowFreeze: 0x00400000, - HighFreeze: 0x00800000 - } -}; - -/** - * Check that server message is valid - * - * @param {Object} message - * @return Boolean - */ - -Remote.isValidMessage = function(message) { - return (typeof message === 'object') - && (typeof message.type === 'string'); -}; - -/** - * Check that server message contains valid - * ledger data - * - * @param {Object} message - * @return {Boolean} - */ - -Remote.isValidLedgerData = function(message) { - return (typeof message === 'object') - && (typeof message.fee_base === 'number') - && (typeof message.fee_ref === 'number') - && (typeof message.ledger_hash === 'string') - && (typeof message.ledger_index === 'number') - && (typeof message.ledger_time === 'number') - && (typeof message.reserve_base === 'number') - && (typeof message.reserve_inc === 'number'); -}; - -/** - * Check that server message contains valid - * load status data - * - * @param {Object} message - * @return {Boolean} - */ - -Remote.isValidLoadStatus = function(message) { - return (typeof message.load_base === 'number') - && (typeof message.load_factor === 'number'); -}; - -/** - * Check that provided ledger is validated - * - * @param {Object} ledger - * @return {Boolean} - */ - -Remote.isValidated = function(message) { - return (message && typeof message === 'object') - && (message.validated === true); -}; - -/** - * Set the emitted state: 'online' or 'offline' - * - * @param {String} state - */ - -Remote.prototype._setState = function(state) { - if (this.state !== state) { - if (this.trace) { - log.info('set_state:', state); - } - - this.state = state; - this.emit('state', state); - - switch (state) { - case 'online': - this._online_state = 'open'; - this._connected = true; - this.emit('connect'); - this.emit('connected'); - break; - case 'offline': - this._online_state = 'closed'; - this._connected = false; - this.emit('disconnect'); - this.emit('disconnected'); - break; - } - } -}; - -/** - * Inform remote that the remote server is not comming back. - */ - -Remote.prototype.setServerFatal = function() { - this._server_fatal = true; -}; - -/** - * Enable debug output - * - * @param {Boolean} trace - */ - -Remote.prototype.setTrace = function(trace) { - this.trace = (trace === undefined || trace); - return this; -}; - -Remote.prototype._trace = function() { - if (this.trace) { - log.info.apply(log, arguments); - } -}; - -/** - * Store a secret - allows the Remote to automatically fill - * out auth information. - * - * @param {String} account - * @param {String} secret - */ - -Remote.prototype.setSecret = function(account, secret) { - this.secrets[account] = secret; -}; - -Remote.prototype.addServer = function(options) { - const self = this; - const server = new Server(this, options); - - function serverMessage(data) { - self._handleMessage(data, server); - } - - server.on('message', serverMessage); - - function serverConnect() { - self._connection_count += 1; - - if (options.primary) { - self._setPrimaryServer(server); - } - if (self._connection_count === 1) { - self._setState('online'); - } - if (self._connection_count === self._servers.length) { - self.emit('ready'); - } - } - - server.on('connect', serverConnect); - - function serverDisconnect() { - self._connection_count--; - if (self._connection_count === 0) { - self._setState('offline'); - } - } - - server.on('disconnect', serverDisconnect); - - this._servers.push(server); - - return server; -}; - -/** - * Reconnect to Ripple network - */ - -Remote.prototype.reconnect = function() { - if (!this._should_connect) { - return; - } - - log.info('reconnecting'); - - this._servers.forEach(function(server) { - server.reconnect(); - }); -}; - -/** - * Connect to the Ripple network - * - * @param [Function] [callback] - * @api public - */ - -Remote.prototype.connect = function(callback = function() {}) { - if (_.isEmpty(this._servers)) { - throw new Error('No servers available.'); - } - - if (this.isConnected()) { - callback(); - return this; - } - - this.once('connect', callback); - this._should_connect = true; - this._servers.forEach(server => { - server.connect(); - }); - - return this; -}; - -/** - * Disconnect from the Ripple network. - * - * @param {Function} [callback] - * @api public - */ - -Remote.prototype.disconnect = function(callback = function() {}) { - if (_.isEmpty(this._servers)) { - throw new Error('No servers available, not disconnecting'); - } - - if (!this.isConnected()) { - callback(); - return this; - } - - this._should_connect = false; - this.once('disconnect', callback); - this._servers.forEach(server => { - server.disconnect(); - }); - - this._setState('offline'); - - return this; -}; - -/** - * Handle server message. Server messages are proxied to - * the Remote, such that global events can be handled - * - * It is possible for messages to be dispatched after the - * connection is closed. - * - * @param {JSON} message - * @param {Server} server - */ - -Remote.prototype._handleMessage = function(message, server) { - if (!Remote.isValidMessage(message)) { - // Unexpected response from remote. - const error = new RippleError('remoteUnexpected', - 'Unexpected response from remote: ' + JSON.stringify(message)); - - this.emit('error', error); - log.error(error); - return; - } - - switch (message.type) { - case 'ledgerClosed': - this._handleLedgerClosed(message, server); - break; - case 'serverStatus': - this._handleServerStatus(message, server); - break; - case 'transaction': - this._handleTransaction(message, server); - break; - case 'path_find': - this._handlePathFind(message, server); - break; - case 'validationReceived': - this._handleValidationReceived(message, server); - break; - default: - if (this.trace) { - log.info(message.type + ': ', message); - } - break; - } -}; - -/** - * - * @param {Function} [callback] - * @api public - */ - -Remote.prototype.getLedgerSequence = function(callback = function() {}) { - if (!this._servers.length) { - callback(new Error('No servers available.')); - return; - } - - let timeout = null; - function onLedgerClosed() { - clearTimeout(timeout); - callback(null, this._ledger_current_index - 1); - } - - if (_.isFinite(this._ledger_current_index)) { - // the "current" ledger is the one after the most recently closed ledger - callback(null, this._ledger_current_index - 1); - } else { - this.once('ledger_closed', onLedgerClosed); - - timeout = setTimeout(() => { - this.removeListener('ledger_closed', onLedgerClosed); - callback(new RippleError('timeout', - 'Timed out waiting for ledger to close.')); - }, this.pathfind_timeout); - } -}; - -/** - * - * @api private - */ - -Remote.prototype.getLedgerSequenceSync = function(): number { - if (!this._ledger_current_index) { - throw new Error('Ledger sequence has not yet been initialized'); - } - // the "current" ledger is the one after the most recently closed ledger - return this._ledger_current_index - 1; -}; - -/** - * Handle server ledger_closed event - * - * @param {Object} message - */ - -Remote.prototype._handleLedgerClosed = function(message, server) { - const self = this; - - // XXX If not trusted, need to verify we consider ledger closed. - // XXX Also need to consider a slow server or out of order response. - // XXX Be more defensive fields could be missing or of wrong type. - // YYY Might want to do some cache management. - if (!Remote.isValidLedgerData(message)) { - return; - } - - const ledgerAdvanced = message.ledger_index >= this._ledger_current_index; - - if (isNaN(this._ledger_current_index) || ledgerAdvanced) { - this._ledger_time = message.ledger_time; - this._ledger_hash = message.ledger_hash; - this._ledger_current_index = message.ledger_index + 1; - - if (this.isConnected()) { - this.emit('ledger_closed', message, server); - } else { - this.once('connect', function() { - // Delay until server is 'online' - self.emit('ledger_closed', message, server); - }); - } - } -}; - -/** - * Handle server validation_received event - * - * @param {Object} message - */ - -Remote.prototype._handleValidationReceived = function(message, server) { - this.emit('validation_received', message, server); -}; - -/** - * Handle server server_status event - * - * @param {Object} message - */ - -Remote.prototype._handleServerStatus = function(message, server) { - this.emit('server_status', message, server); -}; - -/** - * Handle server transaction event - * - * @param {Object} message - */ - -Remote.prototype._handleTransaction = function(message, server) { - // XXX If not trusted, need proof. - const transactionHash = message.transaction.hash; - - if (this._received_tx.get(transactionHash)) { - // De-duplicate transactions - return; - } - - if (message.validated) { - this._received_tx.set(transactionHash, true); - } - - if (this.trace) { - log.info('tx:', message); - } - - const metadata = message.meta || message.metadata; - - if (metadata) { - // Process metadata - message.mmeta = new Meta(metadata); - - // Pass the event on to any related Account objects - message.mmeta.getAffectedAccounts().forEach(function(account) { - if (this._accounts[account]) { - this._accounts[account].notify(message); - } - }, this); - - // Pass the event on to any related OrderBooks - message.mmeta.getAffectedBooks().forEach(function(book) { - if (this._books[book]) { - this._books[book].notify(message); - } - }, this); - } else { - // Transaction could be from proposed transaction stream - // XX - ['Account', 'Destination'].forEach(function(prop) { - if (this._accounts[message.transaction[prop]]) { - this._accounts[message.transaction[prop]].notify(message); - } - }, this); - } - - this.emit('transaction', message, server); - this.emit('transaction_all', message, server); -}; - -/** - * Handle server path_find event - * - * @param {Object} message - */ - -Remote.prototype._handlePathFind = function(message, server) { - // Pass the event to the currently open PathFind object - if (this._cur_path_find) { - this._cur_path_find.notify_update(message); - } - - this.emit('path_find_all', message, server); -}; - -/** - * Returns the current ledger hash - * - * @return {String} ledger hash - */ - -Remote.prototype.getLedgerHash = function() { - return this._ledger_hash; -}; - -/** - * Set primary server. Primary server will be selected - * to handle requested regardless of its internally-tracked - * priority score - * - * @param {Server} server - */ - -Remote.prototype._setPrimaryServer = -Remote.prototype.setPrimaryServer = function(server) { - if (this._primary_server) { - this._primary_server._primary = false; - } - this._primary_server = server; - this._primary_server._primary = true; -}; - -/** - * Get connected state - * - * @return {Boolean} connected - */ - -Remote.prototype.isConnected = function() { - return this._connected; -}; - -/** - * Get array of connected servers - */ - -Remote.prototype.getConnectedServers = function() { - return this._servers.filter(function(server) { - return server.isConnected(); - }); -}; - -/** - * Select a server to handle a request. Servers are - * automatically prioritized - */ - -Remote.prototype._getServer = -Remote.prototype.getServer = function() { - if (this._primary_server && this._primary_server.isConnected()) { - return this._primary_server; - } - - if (!this._servers.length) { - return null; - } - - const connectedServers = this.getConnectedServers(); - if (connectedServers.length === 0 || !connectedServers[0]) { - return null; - } - - let server = connectedServers[0]; - let cScore = server._score + server._fee; - - for (let i = 1; i < connectedServers.length; i++) { - const _server = connectedServers[i]; - const bScore = _server._score + _server._fee; - if (bScore < cScore) { - server = _server; - cScore = bScore; - } - } - - return server; -}; - -/** - * Send a request. This method is called internally by Request - * objects. Each Request contains a reference to Remote, and - * Request.request calls Request.remote.request - * - * @param {Request} request - */ - -Remote.prototype.request = function(request) { - if (typeof request === 'string') { - const prefix = /^request_/.test(request) ? '' : 'request_'; - const requestName = prefix + request; - const methodName = requestName.replace(/(\_\w)/g, m => m[1].toUpperCase()); - - if (typeof this[methodName] === 'function') { - const args = _.slice(arguments, 1); - return this[methodName].apply(this, args); - } - - throw new Error('Command does not exist: ' + requestName); - } - - if (!(request instanceof Request)) { - throw new Error('Argument is not a Request'); - } - - if (!this._servers.length) { - return request.emit('error', new Error('No servers available')); - } - if (!this.isConnected()) { - return this.once('connect', this.request.bind(this, request)); - } - if (request.server === null) { - return request.emit('error', new Error('Server does not exist')); - } - - const server = request.server || this.getServer(); - if (server) { - server._request(request); - } else { - request.emit('error', new Error('No servers available')); - } -}; - -Remote.prototype.rawRequest = function(message, callback) { - const request = new Request(this, message.command); - _.assign(request.message, _.omit(message, _.isUndefined)); - request.request(callback); -}; - -/** - * Request ping - * - * @param {String} [server] host - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.ping = -Remote.prototype.requestPing = function(host, callback_) { - const request = new Request(this, 'ping'); - let callback = callback_; - - switch (typeof host) { - case 'function': - callback = host; - break; - case 'string': - request.setServer(host); - break; - } - - const then = Date.now(); - - request.once('success', function() { - request.emit('pong', Date.now() - then); - }); - - request.callback(callback, 'pong'); - - return request; -}; - -/** - * Request server_info - * - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestServerInfo = function(callback) { - return new Request(this, 'server_info').callback(callback); -}; - -/** - * Request ledger - * - * @return {Request} request - */ - -Remote.prototype.requestLedger = function(options, callback_) { - // XXX This is a bad command. Some variants don't scale. - // XXX Require the server to be trusted. - // utils.assert(this.trusted); - - const request = new Request(this, 'ledger'); - let callback = callback_; - - switch (typeof options) { - case 'undefined': break; - case 'function': - callback = options; - break; - - case 'object': - if (!options) { - break; - } - - Object.keys(options).forEach(function(o) { - switch (o) { - case 'full': - case 'expand': - case 'transactions': - case 'accounts': - request.message[o] = options[o] ? true : false; - break; - case 'ledger': - request.selectLedger(options.ledger); - break; - case 'ledger_index': - case 'ledger_hash': - request.message[o] = options[o]; - break; - case 'closed' : - case 'current' : - case 'validated' : - request.message.ledger_index = o; - break; - } - }, options); - break; - - default: - request.selectLedger(options); - break; - } - - request.callback(callback); - - return request; -}; - -/** - * Request ledger_closed - * - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestLedgerClosed = -Remote.prototype.requestLedgerHash = function(callback) { - // utils.assert(this.trusted); // If not trusted, need to check proof. - return new Request(this, 'ledger_closed').callback(callback); -}; - -/** - * Request ledger_header - * - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestLedgerHeader = function(callback) { - return new Request(this, 'ledger_header').callback(callback); -}; - -/** - * Request ledger_current - * - * Get the current proposed ledger entry. May be closed (and revised) - * at any time (even before returning). - * - * Only for unit testing. - * - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestLedgerCurrent = function(callback) { - return new Request(this, 'ledger_current').callback(callback); -}; - -/** - * Request ledger_data - * - * Get the contents of a specified ledger - * - * @param {Object} options - * @param {Boolean} [options.binary]- Flag which determines if rippled - * returns binary or parsed JSON - * @param {String|Number} [options.ledger] - Hash or sequence of a ledger - * to get contents for - * @param {Number} [options.limit] - Number of contents to retrieve - * from the ledger - * @param {Function} callback - * - * @callback - * @param {Error} error - * @param {LedgerData} ledgerData - * - * @return {Request} request - */ - -Remote.prototype.requestLedgerData = function(options, callback) { - const request = new Request(this, 'ledger_data'); - - request.message.binary = options.binary !== false; - request.selectLedger(options.ledger); - request.message.limit = options.limit; - - request.once('success', function(res) { - if (options.binary === false) { - request.emit('state', res); - return; - } - - function iterator(ledgerData, next) { - async.setImmediate(function() { - next(null, Remote.parseBinaryLedgerData(ledgerData)); - }); - } - - function complete(err, state) { - if (err) { - request.emit('error', err); - } else { - res.state = state; - request.emit('state', res); - } - } - - async.mapSeries(res.state, iterator, complete); - }); - - request.callback(callback, 'state'); - - return request; -}; - -/** - * Request ledger_entry - * - * @param {String} [type] - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestLedgerEntry = function(type, callback_) { - // utils.assert(this.trusted); - // If not trusted, need to check proof, maybe talk packet protocol. - - const self = this; - - const request = new Request(this, 'ledger_entry'); - const callback = _.isFunction(type) ? type : callback_; - - // Transparent caching. When .request() is invoked, look in the Remote object - // for the result. If not found, listen, cache result, and emit it. - // - // Transparent caching: - if (type === 'account_root') { - request.request_default = request.request; - - request.request = function() { - // Intercept default request. - let bDefault = true; - - if (!self._ledger_hash && type === 'account_root') { - let cache = self.ledgers.current.account_root; - - if (!cache) { - cache = self.ledgers.current.account_root = {}; - } - - const node = self.ledgers.current - .account_root[request.message.account_root]; - - if (node) { - // Emulate fetch of ledger entry. - // YYY Missing lots of fields. - request.emit('success', {node: node}); - bDefault = false; - } else { // Was not cached. - // XXX Only allow with trusted mode. Must sync response with advance - switch (type) { - case 'account_root': - request.once('success', function(message) { - // Cache node. - self.ledgers.current - .account_root[message.node.Account] = message.node; - }); - break; - - default: - // This type not cached. - } - } - } - - if (bDefault) { - request.request_default(); - } - }; - } - - request.callback(callback); - - return request; -}; - -/** - * Request subscribe - * - * @param {Array} streams - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestSubscribe = function(streams, callback) { - const request = new Request(this, 'subscribe'); - - if (streams) { - request.message.streams = Array.isArray(streams) ? streams : [streams]; - } - - request.callback(callback); - - return request; -}; - -/** - * Request usubscribe - * - * @param {Array} streams - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestUnsubscribe = function(streams, callback) { - const request = new Request(this, 'unsubscribe'); - - if (streams) { - request.message.streams = Array.isArray(streams) ? streams : [streams]; - } - - request.callback(callback); - - return request; -}; - -/** - * Request transaction_entry - * - * @param {Object} options - - * @param {String} [options.transaction] - hash - * @param {String|Number} [options.ledger='validated'] - hash or sequence - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestTransactionEntry = function(options, callback) { - const request = new Request(this, 'transaction_entry'); - request.txHash(options.hash); - request.selectLedger(options.ledger, 'validated'); - request.callback(callback); - return request; -}; - -/** - * Request tx - * - * @param {Object} options - - * @property {String} [options.hash] - Transaction hash - * @property {Boolean} [options.binary=true] - Flag which determines if rippled - * returns binary or parsed JSON - * @param {Function} [callback] - * @return {Request} request - */ - -Remote.prototype.requestTx = -Remote.prototype.requestTransaction = function(options, callback) { - const request = new Request(this, 'tx'); - request.message.binary = options.binary !== false; - request.message.transaction = options.hash; - - request.once('success', function(res) { - if (options.binary === false) { - request.emit('transaction', res); - } else { - request.emit('transaction', Remote.parseBinaryTransaction(res)); - } - }); - - request.callback(callback, 'transaction'); - - return request; -}; - -/** - * Account Request - * - * Optional paging with limit and marker options - * supported in rippled for 'account_lines' and 'account_offers' - * - * The paged responses aren't guaranteed to be reliable between - * ledger closes. You have to supply a ledger_index or ledger_hash - * when paging to ensure a complete response - * - * @param {String} command - request command, e.g. 'account_lines' - * @param {Object} options - all optional - * @param {String} options.account - ripple address - * @param {String} [options.peer] - ripple address - * @param {String|Number} [options.ledger] - identifier - * @param {Number} [options.limit] - max results per response - * @param {String} [options.marker] - start position in response paging - * @param {Function} [callback] - * @return {Request} - * @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) && !isValidLedgerHash(options.ledger)) { - throw new Error( - 'A ledger_index or ledger_hash must be provided when using a marker'); - } - } - - const request = new Request(this, command); - - request.message.account = options.account; - request.selectLedger(options.ledger); - - if (isValidAddress(options.peer)) { - request.message.peer = options.peer; - } - - if (!isNaN(options.limit)) { - let _limit = Number(options.limit); - - // max for 32-bit unsigned int is 4294967295 - // we'll clamp to 1e9 - if (_limit > 1e9) { - _limit = 1e9; - } - // min for 32-bit unsigned int is 0 - // we'll clamp to 0 - if (_limit < 0) { - _limit = 0; - } - - request.message.limit = _limit; - } - - if (options.marker) { - request.message.marker = options.marker; - } - - request.callback(callback); - - return request; -}; - -/** - * Request account_info - * - * @param {Object} options - - * @param {String} options.account - ripple address - * @param {String} [options.peer] - ripple address - * @param {String|Number} [options.ledger] - identifier - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestAccountInfo = function(options, callback) { - return this._accountRequest('account_info', options, callback); -}; - -/** - * Request account_currencies - * - * @param {Object} options - * @param {String} options.account - ripple address - * @param {String} [options.peer] - ripple address - * @param {String|Number} [options.ledger] - identifier - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestAccountCurrencies = function(options, callback) { - return this._accountRequest('account_currencies', options, callback); -}; - -/** - * Request account_lines - * - * Requests for account_lines support paging, provide a limit and marker - * to page through responses. - * - * The paged responses aren't guaranteed to be reliable between - * ledger closes. You have to supply a ledger_index or ledger_hash - * when paging to ensure a complete response - * - * @param {Object} options - * @param {String} options.account - ripple address - * @param {String} [options.peer] - ripple address - * @param {String|Number} [options.ledger] identifier - * @param {Number} [options.limit] - max results per response - * @param {String} [options.marker] - start position in response paging - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestAccountLines = function(options, callback) { - // XXX Does this require the server to be trusted? - // utils.assert(this.trusted); - return this._accountRequest('account_lines', options, callback); -}; - -/** - * Request account_offers - * - * Requests for account_offers support paging, provide a limit and marker - * to page through responses. - * - * The paged responses aren't guaranteed to be reliable between - * ledger closes. You have to supply a ledger_index or ledger_hash - * when paging to ensure a complete response - * - * @param {Object} options - * @param {String} options.account - ripple address - * @param {String|Number} [options.ledger] identifier - * @param {Number} [options.limit] - max results per response - * @param {String} [options.marker] - start position in response paging - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestAccountOffers = function(options, callback) { - return this._accountRequest('account_offers', options, callback); -}; - -/** - * Request account_tx - * - * @param {Object} options - * - * @param {String} options.account - * @param {Number} [options.ledger_index_min=-1] - * @param {Number} [options.ledger_index_max=-1] - * @param {Boolean} [options.binary=true] - * @param {Boolean} [options.parseBinary=true] - * @param {Boolean} [options.count=false] - * @param {Boolean} [options.descending=false] - * @param {Number} [options.offset=0] - * @param {Number} [options.limit] - * - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestAccountTransactions = -Remote.prototype.requestAccountTx = function(options, callback) { - // XXX Does this require the server to be trusted? - // utils.assert(this.trusted); - - const request = new Request(this, 'account_tx'); - - options.binary = options.binary !== false; - - if (options.min_ledger !== undefined) { - options.ledger_index_min = options.min_ledger; - } - - if (options.max_ledger !== undefined) { - options.ledger_index_max = options.max_ledger; - } - - if (options.binary && options.parseBinary === undefined) { - options.parseBinary = true; - } - - Object.keys(options).forEach(function(o) { - switch (o) { - case 'account': - case 'ledger_index_min': // earliest - case 'ledger_index_max': // latest - case 'binary': // false - case 'count': // false - case 'descending': // false - case 'offset': // 0 - case 'limit': - - // extended account_tx - case 'forward': // false - case 'marker': - request.message[o] = this[o]; - break; - } - }, options); - - request.once('success', function(res) { - if (!options.parseBinary) { - request.emit('transactions', res); - return; - } - - function iterator(transaction, next) { - async.setImmediate(function() { - next(null, Remote.parseBinaryAccountTransaction(transaction)); - }); - } - - function complete(err, transactions) { - if (err) { - request.emit('error', err); - } else { - res.transactions = transactions; - request.emit('transactions', res); - } - } - - async.mapSeries(res.transactions, iterator, complete); - }); - - request.callback(callback, 'transactions'); - - return request; -}; - -/** - * @param {Object} transaction - * @return {Transaction} - */ - -Remote.parseBinaryAccountTransaction = function(transaction) { - 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_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_json.Amount) { - case 'string': - case 'object': - tx_result.meta.delivered_amount = tx_json.Amount; - break; - } - } - - return tx_result; -}; - -Remote.parseBinaryTransaction = function(transaction) { - const tx_json = binary.decode(transaction.tx); - const meta = binary.decode(transaction.meta); - - const tx_result = tx_json; - - tx_result.date = transaction.date; - tx_result.hash = transaction.hash; - tx_result.inLedger = transaction.inLedger; - tx_result.ledger_index = transaction.ledger_index; - tx_result.meta = meta; - tx_result.validated = transaction.validated; - - switch (typeof meta.DeliveredAmount) { - case 'string': - case 'object': - tx_result.meta.delivered_amount = meta.DeliveredAmount; - break; - default: - switch (typeof tx_json.Amount) { - case 'string': - case 'object': - tx_result.meta.delivered_amount = tx_json.Amount; - break; - } - } - - return tx_result; -}; - -/** - * Parse binary ledger state data - * - * @param {Object} ledgerData - * @property {String} ledgerData.data - * @property {String} ledgerData.index - * - * @return {State} - */ - -Remote.parseBinaryLedgerData = function(ledgerData) { - const data = binary.decode(ledgerData.data); - data.index = ledgerData.index; - return data; -}; - -/** - * Request the overall transaction history. - * - * Returns a list of transactions that happened recently on the network. The - * default number of transactions to be returned is 20. - * - * @param {Number} [start] - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestTransactionHistory = function(options, callback) { - // XXX Does this require the server to be trusted? - // utils.assert(this.trusted); - const request = new Request(this, 'tx_history'); - request.message.start = options.start; - request.callback(callback); - - return request; -}; - -/** - * Request book_offers - * - * @param {Object} options - * @param {Object} options.taker_gets - taker_gets with issuer and currency - * @param {Object} options.taker_pays - taker_pays with issuer and currency - * @param {String} [options.taker] - * @param {String} [options.ledger] - * @param {String|Number} [options.limit] - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestBookOffers = function(options, callback) { - const {taker, ledger, limit} = options; - let {taker_gets, taker_pays} = options; - - if (taker_gets === undefined) { - taker_gets = options.gets; - } - if (taker_pays === undefined) { - taker_pays = options.pays; - } - - const request = new Request(this, 'book_offers'); - - request.message.taker_gets = { - currency: taker_gets.currency - }; - - if (normalizeCurrency(request.message.taker_gets.currency) !== 'XRP') { - request.message.taker_gets.issuer = taker_gets.issuer; - } - - request.message.taker_pays = { - currency: taker_pays.currency - }; - - if (normalizeCurrency(request.message.taker_pays.currency) !== 'XRP') { - request.message.taker_pays.issuer = taker_pays.issuer; - } - - request.message.taker = taker ? taker : constants.ACCOUNT_ONE; - request.selectLedger(ledger); - - if (!isNaN(limit)) { - let _limit = Number(limit); - - // max for 32-bit unsigned int is 4294967295 - // we'll clamp to 1e9 - if (_limit > 1e9) { - _limit = 1e9; - } - // min for 32-bit unsigned int is 0 - // we'll clamp to 0 - if (_limit < 0) { - _limit = 0; - } - - request.message.limit = _limit; - } - - request.callback(callback); - - return request; -}; - -/** - * Request wallet_accounts - * - * @param {String} seed - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestWalletAccounts = function(options, callback) { - utils.assert(this.trusted); // Don't send secrets. - const request = new Request(this, 'wallet_accounts'); - request.message.seed = options.seed; - request.callback(callback); - - return request; -}; - -/** - * Request sign - * - * @param {String} secret - * @param {Object} tx_json - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestSign = function(options, callback) { - utils.assert(this.trusted); // Don't send secrets. - - const request = new Request(this, 'sign'); - request.message.secret = options.secret; - request.message.tx_json = options.tx_json; - request.callback(callback); - - return request; -}; - -/** - * Request submit - * - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestSubmit = function(callback) { - return new Request(this, 'submit').callback(callback); -}; - -/** - * Create a subscribe request with current subscriptions. - * - * Other classes can add their own subscriptions to this request by listening - * to the server_subscribe event. - * - * This function will create and return the request, but not submit it. - * - * @param {Function} [callback] - * @api private - */ - -Remote.prototype._serverPrepareSubscribe = function(server, callback_) { - const self = this; - const feeds = ['ledger', 'server']; - const callback = _.isFunction(server) ? server : callback_; - - if (this._transaction_listeners) { - feeds.push('transactions'); - } - - const request = this.requestSubscribe(feeds); - - function serverSubscribed(message) { - self._stand_alone = Boolean(message.stand_alone); - self._testnet = Boolean(message.testnet); - self._handleLedgerClosed(message, server); - self.emit('subscribed'); - } - - request.on('error', function(err) { - if (self.trace) { - log.info('Initial server subscribe failed', err); - } - }); - - request.once('success', serverSubscribed); - - self.emit('prepare_subscribe', request); - - request.callback(callback, 'subscribed'); - - return request; -}; - -/** - * For unit testing: ask the remote to accept the current ledger. - * To be notified when the ledger is accepted, server_subscribe() then listen - * to 'ledger_hash' events. A good way to be notified of the result of this is: - * remote.on('ledger_closed', function(ledger_closed, ledger_index) { ... } ); - * - * @param {Function} [callback] - */ - -Remote.prototype.ledgerAccept = -Remote.prototype.requestLedgerAccept = function(callback) { - /* eslint-disable consistent-return */ - const request = new Request(this, 'ledger_accept'); - - if (!this._stand_alone) { - // XXX This should emit error on the request - this.emit('error', new RippleError('notStandAlone')); - return; - } - - this.once('ledger_closed', function(ledger) { - request.emit('ledger_closed', ledger); - }); - - request.callback(callback, 'ledger_closed'); - request.request(); - - return request; - /* eslint-enable consistent-return */ -}; - -/** - * Account root request abstraction - * - * @this Remote - * @api private - */ - -Remote.prototype._accountRootRequest = function(command, filter, - options, callback) { - const request = this.requestLedgerEntry('account_root'); - request.accountRoot(options.account); - request.selectLedger(options.ledger); - - request.once('success', function(message) { - request.emit(command, filter(message)); - }); - - request.callback(callback, command); - - return request; -}; - -/** - * Request account balance - * - * @param {Object} options - * @param {String} options.account - - * @param {String|Number} [options.ledger] - - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestAccountBalance = function(options, callback) { - function responseFilter(message) { - return Amount.from_json(message.node.Balance); - } - return this._accountRootRequest( - 'account_balance', responseFilter, options, callback); -}; - -/** - * Request account flags - * - * @param {Object} options - * @param {String} options.account - - * @param {String|Number} [options.ledger] - - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestAccountFlags = function(options, callback) { - function responseFilter(message) { - return message.node.Flags; - } - return this._accountRootRequest( - 'account_flags', responseFilter, options, callback); -}; - -/** - * Request owner count - * - * @param {Object} options - * @param {String} options.account - * @param {String|Number} [options.ledger] - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestOwnerCount = function(options, callback) { - function responseFilter(message) { - return message.node.OwnerCount; - } - return this._accountRootRequest( - 'owner_count', responseFilter, options, callback); -}; - -/** - * Get an account by accountID (address) - * - * - * @param {String} account - * @return {Account} - */ - -Remote.prototype.getAccount = function(accountID) { - return this._accounts[accountID]; -}; - -/** - * Add an account by accountID (address) - * - * @param {String} account - * @return {Account} - */ - -Remote.prototype.addAccount = function(accountID) { - const account = new Account(this, accountID); - - if (account.isValid()) { - this._accounts[accountID] = account; - } - - return account; -}; - -/** - * Add an account if it does not exist, return the - * account by accountID (address) - * - * @param {String} account - * @return {Account} - */ - -Remote.prototype.account = -Remote.prototype.findAccount = function(accountID) { - const account = this.getAccount(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 - * - * @param {Object} options - - * @param {Function} [callback] - - * @return {PathFind} - - */ -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; - } - - const pathFind = new PathFind(this, - options.src_account, options.dst_account, - options.dst_amount, options.src_currencies, options.src_amount); - - if (this._cur_path_find) { - this._cur_path_find.notify_superceded(); - } - - if (callback) { - const updateTimeout = setTimeout(() => { - callback(new RippleError('tejTimeout', - 'createPathFind request timed out')); - 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. - // If another pathfinding request is already open on the same - // connection, the old request is automatically closed and replaced - // with the new request." - // - ripple.com/build/rippled-apis/#path-find-create - if (this._queued_path_finds.length > 0) { - const pathfind = this._queued_path_finds.shift(); - this.createPathFind(pathfind.options, pathfind.callback); - } else { - pathFind.close(); - } - } - }); - pathFind.once('error', (error) => { - clearTimeout(updateTimeout); - this._cur_path_find = null; - callback(error); - }); - } - - this._cur_path_find = pathFind; - pathFind.create(); - return pathFind; -}; - -Remote.prepareTrade = function(currency, issuer) { - const suffix = normalizeCurrency(currency) === 'XRP' ? '' : ('/' + issuer); - return currency + suffix; -}; - -/** - * Create an OrderBook if it does not exist, return - * the order book - * - * @param {Object} options - * @return {OrderBook} - */ - -Remote.prototype.book = Remote.prototype.createOrderBook = function(options) { - const gets = Remote.prepareTrade(options.currency_gets, options.issuer_gets); - const pays = Remote.prepareTrade(options.currency_pays, options.issuer_pays); - const key = gets + ':' + pays; - - if (this._books.hasOwnProperty(key)) { - return this._books[key]; - } - - const book = new OrderBook(this, - options.currency_gets, options.issuer_gets, - options.currency_pays, options.issuer_pays, - key); - - if (book.is_valid()) { - this._books[key] = book; - } - - return book; -}; - -/** - * Return the next account sequence - * - * @param {String} account - * @param {String} sequence modifier (ADVANCE or REWIND) - * @return {Number} sequence - */ - -Remote.prototype.accountSeq = -Remote.prototype.getAccountSequence = function(account, advance) { - const accountInfo = this.accounts[account]; - - if (!accountInfo) { - return NaN; - } - - const seq = accountInfo.seq; - const change = {ADVANCE: 1, REWIND: -1}[advance.toUpperCase()] || 0; - - accountInfo.seq += change; - - return seq; -}; - -/** - * Set account sequence - * - * @param {String} account - * @param {Number} sequence - */ - -Remote.prototype.setAccountSequence = -Remote.prototype.setAccountSeq = function(account, sequence) { - if (!this.accounts.hasOwnProperty(account)) { - this.accounts[account] = { }; - } - - this.accounts[account].seq = sequence; -}; - -/** - * Refresh an account's sequence from server - * - * @param {Object} options - * @param {String} options.account - * @param {String|Number} [options.ledger] - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.accountSeqCache = function(options, callback) { - if (!this.accounts.hasOwnProperty(options.account)) { - this.accounts[options.account] = { }; - } - - const account_info = this.accounts[options.account]; - let request = account_info.caching_seq_request; - - function accountRootSuccess(message) { - delete account_info.caching_seq_request; - - const seq = message.node.Sequence; - account_info.seq = seq; - - request.emit('success_cache', message); - } - - function accountRootError(message) { - delete account_info.caching_seq_request; - - request.emit('error_cache', message); - } - - if (!request) { - request = this.requestLedgerEntry('account_root'); - request.accountRoot(options.account); - - if (!_.isUndefined(options.ledger)) { - request.selectLedger(options.ledger); - } - - request.once('success', accountRootSuccess); - request.once('error', accountRootError); - - account_info.caching_seq_request = request; - } - - request.callback(callback, 'success_cache', 'error_cache'); - - return request; -}; - -/** - * Mark an account's root node as dirty. - * - * @param {String} account - */ - -Remote.prototype.dirtyAccountRoot = function(account) { - delete this.ledgers.current.account_root[account]; -}; - -/** - * Get an Offer from the ledger - * - * @param {Object} options - * @param {String|Number} options.ledger - * @param {String} [options.account] - Required unless using options.index - * @param {Number} [options.sequence] - Required unless using options.index - * @param {String} [options.index] - Required only if options.account and - * options.sequence not provided - * - * @callback - * @param {Error} error - * @param {Object} message - * - * @return {Request} - */ - -Remote.prototype.requestOffer = function(options, callback) { - const request = this.requestLedgerEntry('offer'); - - if (options.account && options.sequence) { - request.offerId(options.account, options.sequence); - } else if (options.index) { - request.offerIndex(options.index); - } - - request.ledgerSelect(options.ledger); - - request.once('success', function(res) { - request.emit('offer', res); - }); - - request.callback(callback, 'offer'); - - return request; -}; - - -/** - * Get an account's balance - * - * @param {Object} options - * @param {String} options.account - * @param {String} [options.issuer] - * @param {String} [options.currency] - * @param {String|Number} [options.ledger] - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestRippleBalance = function(options, callback) { - // YYY Could be cached per ledger. - const request = this.requestLedgerEntry('ripple_state'); - request.rippleState(options.account, options.issuer, options.currency); - - if (!_.isUndefined(options.ledger)) { - request.selectLedger(options.ledger); - } - - function rippleState(message) { - const node = message.node; - const lowLimit = Amount.from_json(node.LowLimit); - const highLimit = Amount.from_json(node.HighLimit); - - // The amount the low account holds of issuer. - const balance = Amount.from_json(node.Balance); - - // accountHigh implies for account: balance is negated. highLimit is the - // limit set by account. - const accountHigh = (options.account === highLimit.issuer()); - - request.emit('ripple_state', { - account_balance: (accountHigh - ? balance.negate() - : balance.clone()).parse_issuer(options.account), - peer_balance: (!accountHigh - ? balance.negate() - : balance.clone()).parse_issuer(options.issuer), - account_limit: (accountHigh - ? highLimit - : lowLimit).clone().parse_issuer(options.issuer), - peer_limit: (!accountHigh - ? highLimit - : lowLimit).clone().parse_issuer(options.account), - account_quality_in: (accountHigh - ? node.HighQualityIn - : node.LowQualityIn), - peer_quality_in: (!accountHigh - ? node.HighQualityIn - : node.LowQualityIn), - account_quality_out: (accountHigh - ? node.HighQualityOut - : node.LowQualityOut), - peer_quality_out: (!accountHigh - ? node.HighQualityOut - : node.LowQualityOut) - }); - } - - request.once('success', rippleState); - request.callback(callback, 'ripple_state'); - - return request; -}; - -Remote.prepareCurrency = -Remote.prepareCurrencies = function(currency) { - const newCurrency = { }; - - if (currency.hasOwnProperty('issuer')) { - newCurrency.issuer = currency.issuer; - } - - if (currency.hasOwnProperty('currency')) { - newCurrency.currency = currency.currency; - } - - return newCurrency; -}; - -/** - * Request ripple_path_find - * - * @param {Object} options - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestRipplePathFind = function(options, callback) { - const request = new Request(this, 'ripple_path_find'); - request.message.source_account = options.source_account; - request.message.destination_account = options.destination_account; - request.message.destination_amount = - Amount.json_rewrite(options.destination_amount); - - if (Array.isArray(options.source_currencies)) { - request.message.source_currencies = - options.source_currencies.map(Remote.prepareCurrency); - } - - request.callback(callback); - - return request; -}; - -/** - * Request path_find/create - * - * @param {Object} options - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestPathFindCreate = function(options, callback) { - const request = new Request(this, 'path_find'); - request.message.subcommand = 'create'; - - request.message.source_account = options.source_account; - request.message.destination_account = options.destination_account; - request.message.destination_amount = - Amount.json_rewrite(options.destination_amount); - - if (Array.isArray(options.source_currencies)) { - request.message.source_currencies = - options.source_currencies.map(Remote.prepareCurrency); - } - - if (options.send_max) { - request.message.send_max = Amount.json_rewrite(options.send_max); - } - - request.callback(callback); - return request; -}; - -/** - * Request path_find/close - * - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestPathFindClose = function(callback) { - const request = new Request(this, 'path_find'); - request.message.subcommand = 'close'; - request.callback(callback); - return request; -}; - -/** - * Request unl_list - * - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestUnlList = function(callback) { - return new Request(this, 'unl_list').callback(callback); -}; - -/** - * Request unl_add - * - * @param {String} address - * @param {String} comment - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestUnlAdd = function(address, comment, callback) { - const request = new Request(this, 'unl_add'); - - request.message.node = address; - - if (comment) { - // note is not specified anywhere, should remove? - request.message.comment = undefined; - } - - request.callback(callback); - - return request; -}; - -/** - * Request unl_delete - * - * @param {String} node - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestUnlDelete = function(node, callback) { - const request = new Request(this, 'unl_delete'); - - request.message.node = node; - request.callback(callback); - - return request; -}; - -/** - * Request peers - * - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestPeers = function(callback) { - return new Request(this, 'peers').callback(callback); -}; - -/** - * Request connect - * - * @param {String} ip - * @param {Number} port - * @param {Function} [callback] - * @return {Request} - */ - -Remote.prototype.requestConnect = function(ip, port, callback) { - const request = new Request(this, 'connect'); - - request.message.ip = ip; - - if (port) { - request.message.port = port; - } - - request.callback(callback); - - return request; -}; - -Remote.prototype.requestGatewayBalances = function(options, callback) { - assert(_.isObject(options), 'Options missing'); - assert(options.account, 'Account missing'); - - const request = new Request(this, 'gateway_balances'); - - request.message.account = options.account; - - if (!_.isUndefined(options.hotwallet)) { - request.message.hotwallet = options.hotwallet; - } - if (!_.isUndefined(options.strict)) { - request.message.strict = options.strict; - } - if (!_.isUndefined(options.ledger)) { - request.selectLedger(options.ledger); - } - - request.callback(callback); - - return request; -}; - -/** - * Create a Transaction - * - * @param {String} TransactionType - * @param {Object} options - * @return {Transaction} - */ - -Remote.prototype.transaction = -Remote.prototype.createTransaction = function(type, options = {}) { - const transaction = new Transaction(this); - - if (arguments.length === 0) { - // Fallback - return transaction; - } - - assert.strictEqual(typeof type, 'string', 'TransactionType must be a string'); - - const constructorMap = { - Payment: transaction.payment, - AccountSet: transaction.accountSet, - TrustSet: transaction.trustSet, - OfferCreate: transaction.offerCreate, - OfferCancel: transaction.offerCancel, - SetRegularKey: transaction.setRegularKey, - SignerListSet: transaction.setSignerList, - SuspendedPaymentCreate: transaction.suspendedPaymentCreate, - SuspendedPaymentFinish: transaction.suspendedPaymentFinish, - SuspendedPaymentCancel: transaction.suspendedPaymentCancel - }; - - const transactionConstructor = constructorMap[type]; - - if (!transactionConstructor) { - throw new Error('TransactionType must be a valid transaction type'); - } - - return transactionConstructor.call(transaction, options); -}; - -/** - * Calculate a transaction fee for a number of tx fee units. - * - * This takes into account the last known network and local load fees. - * - * @param {Number} fee units - * @return {Amount} Final fee in XRP for specified number of fee units. - */ - -Remote.prototype.feeTx = function(units) { - const server = this.getServer(); - - if (!server) { - throw new Error('No connected servers'); - } - - return server._feeTx(units); -}; - -/** - * Same as feeTx, but will wait to connect to server if currently - * disconnected. - * - * @param {Number} fee units - * @param {Function} callback - */ - -Remote.prototype.feeTxAsync = function(units, callback) { - if (!this._servers.length) { - callback(new Error('No servers available.')); - return; - } - - let server = this.getServer(); - - if (!server) { - this.once('connected', () => { - server = this.getServer(); - callback(null, server._feeTx(units)); - }); - } else { - callback(null, server._feeTx(units)); - } -}; - -/** - * Get the current recommended transaction fee unit. - * - * Multiply this value with the number of fee units in order to calculate the - * recommended fee for the transaction you are trying to submit. - * - * @return {Number} Recommended amount for one fee unit as float. - */ - -Remote.prototype.feeTxUnit = function() { - const server = this.getServer(); - - if (!server) { - throw new Error('No connected servers'); - } - - return server._feeTxUnit(); -}; - -/** - * Get the current recommended reserve base. - * - * Returns the base reserve with load fees and safety margin applied. - * - * @param {Number} owner count - * @return {Amount} - */ - -Remote.prototype.reserve = function(owner_count) { - const server = this.getServer(); - - if (!server) { - throw new Error('No connected servers'); - } - - return server._reserve(owner_count); -}; - -exports.Remote = Remote; - -// vim:sw=2:sts=2:ts=8:et diff --git a/src/core/request.js b/src/core/request.js deleted file mode 100644 index 8f5c4a01..00000000 --- a/src/core/request.js +++ /dev/null @@ -1,633 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const EventEmitter = require('events').EventEmitter; -const util = require('util'); -const async = require('async'); -const {normalizeCurrency} = require('./currency'); -const RippleError = require('./rippleerror').RippleError; - -// Request events emitted: -// 'success' : Request successful. -// 'error' : Request failed. -// 'remoteError' -// 'remoteUnexpected' -// 'remoteDisconnected' - -/** - * Request - * - * @param {Remote} remote - * @param {String} command - */ - -function Request(remote, command) { - EventEmitter.call(this); - - this.remote = remote; - this.requested = false; - this.reconnectTimeout = 1000 * 3; - this.successEvent = 'success'; - this.errorEvent = 'error'; - this.message = { - command: command, - id: undefined - }; - this._timeout = this.remote.submission_timeout; -} - -util.inherits(Request, EventEmitter); - -// Send the request to a remote. -Request.prototype.request = function(servers, callback_) { - const callback = typeof servers === 'function' ? servers : callback_; - const self = this; - - if (this.requested) { - throw new Error('Already requested'); - } - - this.emit('before'); - // emit handler can set requested flag - if (this.requested) { - return this; - } - - this.requested = true; - this.callback(callback); - - this.on('error', function() {}); - this.emit('request', this.remote); - - function doRequest() { - if (Array.isArray(servers)) { - servers.forEach(function(server) { - self.setServer(server); - self.remote.request(self); - }, self); - } else { - self.remote.request(self); - } - } - - const timeout = setTimeout(() => { - if (typeof callback === 'function') { - callback(new RippleError('tejTimeout')); - } - - this.emit('timeout'); - // just in case - this.emit = _.noop; - this.cancel(); - this.remote.removeListener('connected', doRequest); - }, this._timeout); - - if (this.remote.isConnected()) { - this.remote.on('connected', doRequest); - } - - 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(); - - return this; -}; - -function isResponseNotError(res) { - return typeof res === 'object' && !res.hasOwnProperty('error'); -} - -/** - * Broadcast request to all servers, filter responses if a function is - * provided. Return first response that satisfies the filter. Pre-filter - * requests by ledger_index (if a ledger_index is set on the request), and - * automatically retry servers when they reconnect--if they are expected to - * - * Whew - * - * @param [Function] fn - */ - - -Request.prototype.filter = -Request.prototype.addFilter = -Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) { - const self = this; - - if (!this.requested) { - // Defer until requested, and prevent the normal request() from executing - this.once('before', function() { - self.requested = true; - self.broadcast(isResponseSuccess); - }); - return this; - } - - this.on('error', function() {}); - let lastResponse = new Error('No servers available'); - const connectTimeouts = { }; - const emit = this.emit; - - this.emit = function(event, a, b) { - // Proxy success/error events - switch (event) { - case 'success': - case 'error': - emit.call(self, 'proposed', a, b); - break; - default: - emit.apply(self, arguments); - } - }; - - let serversCallbacks = { }; - let serversTimeouts = { }; - let serversClearConnectHandlers = { }; - - function iterator(server, callback) { - // Iterator is called in parallel - - 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 - function serverReconnected() { - clearTimeout(connectTimeouts[serverID]); - connectTimeouts[serverID] = null; - iterator(server, callback); - } - - connectTimeouts[serverID] = setTimeout(function() { - server.removeListener('connect', serverReconnected); - callback(false); - }, self.reconnectTimeout); - - 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) - // Pre-filter servers that do not contain the ledger in request - && (!self.message.hasOwnProperty('ledger_index') - || server.hasLedger(self.message.ledger_index)) - && (!self.message.hasOwnProperty('ledger_index_min') - || self.message.ledger_index_min === -1 - || server.hasLedger(self.message.ledger_index_min)) - && (!self.message.hasOwnProperty('ledger_index_max') - || self.message.ledger_index_max === -1 - || server.hasLedger(self.message.ledger_index_max)); - }); - - // Apply iterator in parallel to connected servers, complete when the - // supplied filter function is satisfied once by a server's response - async.some(servers, iterator, complete); - - return this; -}; - -Request.prototype.cancel = function() { - this.removeAllListeners(); - this.on('error', function() {}); - - return this; -}; - -Request.prototype.setCallback = function(fn) { - if (typeof fn === 'function') { - this.callback(fn); - } - - return this; -}; - -Request.prototype.setReconnectTimeout = function(timeout) { - if (typeof timeout === 'number' && !isNaN(timeout)) { - this.reconnectTimeout = timeout; - } - - return this; -}; - -Request.prototype.callback = function(callback, successEvent, errorEvent) { - const self = this; - - if (typeof callback !== 'function') { - return this; - } - - if (typeof successEvent === 'string') { - this.successEvent = successEvent; - } - if (typeof errorEvent === 'string') { - this.errorEvent = errorEvent; - } - - let called = false; - - function requestError(error) { - if (!called) { - called = true; - - if (!(error instanceof RippleError)) { - callback.call(self, new RippleError(error)); - } else { - callback.call(self, error); - } - } - } - - function requestSuccess(message) { - if (!called) { - called = true; - callback.call(self, null, message); - } - } - - this.once(this.successEvent, requestSuccess); - this.once(this.errorEvent, requestError); - - if (!this.requested) { - this.request(); - } - - return this; -}; - -Request.prototype.setTimeout = function(delay) { - if (!_.isFinite(delay)) { - throw new Error('delay must be number'); - } - this._timeout = delay; - - return this; -}; - -Request.prototype.setServer = function(server) { - let selected = null; - - if (_.isString(server)) { - selected = _.find(this.remote._servers, s => s._url === server) || null; - } else if (_.isObject(server)) { - selected = server; - } - - this.server = selected; - return this; -}; - -Request.prototype.buildPath = function(build) { - if (this.remote.local_signing) { - throw new Error( - '`build_path` is completely ignored when doing local signing as ' - + '`Paths` is a component of the signed blob. The `tx_blob` is signed,' - + 'sealed and delivered, and the txn unmodified after'); - } - - if (build) { - this.message.build_path = true; - } else { - // ND: rippled currently intreprets the mere presence of `build_path` as the - // value being `truthy` - delete this.message.build_path; - } - - return this; -}; - -Request.prototype.ledgerChoose = function(current) { - if (current) { - this.message.ledger_index = this.remote._ledger_current_index; - } else { - this.message.ledger_hash = this.remote._ledger_hash; - } - - return this; -}; - -// Set the ledger for a request. -// - ledger_entry -// - transaction_entry -Request.prototype.ledgerHash = function(hash) { - this.message.ledger_hash = hash; - return this; -}; - -// Set the ledger_index for a request. -// - ledger_entry -Request.prototype.ledgerIndex = function(ledger_index) { - this.message.ledger_index = ledger_index; - return this; -}; - -/** - * Set either ledger_index or ledger_hash based on heuristic - * - * @param {Number|String} ledger - identifier - * @param {Object} options - - * @param {Number|String} defaultValue - default if `ledger` unspecifed - */ -Request.prototype.ledgerSelect = -Request.prototype.selectLedger = function(ledger, defaultValue) { - const selected = ledger || defaultValue; - - switch (selected) { - case 'current': - case 'closed': - case 'validated': - this.message.ledger_index = selected; - break; - default: - if (Number(selected) && isFinite(Number(selected))) { - this.message.ledger_index = Number(selected); - } else if (/^[A-F0-9]{64}$/.test(selected)) { - this.message.ledger_hash = selected; - } else if (selected !== undefined) { - throw new Error('unknown ledger format: ' + selected); - } - break; - } - return this; -}; - -Request.prototype.accountRoot = function(account) { - this.message.account_root = account; - return this; -}; - -Request.prototype.index = function(index) { - this.message.index = index; - return this; -}; - -// Provide the information id an offer. -// --> account -// --> seq : sequence number of transaction creating offer (integer) -Request.prototype.offerId = function(account, sequence) { - this.message.offer = { - account: account, - seq: sequence - }; - return this; -}; - -// --> index : ledger entry index. -Request.prototype.offerIndex = function(index) { - this.message.offer = index; - return this; -}; - -Request.prototype.secret = function(secret) { - if (secret) { - this.message.secret = secret; - } - return this; -}; - -Request.prototype.txHash = function(hash) { - this.message.tx_hash = hash; - return this; -}; - -Request.prototype.txJson = function(json) { - this.message.tx_json = json; - return this; -}; - -Request.prototype.txBlob = function(json) { - this.message.tx_blob = json; - return this; -}; - -Request.prototype.rippleState = function(account, issuer, currency) { - this.message.ripple_state = { - currency: currency, - accounts: [ - account, - issuer - ] - }; - return this; -}; - -Request.prototype.setAccounts = -Request.prototype.accounts = function(accountsIn, proposed) { - const accounts = Array.isArray(accountsIn) ? accountsIn : [accountsIn]; - - // Process accounts parameters - const processedAccounts = accounts.map(function(account) { - return account; - }); - - if (proposed) { - this.message.accounts_proposed = processedAccounts; - } else { - this.message.accounts = processedAccounts; - } - - return this; -}; - -Request.prototype.addAccount = function(account, proposed) { - if (Array.isArray(account)) { - account.forEach(this.addAccount, this); - return this; - } - - const processedAccount = account; - const prop = proposed === true ? 'accounts_proposed' : 'accounts'; - this.message[prop] = (this.message[prop] || []).concat(processedAccount); - - return this; -}; - -Request.prototype.setAccountsProposed = -Request.prototype.rtAccounts = -Request.prototype.accountsProposed = function(accounts) { - return this.accounts(accounts, true); -}; - -Request.prototype.addAccountProposed = function(account) { - if (Array.isArray(account)) { - account.forEach(this.addAccountProposed, this); - return this; - } - - return this.addAccount(account, true); -}; - -Request.prototype.setBooks = -Request.prototype.books = function(books, snapshot) { - // Reset list of books (this method overwrites the current list) - this.message.books = []; - - for (let i = 0, l = books.length; i < l; i++) { - const book = books[i]; - this.addBook(book, snapshot); - } - - return this; -}; - -Request.prototype.addBook = function(book, snapshot) { - if (Array.isArray(book)) { - book.forEach(this.addBook, this); - return this; - } - - const json = { }; - - function processSide(side) { - if (!book[side]) { - throw new Error('Missing ' + side); - } - - const obj = json[side] = { - currency: normalizeCurrency(book[side].currency) - }; - - if (obj.currency !== 'XRP') { - obj.issuer = book[side].issuer; - } - } - - ['taker_gets', 'taker_pays'].forEach(processSide); - - if (typeof snapshot !== 'boolean') { - json.snapshot = true; - } else if (snapshot) { - json.snapshot = true; - } else { - delete json.snapshot; - } - - if (book.both) { - json.both = true; - } - - this.message.books = (this.message.books || []).concat(json); - - return this; -}; - -Request.prototype.addStream = function(stream, values) { - if (Array.isArray(values)) { - switch (stream) { - case 'accounts': - this.addAccount(values); - break; - case 'accounts_proposed': - this.addAccountProposed(values); - break; - case 'books': - this.addBook(values); - break; - } - } else if (arguments.length > 1) { - for (const arg in arguments) { - this.addStream(arguments[arg]); - } - return this; - } - - if (!Array.isArray(this.message.streams)) { - this.message.streams = []; - } - - if (this.message.streams.indexOf(stream) === -1) { - this.message.streams.push(stream); - } - - return this; -}; - -exports.Request = Request; diff --git a/src/core/rippleerror.js b/src/core/rippleerror.js deleted file mode 100644 index f5f4e085..00000000 --- a/src/core/rippleerror.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -const util = require('util'); -const utils = require('./utils'); -const _ = require('lodash'); - -function RippleError(code?: any, message?: string) { - if (code instanceof Error) { - this.result = code; - this.result_message = code.message; - } else { - switch (typeof code) { - case 'object': - _.extend(this, code); - break; - - case 'string': - this.result = code; - this.result_message = message; - break; - } - } - - this.result = - _.get(this, ['remote', 'error'], this.result); - this.result_message = - _.get(this, ['remote', 'error_message'], this.result_message); - this.engine_result = this.result = (this.result || this.engine_result || - this.error || 'Error'); - this.engine_result_message = this.result_message = (this.result_message || - this.engine_result_message || this.error_message || this.result || 'Error'); - this.message = this.result_message; - - let stack; - - if (Boolean(Error.captureStackTrace)) { - Error.captureStackTrace(this, code || this); - } else { - stack = new Error().stack; - if (Boolean(stack)) { - this.stack = stack; - } - } -} - -util.inherits(RippleError, Error); - -RippleError.prototype.name = 'RippleError'; - - -RippleError.prototype.toString = function() { - let result = '[RippleError(' + this.result; - if (this.result_message && this.result_message !== this.result) { - result += ', ' + this.result_message; - } - result += ')]'; - return result; -}; - -/* - console.log in node uses util.inspect on object, and util.inspect allows - to cutomize it output: - https://nodejs.org/api/util.html#util_custom_inspect_function_on_objects -*/ -RippleError.prototype.inspect = function(depth) { - utils.unused(depth); - return this.toString(); -}; - - -exports.RippleError = RippleError; diff --git a/src/core/server.js b/src/core/server.js deleted file mode 100644 index f0a4fdc9..00000000 --- a/src/core/server.js +++ /dev/null @@ -1,992 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const assert = require('assert'); -const util = require('util'); -const url = require('url'); -const LRU = require('lru-cache'); -const HttpsProxyAgent = require('https-proxy-agent'); -const EventEmitter = require('events').EventEmitter; -const RippleError = require('./rippleerror').RippleError; -const Amount = require('./amount').Amount; -const RangeSet = require('./rangeset').RangeSet; -const log = require('./log').internal.sub('server'); - -/** - * @constructor Server - * - * @param {Remote} Reference to a Remote object - * @param {Object} Options - * @param {String} host - * @param {Number|String} port - * @param [Boolean] securec - */ - -function Server(remote, opts_) { - EventEmitter.call(this); - - const self = this; - let opts; - - if (typeof opts_ === 'string') { - const parsedUrl = url.parse(opts_); - opts = { - host: parsedUrl.hostname, - port: parsedUrl.port, - secure: (parsedUrl.protocol === 'ws:') ? false : true - }; - } else { - opts = opts_; - } - - if (typeof opts !== 'object') { - throw new TypeError('Server configuration is not an Object'); - } - - if (!Server.DOMAIN_RE.test(opts.host)) { - throw new Error( - 'Server host is malformed, use "host" and "port" server configuration'); - } - - if (typeof opts.secure !== 'boolean') { - opts.secure = true; - } - - if (!Boolean(opts.port)) { - opts.port = opts.secure ? 443 : 80; - } - - // We want to allow integer strings as valid port numbers for backward - // compatibility - opts.port = Number(opts.port); - if (isNaN(opts.port)) { - throw new TypeError('Server port must be a number'); - } - - if (opts.port < 1 || opts.port > 65535) { - throw new TypeError('Server "port" must be an integer in range 1-65535'); - } - - this._remote = remote; - this._opts = opts; - this._ws = undefined; - - this._connected = false; - this._shouldConnect = false; - this._state = 'offline'; - this._ledgerRanges = new RangeSet(); - this._ledgerMap = new LRU({max: 200}); - - this._id = 0; // request ID - this._retry = 0; - this._requests = {}; - - this._load_base = 256; - this._load_factor = 256; - - this._fee = 10; - this._fee_ref = 10; - this._fee_base = 10; - this._reserve_base = undefined; - this._reserve_inc = undefined; - this._fee_cushion = this._remote.fee_cushion; - - this._lastLedgerIndex = NaN; - this._lastLedgerClose = NaN; - - this._score = 0; - this._scoreWeights = { - ledgerclose: 5, - response: 1 - }; - - this._pubkey_node = ''; - - this._url = this._opts.url = (this._opts.secure ? 'wss://' : 'ws://') - + this._opts.host + ':' + this._opts.port; - - this.on('message', function onMessage(message) { - self._handleMessage(message); - }); - - this.on('response_subscribe', function onSubscribe(message) { - self._handleResponseSubscribe(message); - }); - - function setActivityInterval() { - const interval = self._checkActivity.bind(self); - self._activityInterval = setInterval(interval, 1000); - } - - this.on('disconnect', function onDisconnect() { - clearInterval(self._activityInterval); - self.once('ledger_closed', setActivityInterval); - }); - - this.once('ledger_closed', setActivityInterval); - - this._remote.on('ledger_closed', function onRemoteLedgerClose(ledger) { - self._updateScore('ledgerclose', ledger); - }); - - this.on('response_ping', function onPingResponse(message, request) { - _.noop(message); - self._updateScore('response', request); - }); - - this.on('load_changed', function onLoadChange(load) { - self._updateScore('loadchange', load); - }); - - this.on('connect', function() { - self.requestServerID(); - }); -} - -util.inherits(Server, EventEmitter); - -/* eslint-disable max-len */ -Server.DOMAIN_RE = /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|[-_]){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|[-_]){0,61}[0-9A-Za-z])?)*\.?$/; -/* eslint-enable max-len */ - -Server.TLS_ERRORS = [ - 'UNABLE_TO_GET_ISSUER_CERT', 'UNABLE_TO_GET_CRL', - 'UNABLE_TO_DECRYPT_CERT_SIGNATURE', 'UNABLE_TO_DECRYPT_CRL_SIGNATURE', - 'UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY', 'CERT_SIGNATURE_FAILURE', - 'CRL_SIGNATURE_FAILURE', 'CERT_NOT_YET_VALID', 'CERT_HAS_EXPIRED', - 'CRL_NOT_YET_VALID', 'CRL_HAS_EXPIRED', 'ERROR_IN_CERT_NOT_BEFORE_FIELD', - 'ERROR_IN_CERT_NOT_AFTER_FIELD', 'ERROR_IN_CRL_LAST_UPDATE_FIELD', - 'ERROR_IN_CRL_NEXT_UPDATE_FIELD', 'OUT_OF_MEM', - 'DEPTH_ZERO_SELF_SIGNED_CERT', 'SELF_SIGNED_CERT_IN_CHAIN', - 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY', 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', - 'CERT_CHAIN_TOO_LONG', 'CERT_REVOKED', 'INVALID_CA', - 'PATH_LENGTH_EXCEEDED', 'INVALID_PURPOSE', 'CERT_UNTRUSTED', - 'CERT_REJECTED' -]; - -/** - * Server states that we will treat as the server being online. - * - * Our requirements are that the server can process transactions and notify - * us of changes. - */ - -Server.onlineStates = [ - 'syncing', - 'tracking', - 'proposing', - 'validating', - 'full' -]; - -/** - * This is the final interface between client code and a socket connection to a - * `rippled` server. As such, this is a decent hook point to allow a WebSocket - * interface conforming object to be used as a basis to mock rippled. This - * avoids the need to bind a websocket server to a port and allows a more - * synchronous style of code to represent a client <-> server message sequence. - * We can also use this to log a message sequence to a buffer. - * - * @api private - */ - -Server.websocketConstructor = function() { - // We require this late, because websocket shims may be loaded after - // ripple-lib in the browser - return require('ws'); -}; - -/** - * Set server state - * - * @param {String} state - * @api private - */ - -Server.prototype._setState = function(state) { - if (state !== this._state) { - if (this._remote.trace) { - log.info(this.getServerID(), 'set_state:', state); - } - - this._state = state; - this.emit('state', state); - - switch (state) { - case 'online': - this._connected = true; - this._retry = 0; - this.emit('connect'); - break; - case 'offline': - this._connected = false; - this.emit('disconnect'); - break; - } - } -}; - -/** - * Check that server is still active. - * - * Server activity is determined by ledger_closed events. - * Maximum delay to receive a ledger_closed event is 20s. - * - * If server is inactive, reconnect - * - * @api private - */ - -Server.prototype._checkActivity = function() { - if (!this.isConnected()) { - return; - } - - if (isNaN(this._lastLedgerClose)) { - return; - } - - const delta = (Date.now() - this._lastLedgerClose); - - if (delta > (1000 * 25)) { - if (this._remote.trace) { - log.info(this.getServerID(), 'reconnect: activity delta:', delta); - } - this.reconnect(); - } -}; - -/** - * If server is not up-to-date, request server_info for getting pubkey_node - * & hostid information. Otherwise this information is available on the - * initial server subscribe response - */ - -Server.prototype.requestServerID = function() { - const self = this; - - if (this._pubkey_node) { - return; - } - - this.on('response_server_info', function setServerID(message) { - try { - self._pubkey_node = message.info.pubkey_node; - } catch (e) { - log.warn('Failed to get server pubkey_node', message); - } - }); - - const serverInfoRequest = this._remote.requestServerInfo(); - serverInfoRequest.on('error', function() {}); - this._request(serverInfoRequest); -}; - -/** - * Server maintains a score for request prioritization. - * - * The score is determined by various data including - * this server's lag to receive ledger_closed events, - * ping response time, and load(fee) change - * - * @param {String} type - * @param {Object} data - * @api private - */ - -Server.prototype._updateScore = function(type, data) { - if (!this.isConnected()) { - return; - } - - const weight = this._scoreWeights[type] || 1; - let delta; - - switch (type) { - case 'ledgerclose': - // Ledger lag - delta = data.ledger_index - this._lastLedgerIndex; - if (delta > 0) { - this._score += weight * delta; - } - break; - case 'response': - // Ping lag - // Servers are not pinged by default - delta = Math.floor((Date.now() - data.time) / 200); - this._score += weight * delta; - break; - case 'loadchange': - // Load/fee change - this._fee = Number(this._computeFee(10)); - break; - } - - if (this._score > 1e3) { - if (this._remote.trace) { - log.info(this.getServerID(), 'reconnect: score:', this._score); - } - this.reconnect(); - } -}; - -/** - * Get the server's remote address - * - * Incompatible with ripple-lib client build - */ - -Server.prototype.getRemoteAddress = -Server.prototype._remoteAddress = function() { - try { - return this._ws._socket.remoteAddress; - } catch (e) { - log.warn('Cannot get remote address. Does not work in browser'); - } -}; - -/** - * Get the server's hostid - */ - -Server.prototype.getHostID = Server.prototype.getServerID = function() { - return this._url + ' (' + (this._pubkey_node ? this._pubkey_node : '') + ')'; -}; - -/** - * Disconnect from rippled WebSocket server - * - * @api public - */ - -Server.prototype.disconnect = function() { - const self = this; - - if (!this.isConnected()) { - this.once('socket_open', function() { - self.disconnect(); - }); - return; - } - - // these need to be reset so that updateScore - // and checkActivity do not trigger reconnect - this._lastLedgerIndex = NaN; - this._lastLedgerClose = NaN; - this._score = 0; - this._shouldConnect = false; - this._ledgerRanges.reset(); - this._ledgerMap.reset(); - this._setState('offline'); - - if (this._ws) { - this._ws.close(); - } -}; - -/** - * Reconnect to rippled WebSocket server - * - * @api public - */ - -Server.prototype.reconnect = function() { - const self = this; - - function reconnect() { - self._shouldConnect = true; - self._retry = 0; - self.connect(); - } - - if (this._ws && this._shouldConnect) { - if (this.isConnected()) { - this.once('disconnect', reconnect); - this.disconnect(); - } else { - reconnect(); - } - } -}; - -/** - * Connect to rippled WebSocket server and subscribe to events that are - * internally requisite. Automatically retry connections with a gradual - * back-off - * - * @api public - */ - -Server.prototype.connect = function() { - const self = this; - const WebSocket = Server.websocketConstructor(); - - if (!WebSocket) { - throw new Error('No websocket support detected!'); - } - - // We don't connect if we believe we're already connected. This means we have - // recently received a message from the server and the WebSocket has not - // reported any issues either. If we do fail to ping or the connection drops, - // we will automatically reconnect. - if (this.isConnected()) { - return; - } - - // Ensure any existing socket is given the command to close first. - if (this._ws) { - this._ws.close(); - } - - if (this._remote.trace) { - log.info(this.getServerID(), 'connect'); - } - - const wsOptions = {}; - - if (!_.isUndefined(this._remote.proxy)) { - const proxyOptions = _.merge(url.parse(this._remote.proxy), { - secureEndpoint: /^wss/.test(this._opts.url) - }); - - wsOptions.agent = new HttpsProxyAgent(proxyOptions); - } - - if (!_.isUndefined(this._remote.basic_auth)) { - const authOptions = this._remote.basic_auth; - const auth = new Buffer(authOptions).toString('base64'); - wsOptions.headers = {Authorization: `Basic ${auth}`}; - } - - this._ws = new WebSocket(this._opts.url, wsOptions); - - const ws = this._ws; - - this._shouldConnect = true; - - self.emit('connecting'); - - ws.onmessage = function onMessage(msg) { - let message = msg.data; - - try { - message = JSON.parse(message); - } catch (e) { - const error = new RippleError('unexpected', - 'Unexpected response from server: ' + JSON.stringify(message)); - - self.emit('unexpected', message); - log.error(error); - return; - } - - 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 - const request = self._remote._serverPrepareSubscribe(self); - request.once('response', () => { - self._remote.removeListener('error', onRemoteError); - }); - self._request(request); - } - }; - - ws.onerror = function onError(e) { - if (ws === self._ws) { - self.emit('socket_error'); - - if (self._remote.trace) { - log.info(`${self.getServerID()} onerror: ${e.toString()}`); - } - - if (Server.TLS_ERRORS.includes(e.message)) { - throw e; - } - if (e.message.includes('unexpected')) { - throw e; - } - - // Most connection errors for WebSockets are conveyed as 'close' - // events with - // code 1006. This is done for security purposes and therefore unlikely to - // ever change. - - // This means that this handler is hardly ever called in practice. - // If it is, - // it probably means the server's WebSocket implementation is corrupt, or - // the connection is somehow producing corrupt data. - - // Most WebSocket applications simply log and ignore this error. Once we - // support for multiple servers, we may consider doing something like - // lowering this server's quality score. - - // However, in Node.js this event may be triggered instead of the close - // event, so we need to handle it. - self._handleClose(); - } - }; - - ws.onclose = function onClose() { - if (ws === self._ws) { - if (self._remote.trace) { - log.info(self.getServerID(), 'onclose:', ws.readyState); - } - self._handleClose(); - } - }; -}; - -/** - * Retry connection to rippled server - * - * @api private - */ - -Server.prototype._retryConnect = function() { - const self = this; - - this._retry += 1; - - /*eslint-disable */ - - const retryTimeout = (this._retry < 40) - // First, for 2 seconds: 20 times per second - ? (1000 / 20) - : (this._retry < 40 + 60) - // Then, for 1 minute: once per second - ? (1000) - : (this._retry < 40 + 60 + 60) - // Then, for 10 minutes: once every 10 seconds - ? (10 * 1000) - // Then: once every 30 seconds - : (30 * 1000); - - /*eslint-enable */ - - function connectionRetry() { - if (self._shouldConnect) { - if (self._remote.trace) { - log.info(self.getServerID(), 'retry', self._retry); - } - self.connect(); - } - } - - this._retryTimer = setTimeout(connectionRetry, retryTimeout); -}; - -/** - * Handle connection closes - * - * @api private - */ - -Server.prototype._handleClose = function() { - const ws = this._ws; - - // Prevent additional events from this socket - ws.onopen = ws.onerror = ws.onclose = ws.onmessage = _.noop; - - this.emit('socket_close'); - this._setState('offline'); - - if (this._shouldConnect) { - this._retryConnect(); - } -}; - -/** - * Handle incoming messages from rippled WebSocket server - * - * @param {JSON-parseable} message - * @api private - */ - -Server.prototype._handleMessage = function(message) { - if (!Server.isValidMessage(message)) { - this.emit('unexpected', message); - return; - } - - switch (message.type) { - case 'ledgerClosed': - this._handleLedgerClosed(message); - break; - case 'serverStatus': - this._handleServerStatus(message); - break; - case 'response': - this._handleResponse(message); - break; - case 'path_find': - this._handlePathFind(message); - break; - } -}; - -Server.prototype._handleLedgerClosed = function(message) { - this._lastLedgerIndex = message.ledger_index; - this._lastLedgerClose = Date.now(); - this._ledgerRanges.addValue(message.ledger_index); - this._ledgerMap.set(message.ledger_hash, message.ledger_index); - this.emit('ledger_closed', message); -}; - -Server.prototype._handleServerStatus = function(message) { - // This message is only received when online. - // As we are connected, it is the definitive final state. - const isOnline = _.includes(Server.onlineStates, message.server_status); - - this._setState(isOnline ? 'online' : 'offline'); - - if (!Server.isLoadStatus(message)) { - return; - } - - this.emit('load', message, this); - this._remote.emit('load', message, this); - - const loadChanged = message.load_base !== this._load_base - || message.load_factor !== this._load_factor; - - if (loadChanged) { - this._load_base = message.load_base; - this._load_factor = message.load_factor; - this.emit('load_changed', message, this); - this._remote.emit('load_changed', message, this); - } -}; - -Server.prototype._handleResponse = function(message) { - // A response to a request. - const request = this._requests[message.id]; - - delete this._requests[message.id]; - - if (!request) { - if (this._remote.trace) { - log.info(this.getServerID(), 'UNEXPECTED:', message); - } - return; - } - - if (message.status === 'success') { - if (this._remote.trace) { - log.info(this.getServerID(), 'response:', message); - } - - const command = request.message.command; - const result = message.result; - const responseEvent = 'response_' + command; - - request.emit('success', result, this); - - [this, this._remote].forEach(function(emitter) { - emitter.emit(responseEvent, result, request, message); - }); - } else if (message.error) { - if (this._remote.trace) { - log.info(this.getServerID(), 'error:', message); - } - - request.emit('error', { - error: 'remoteError', - error_message: 'Remote reported an error.', - remote: message - }, this); - } - request.emit('response', message, this); -}; - -Server.prototype._handlePathFind = function(message) { - if (this._remote.trace) { - log.info(this.getServerID(), 'path_find:', message); - } -}; - -/** - * Handle initial subscription response message. The server is considered - * `connected` after it has received a response to initial subscription to - * ledger and server streams - * - * @param {Object} message - * @api private - */ - -Server.prototype._handleResponseSubscribe = function(message) { - if (this.isConnected()) { - // This function only concerns initializing the server's internal - // state after a connection - return; - } - - if (!this._remote.allow_partial_history - && !Server.hasFullLedgerHistory(message)) { - // Server has partial history and Remote has been configured to disallow - // servers with incomplete history - this.reconnect(); - return; - } - - if (message.pubkey_node) { - // pubkey_node is used to identify the server - this._pubkey_node = message.pubkey_node; - } - - if (Server.isLoadStatus(message)) { - this._load_base = message.load_base || 256; - this._load_factor = message.load_factor || 256; - this._fee_ref = message.fee_ref || 10; - this._fee_base = message.fee_base || 10; - this._reserve_base = message.reserve_base; - this._reserve_inc = message.reserve_inc; - } - - if (message.validated_ledgers) { - // Add validated ledgers to ledger range set - this._ledgerRanges.parseAndAddRanges(message.validated_ledgers); - } - - if (_.includes(Server.onlineStates, message.server_status)) { - this._setState('online'); - } -}; - -/** - * Check that server message indicates that server has complete ledger history - * - * @param {Object} message - * @return {Boolean} - */ - -Server.hasFullLedgerHistory = function(message) { - return (typeof message === 'object') - && (message.server_status === 'full') - && (typeof message.validated_ledgers === 'string') - && (message.validated_ledgers.split('-').length === 2); -}; - -/** - * Check that received message from rippled is valid - * - * @param {Object} message - * @return {Boolean} - */ - -Server.isValidMessage = function(message) { - return (typeof message === 'object') - && (typeof message.type === 'string'); -}; - -/** - * Check that received serverStatus message contains load status information - * - * @param {Object} message - * @return {Boolean} - */ - -Server.isLoadStatus = function(message) { - return (typeof message === 'object') - && (typeof message.load_base === 'number') - && (typeof message.load_factor === 'number'); -}; - -/** - * Send JSON message to rippled WebSocket server - * - * @param {JSON-Stringifiable} message - * @api private - */ - -Server.prototype._sendMessage = function(message) { - if (this._ws) { - if (this._remote.trace) { - log.info(this.getServerID(), 'request:', 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); - }); - } - }); - } -}; - -/** - * Submit a Request object - * - * Requests are indexed by message ID, which is repeated in the response from - * rippled WebSocket server - * - * @param {Request} request - * @api private - */ - -Server.prototype._request = function(request) { - const self = this; - - // Only bother if we are still connected. - if (!this._ws) { - if (this._remote.trace) { - log.info(this.getServerID(), 'request: DROPPING:', request.message); - } - return; - } - - request.server = this; - request.message.id = this._id; - request.time = Date.now(); - - this._requests[request.message.id] = request; - - // Advance message ID - this._id++; - - function sendRequest() { - self._sendMessage(request.message); - } - - const isOpen = this._ws.readyState === 1; - const isSubscribeRequest = request && request.message.command === 'subscribe'; - - if (this.isConnected() || (isOpen && isSubscribeRequest)) { - sendRequest(); - } else { - this.once('connect', sendRequest); - } -}; - -/** - * Get server connected status - * - * @return boolean - */ - -Server.prototype.isConnected = Server.prototype._isConnected = function() { - return this._connected; -}; - -/** - * Calculate transaction fee - * - * @param {Transaction|Number} Fee units for a provided transaction - * @return {String} Final fee in XRP for specified number of fee units - * @api private - */ - -Server.prototype._computeFee = function(feeUnits) { - if (isNaN(feeUnits)) { - throw new Error('Invalid argument'); - } - - return this._feeTx(Number(feeUnits)).to_json(); -}; - -/** - * Calculate a transaction fee for a number of tx fee units. - * - * This takes into account the last known network and local load fees. - * - * @param {Number} Fee units for a provided transaction - * @return {Amount} Final fee in XRP for specified number of fee units. - */ - -Server.prototype._feeTx = function(units) { - const fee_unit = this._feeTxUnit(); - return Amount.from_json(String(Math.ceil(units * fee_unit))); -}; - -/** - * Get the current recommended transaction fee unit. - * - * Multiply this value with the number of fee units in order to calculate the - * recommended fee for the transaction you are trying to submit. - * - * @return {Number} Recommended amount for one fee unit as float. - */ - -Server.prototype._feeTxUnit = function() { - let fee_unit = this._fee_base / this._fee_ref; - - // Apply load fees - fee_unit *= this._load_factor / this._load_base; - - // Apply fee cushion (a safety margin in case fees rise since - // we were last updated) - fee_unit *= this._fee_cushion; - - return fee_unit; -}; - -/** - * Get the current recommended reserve base. - * - * Returns the base reserve with load fees and safety margin applied. - */ - -Server.prototype._reserve = function(ownerCount) { - // We should be in a valid state before calling this method - assert(this._reserve_base && this._reserve_inc); - - const reserve_base = Amount.from_json(String(this._reserve_base)); - const reserve_inc = Amount.from_json(String(this._reserve_inc)); - const owner_count = ownerCount || 0; - - if (owner_count < 0) { - throw new Error('Owner count must not be negative.'); - } - - return reserve_base.add(reserve_inc.multiply(owner_count)); -}; - -/** - * Check that server has seen closed ledger - * - * @param {string|number} ledger hash or index - * @return boolean - */ - -Server.prototype.hasLedger = function(ledger) { - let result = false; - - if (typeof ledger === 'string' && /^[A-F0-9]{64}$/.test(ledger)) { - result = this._ledgerMap.has(ledger); - } else if (ledger !== null && !isNaN(ledger)) { - result = this._ledgerRanges.containsValue(ledger); - } - - return result; -}; - -Server.prototype.hasLedgerRange = function(startLedger, endLedger) { - return this._ledgerRanges.containsRange(startLedger, endLedger); -}; - -/** - * Get ledger index of last seen validated ledger - * - * @return number - */ - -Server.prototype.getLastLedger = -Server.prototype.getLastLedgerIndex = function() { - return this._lastLedgerIndex; -}; - -exports.Server = Server; - -// vim:sw=2:sts=2:ts=8:et diff --git a/src/core/transaction.js b/src/core/transaction.js deleted file mode 100644 index f4f08e9c..00000000 --- a/src/core/transaction.js +++ /dev/null @@ -1,1653 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const util = require('util'); -const _ = require('lodash'); -const {deriveKeypair, sign} = require('ripple-keypairs'); -const EventEmitter = require('events').EventEmitter; -const utils = require('./utils'); -const sjclcodec = require('sjcl-codec'); -const Amount = require('./amount').Amount; -const {normalizeCurrency, isValidCurrency} = require('./currency'); -const RippleError = require('./rippleerror').RippleError; -const log = require('./log').internal.sub('transaction'); -const {isValidAddress, decodeAddress} = require('ripple-address-codec'); -const binary = require('ripple-binary-codec'); -const {computeTransactionHash, computeTransactionSigningHash} - = require('ripple-hashes'); - -/** - * @constructor Transaction - * - * Notes: - * All transactions including those with local and malformed errors may be - * forwarded anyway. - * - * A malicous server can: - * - may or may not forward - * - give any result - * + it may declare something correct as incorrect or something incorrect - * as correct - * + it may not communicate with the rest of the network - */ - -function Transaction(remote) { - EventEmitter.call(this); - - const self = this; - const remoteExists = (typeof remote === 'object'); - - this.remote = remote; - this.tx_json = {Flags: 0}; - this._secret = undefined; - this._build_path = false; - this._should_resubmit = remoteExists - ? this.remote.automatic_resubmission - : true; - this._maxFee = remoteExists ? this.remote.max_fee : undefined; - this._lastLedgerOffset = remoteExists ? this.remote.last_ledger_offset : 3; - this.state = 'unsubmitted'; - this.finalized = false; - this.previousSigningHash = undefined; - this.submitIndex = undefined; - this.canonical = remoteExists ? this.remote.canonical_signing : true; - this.submittedIDs = [ ]; - this.attempts = 0; - this.submissions = 0; - this.responses = 0; - - this.once('success', function(message) { - // Transaction definitively succeeded - self.setState('validated'); - self.finalize(message); - if (self._successHandler) { - self._successHandler(message); - } - }); - - this.once('error', function(message) { - // Transaction definitively failed - self.setState('failed'); - self.finalize(message); - if (self._errorHandler) { - self._errorHandler(message); - } - }); - - this.once('submitted', function() { - // Transaction was submitted to the network - self.setState('submitted'); - }); - - this.once('proposed', function() { - // Transaction was submitted successfully to the network - self.setState('pending'); - }); -} - -util.inherits(Transaction, EventEmitter); - -// This is currently a constant in rippled known as the "base reference" -// https://wiki.ripple.com/Transaction_Fee#Base_Fees -Transaction.fee_units = { - default: 10 -}; - -Transaction.flags = { - // Universal flags can apply to any transaction type - Universal: { - FullyCanonicalSig: 0x80000000 - }, - - AccountSet: { - RequireDestTag: 0x00010000, - OptionalDestTag: 0x00020000, - RequireAuth: 0x00040000, - OptionalAuth: 0x00080000, - DisallowXRP: 0x00100000, - AllowXRP: 0x00200000 - }, - - TrustSet: { - SetAuth: 0x00010000, - NoRipple: 0x00020000, - SetNoRipple: 0x00020000, - ClearNoRipple: 0x00040000, - SetFreeze: 0x00100000, - ClearFreeze: 0x00200000 - }, - - OfferCreate: { - Passive: 0x00010000, - ImmediateOrCancel: 0x00020000, - FillOrKill: 0x00040000, - Sell: 0x00080000 - }, - - Payment: { - NoRippleDirect: 0x00010000, - PartialPayment: 0x00020000, - LimitQuality: 0x00040000 - } -}; - -// The following are integer (as opposed to bit) flags -// that can be set for particular transactions in the -// SetFlag or ClearFlag field -Transaction.set_clear_flags = { - AccountSet: { - asfRequireDest: 1, - asfRequireAuth: 2, - asfDisallowXRP: 3, - asfDisableMaster: 4, - asfAccountTxnID: 5, - asfNoFreeze: 6, - asfGlobalFreeze: 7, - asfDefaultRipple: 8 - } -}; - -Transaction.MEMO_TYPES = {}; - -/* eslint-disable max-len */ - -// URL characters per RFC 3986 -Transaction.MEMO_REGEX = /^[0-9a-zA-Z-\.\_\~\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\%]+$/; -/* eslint-enable max-len */ - -Transaction.formats = require('./binformat').tx; - -Transaction.prototype.consts = { - telLOCAL_ERROR: -399, - temMALFORMED: -299, - tefFAILURE: -199, - terRETRY: -99, - tesSUCCESS: 0, - tecCLAIMED: 100 -}; - -Transaction.prototype.isTelLocal = function(ter) { - return ter >= this.consts.telLOCAL_ERROR && ter < this.consts.temMALFORMED; -}; - -Transaction.prototype.isTemMalformed = function(ter) { - return ter >= this.consts.temMALFORMED && ter < this.consts.tefFAILURE; -}; - -Transaction.prototype.isTefFailure = function(ter) { - return ter >= this.consts.tefFAILURE && ter < this.consts.terRETRY; -}; - -Transaction.prototype.isTerRetry = function(ter) { - return ter >= this.consts.terRETRY && ter < this.consts.tesSUCCESS; -}; - -Transaction.prototype.isTepSuccess = function(ter) { - return ter >= this.consts.tesSUCCESS; -}; - -Transaction.prototype.isTecClaimed = function(ter) { - return ter >= this.consts.tecCLAIMED; -}; - -Transaction.prototype.isRejected = function(ter) { - return this.isTelLocal(ter) || - this.isTemMalformed(ter) || - this.isTefFailure(ter); -}; - -Transaction.from_json = function(j) { - return (new Transaction()).setJson(j); -}; - -Transaction.prototype.setJson = -Transaction.prototype.parseJson = function(v) { - this.tx_json = _.merge({}, v); - return this; -}; - -/** - * Set state on the condition that the state is different - * - * @param {String} state - */ - -Transaction.prototype.setState = function(state) { - if (this.state !== state) { - this.state = state; - this.emit('state', state); - } -}; - -Transaction.prototype.setResubmittable = function(v) { - if (typeof v === 'boolean') { - this._should_resubmit = v; - } -}; -Transaction.prototype.isResubmittable = function() { - return this._should_resubmit; -}; - -/** - * Finalize transaction. This will prevent future activity - * - * @param {Object} message - * @api private - */ - -Transaction.prototype.finalize = function(message) { - this.finalized = true; - - if (this.result) { - this.result.ledger_index = message.ledger_index; - this.result.ledger_hash = message.ledger_hash; - } else { - this.result = message; - this.result.tx_json = this.tx_json; - } - - this.emit('cleanup'); - this.emit('final', message); - - if (this.remote && this.remote.trace) { - log.info('transaction finalized:', - this.tx_json, this.getManager()._pending.getLength()); - } - - return this; -}; - -/** - * Get transaction Account - * - * @return {Account} - */ - -Transaction.prototype.getAccount = function() { - return this.tx_json.Account; -}; - -/** - * Get TransactionType - * - * @return {String} - */ - -Transaction.prototype.getType = -Transaction.prototype.getTransactionType = function() { - return this.tx_json.TransactionType; -}; - -/** - * Get transaction TransactionManager - * - * @param [String] account - * @return {TransactionManager] - */ - -Transaction.prototype.getManager = function(account) { - if (!this.remote) { - return undefined; - } - - return this.remote.account(account || this.getAccount())._transactionManager; -}; - -/** - * Get transaction secret - * - * @param [String] account - */ - -Transaction.prototype.getSecret = -Transaction.prototype._accountSecret = function(account) { - if (!this.remote) { - return undefined; - } - - return this.remote.secrets[account || this.getAccount()]; -}; - -/** - * Returns the number of fee units this transaction will cost. - * - * Each Ripple transaction based on its type and makeup costs a certain number - * of fee units. The fee units are calculated on a per-server basis based on the - * current load on both the network and the server. - * - * @see https://ripple.com/wiki/Transaction_Fee - * - * @return {Number} Number of fee units for this transaction. - */ - -Transaction.prototype._getFeeUnits = -Transaction.prototype.feeUnits = function() { - return Transaction.fee_units.default; -}; - -/** - * Compute median server fee - * - * @return {String} median fee - */ - -Transaction.prototype._computeFee = function() { - if (!this.remote) { - return undefined; - } - - const servers = this.remote._servers; - const fees = [ ]; - - for (let i = 0; i < servers.length; i++) { - const server = servers[i]; - if (server.isConnected()) { - fees.push(Number(server._computeFee(this._getFeeUnits()))); - } - } - - switch (fees.length) { - case 0: return undefined; - case 1: return String(fees[0]); - } - - fees.sort(function ascending(a, b) { - if (a > b) { - return 1; - } else if (a < b) { - return -1; - } - return 0; - }); - - const midInd = Math.floor(fees.length / 2); - const median = fees.length % 2 === 0 - ? Math.floor(0.5 + (fees[midInd] + fees[midInd - 1]) / 2) - : fees[midInd]; - - return String(median); -}; - -/** - * Attempts to complete the transaction for submission. - * - * This function seeks to fill out certain fields, such as Fee and - * SigningPubKey, which can be determined by the library based on network - * information and other fields. - * - * @return {Boolean|Transaction} If succeeded, return transaction. Otherwise - * return `false` - */ - -Transaction.prototype.err = function(error, errorMessage) { - this.emit('error', new RippleError(error, errorMessage)); - return false; -}; - -Transaction.prototype.complete = function() { - const hasMultiSigners = this.hasMultiSigners(); - - if (!hasMultiSigners) { - // Auto-fill the secret - this._secret = this._secret || this.getSecret(); - - 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)) { - if (hasMultiSigners) { - this.setSigningPubKey(''); - } else { - try { - this.setSigningPubKey(this.getSigningPubKey()); - } catch (e) { - return this.err('tejSecretInvalid', 'Invalid secret'); - } - } - } - - // Auto-fill transaction Fee - if (_.isUndefined(this.tx_json.Fee)) { - if (this.remote && (this.remote.local_fee || !this.remote.trusted)) { - const computedFee = this._computeFee(); - - if (!computedFee) { - // Unable to compute fee due to no connected servers - return this.err('tejUnconnected'); - } - - this.tx_json.Fee = computedFee; - } - } - - if (Number(this.tx_json.Fee) > this._maxFee) { - return this.err('tejMaxFeeExceeded', 'Max fee exceeded'); - } - - // Set canonical flag - this enables canonicalized signature checking - this.setCanonicalFlag(); - - return this.tx_json; -}; - -Transaction.prototype.getSigningPubKey = function(secret) { - return deriveKeypair(secret || this._secret).publicKey; -}; - -Transaction.prototype.setSigningPubKey = function(key) { - this.tx_json.SigningPubKey = key; - return this; -}; - -Transaction.prototype.setCanonicalFlag = function() { - if (this.remote && this.remote.local_signing && this.canonical) { - this.tx_json.Flags |= Transaction.flags.Universal.FullyCanonicalSig; - - // JavaScript converts operands to 32-bit signed ints before doing bitwise - // operations. We need to convert it back to an unsigned int. - this.tx_json.Flags = this.tx_json.Flags >>> 0; - } - - return this; -}; - -Transaction.prototype.serialize = function() { - return binary.encode(this.tx_json); -}; - -Transaction.prototype.signingHash = function() { - return computeTransactionSigningHash(this.tx_json); -}; - -Transaction.prototype.signingData = function() { - return binary.encodeForSigning(this.tx_json); -}; - -Transaction.prototype.multiSigningData = function(account) { - return binary.encodeForMultisigning(this.tx_json, account); -}; - -Transaction.prototype.hash = function() { - return computeTransactionHash(this.tx_json); -}; - -Transaction.prototype.sign = function(secret) { - if (this.hasMultiSigners()) { - return this; - } - - const prev_sig = this.tx_json.TxnSignature; - delete this.tx_json.TxnSignature; - const hash = this.signingHash(); - - // If the hash is the same, we can re-use the previous signature - if (prev_sig && hash === this.previousSigningHash) { - this.tx_json.TxnSignature = prev_sig; - return this; - } - - const keypair = deriveKeypair(secret || this._secret); - this.tx_json.TxnSignature = sign(this.signingData(), keypair.privateKey); - this.previousSigningHash = hash; - - return this; -}; - -/** - * Add an ID to cached list of submitted IDs - * - * @param {String} transaction id - * @api private - */ - -Transaction.prototype.addId = function(id) { - if (!_.contains(this.submittedIDs, id)) { - this.submittedIDs.unshift(id); - } -}; - -/** - * Find ID within cached received (validated) IDs. If this transaction has - * an ID that is within the cache, it has been seen validated, so return the - * received message - * - * @param {Object} cache - * @return {Object} message - * @api private - */ - -Transaction.prototype.findId = function(cache) { - const cachedTransactionID = _.detect(this.submittedIDs, function(id) { - return cache.hasOwnProperty(id); - }); - return cache[cachedTransactionID]; -}; - -/** - * Set client ID. This is an identifier specified by the user of the API to - * identify a transaction in the event of a disconnect. It is not currently - * persisted in the transaction itself, but used offline for identification. - * In applications that require high reliability, client-specified ID should - * be persisted such that one could map it to submitted transactions. Use - * .summary() for a consistent transaction summary output for persisitng. In - * the future, this ID may be stored in the transaction itself (in the ledger) - * - * @param {String} id - */ - -Transaction.prototype.setClientID = -Transaction.prototype.clientID = function(id) { - if (typeof id === 'string') { - this._clientID = id; - } - return this; -}; - -Transaction.prototype.setLastLedgerSequenceOffset = function(offset) { - this._lastLedgerOffset = offset; -}; - -Transaction.prototype.getLastLedgerSequenceOffset = function() { - return this._lastLedgerOffset; -}; - -Transaction.prototype.lastLedger = -Transaction.prototype.setLastLedger = -Transaction.prototype.setLastLedgerSequence = function(sequence) { - if (!_.isUndefined(sequence)) { - this._setUInt32('LastLedgerSequence', sequence); - } else { - // Autofill LastLedgerSequence - assert(this.remote, 'Unable to set LastLedgerSequence, missing Remote'); - - this._setUInt32('LastLedgerSequence', - this.remote.getLedgerSequenceSync() + 1 - + this.getLastLedgerSequenceOffset()); - } - - this._setLastLedger = true; - - return this; -}; - -/** - * Set max fee. Submission will abort if this is exceeded. Specified fee must - * be >= 0. - * - * @param {Number} fee The proposed fee - */ - -Transaction.prototype.setMaxFee = -Transaction.prototype.maxFee = function(fee) { - if (typeof fee === 'number' && fee >= 0) { - this._setMaxFee = true; - this._maxFee = fee; - } - return this; -}; - -/* - * Set the fee user will pay to the network for submitting this transaction. - * Specified fee must be >= 0. - * - * @param {Number} fee The proposed fee - * - * @returns {Transaction} calling instance for chaining - */ -Transaction.prototype.setFixedFee = function(fee) { - return this.setFee(fee, {fixed: true}); -}; - -Transaction.prototype.setFee = function(fee, options = {}) { - if (_.isNumber(fee) && fee >= 0) { - this.tx_json.Fee = String(fee); - if (options.fixed) { - this._setFixedFee = true; - } - } - - return this; -}; - -Transaction.prototype.setSequence = function(sequence) { - if (_.isNumber(sequence)) { - this._setUInt32('Sequence', sequence); - this._setSequence = true; - } - - return this; -}; - -/** - * Set secret If the secret has been set with Remote.setSecret, it does not - * need to be provided - * - * @param {String} secret - */ - -Transaction.prototype.setSecret = -Transaction.prototype.secret = function(secret) { - if (typeof secret === 'string') { - this._secret = secret; - } - return this; -}; - -Transaction.prototype.setType = function(type) { - if (_.isUndefined(Transaction.formats, type)) { - throw new Error('TransactionType must be a valid transaction type'); - } - - this.tx_json.TransactionType = type; - - return this; -}; - -Transaction.prototype._setUInt32 = function(name, value, options_) { - const options = _.merge({}, options_); - const isValidUInt32 = typeof value === 'number' - && value >= 0 && value < Math.pow(256, 4); - - if (!isValidUInt32) { - throw new Error(name + ' must be a valid UInt32'); - } - if (!_.isUndefined(options.min_value) && value < options.min_value) { - throw new Error(name + ' must be >= ' + options.min_value); - } - - this.tx_json[name] = value; - - return this; -}; - -/** - * Set SourceTag - * - * @param {Number} source tag - */ - -Transaction.prototype.setSourceTag = -Transaction.prototype.sourceTag = function(tag) { - return this._setUInt32('SourceTag', tag); -}; - -Transaction.prototype._setAccount = function(name, value) { - if (!isValidAddress(value)) { - throw new Error(name + ' must be a valid account'); - } - this.tx_json[name] = value; - return this; -}; - -Transaction.prototype.setAccount = function(account) { - return this._setAccount('Account', account); -}; - -Transaction.prototype._setAmount = function(name, amount, options_) { - const options = _.merge({no_native: false}, options_); - const parsedAmount = Amount.from_json(amount); - - if (parsedAmount.is_negative()) { - throw new Error(name + ' value must be non-negative'); - } - - const isNative = parsedAmount.is_native(); - - if (isNative && options.no_native) { - throw new Error(name + ' must be a non-native amount'); - } - if (!(isNative || isValidCurrency(parsedAmount.currency()))) { - throw new Error(name + ' must have a valid currency'); - } - if (!(isNative || isValidAddress(parsedAmount.issuer()))) { - throw new Error(name + ' must have a valid issuer'); - } - - this.tx_json[name] = parsedAmount.to_json(); - - return this; -}; - -Transaction.prototype._setHash256 = function(name, value, options_) { - if (typeof value !== 'string') { - throw new Error(name + ' must be a valid Hash256'); - } - - const options = _.merge({pad: false}, options_); - let hash256 = value; - - if (options.pad) { - while (hash256.length < 64) { - hash256 += '0'; - } - } - - if (!/^[0-9A-Fa-f]{64}$/.test(hash256)) { - throw new Error(name + ' must be a valid Hash256'); - } - - this.tx_json[name] = hash256; - - return this; -}; - -Transaction.prototype.setAccountTxnID = -Transaction.prototype.accountTxnID = function(id) { - return this._setHash256('AccountTxnID', id); -}; - -/** - * Set Flags. You may specify flags as a number, as the string name of the - * flag, or as an array of strings. - * - * setFlags(Transaction.flags.AccountSet.RequireDestTag) - * setFlags('RequireDestTag') - * setFlags('RequireDestTag', 'RequireAuth') - * setFlags([ 'RequireDestTag', 'RequireAuth' ]) - * - * @param {Number|String|Array} flags - */ - -Transaction.prototype.setFlags = function(flags) { - if (flags === undefined) { - return this; - } - - if (typeof flags === 'number') { - this.tx_json.Flags = flags; - return this; - } - - const transaction_flags = Transaction.flags[this.getType()] || { }; - const flag_set = Array.isArray(flags) ? flags : [].slice.call(arguments); - - for (let i = 0, l = flag_set.length; i < l; i++) { - const flag = flag_set[i]; - - if (transaction_flags.hasOwnProperty(flag)) { - this.tx_json.Flags += transaction_flags[flag]; - } else { - // XXX Should throw? - this.emit('error', new RippleError('tejInvalidFlag')); - return this; - } - } - - return this; -}; - -function convertStringToHex(string) { - const utf8String = sjclcodec.utf8String.toBits(string); - return sjclcodec.hex.fromBits(utf8String).toUpperCase(); -} - -/** - * Add a Memo to transaction. - * - * @param [String] memoType - * - describes what the data represents, must contain valid URL characters - * @param [String] memoFormat - * - describes what format the data is in, MIME type, must contain valid URL - * - characters - * @param [String] memoData - * - data for the memo, can be any JS object. Any object other than string will - * be stringified (JSON) for transport - */ - -Transaction.prototype.addMemo = function(options_) { - let options; - - if (typeof options_ === 'object') { - options = _.merge({}, options_); - } else { - options = { - memoType: arguments[0], - memoFormat: arguments[1], - memoData: arguments[2] - }; - } - - const memo = {}; - const memoRegex = Transaction.MEMO_REGEX; - let memoType = options.memoType; - const memoFormat = options.memoFormat; - let memoData = options.memoData; - - if (memoType) { - if (!(_.isString(memoType) && memoRegex.test(memoType))) { - throw new Error( - 'MemoType must be a string containing only valid URL characters'); - } - if (Transaction.MEMO_TYPES[memoType]) { - // XXX Maybe in the future we want a schema validator for - // memo types - memoType = Transaction.MEMO_TYPES[memoType]; - } - memo.MemoType = convertStringToHex(memoType); - } - - if (memoFormat) { - if (!(_.isString(memoFormat) && memoRegex.test(memoFormat))) { - throw new Error( - 'MemoFormat must be a string containing only valid URL characters'); - } - - memo.MemoFormat = convertStringToHex(memoFormat); - } - - if (memoData) { - if (typeof memoData !== 'string') { - if (memoFormat.toLowerCase() === 'json') { - try { - memoData = JSON.stringify(memoData); - } catch (e) { - throw new Error( - 'MemoFormat json with invalid JSON in MemoData field'); - } - } else { - throw new Error( - 'MemoData can only be a JSON object with a valid json MemoFormat'); - } - } - - memo.MemoData = convertStringToHex(memoData); - } - - this.tx_json.Memos = (this.tx_json.Memos || []).concat({Memo: memo}); - - return this; -}; - -/** - * Construct an 'AccountSet' transaction - * - * Note that bit flags can be set using the .setFlags() method but for - * 'AccountSet' transactions there is an additional way to modify AccountRoot - * flags. The values available for the SetFlag and ClearFlag are as follows: - * - * asfRequireDest: Require a destination tag - * asfRequireAuth: Authorization is required to extend trust - * asfDisallowXRP: XRP should not be sent to this account - * asfDisableMaster: Disallow use of the master key - * asfNoFreeze: Permanently give up the ability to freeze individual - * trust lines. This flag can never be cleared. - * asfGlobalFreeze: Freeze all assets issued by this account - * - * @param [String] set flag - * @param [String] clear flag - */ - -Transaction.prototype.accountSet = function(options_) { - let options; - - if (typeof options_ === 'object') { - options = _.merge({}, options_); - - if (_.isUndefined(options.account)) { - options.account = options.src; - } - if (_.isUndefined(options.set_flag)) { - options.set_flag = options.set; - } - if (_.isUndefined(options.clear_flag)) { - options.clear_flag = options.clear; - } - } else { - options = { - account: arguments[0], - set_flag: arguments[1], - clear_flag: arguments[2] - }; - } - - this.setType('AccountSet'); - this.setAccount(options.account); - - if (!_.isUndefined(options.set_flag)) { - this.setSetFlag(options.set_flag); - } - if (!_.isUndefined(options.clear_flag)) { - this.setClearFlag(options.clear_flag); - } - - return this; -}; - -Transaction.prototype.setAccountSetFlag = function(name, value) { - const accountSetFlags = Transaction.set_clear_flags.AccountSet; - let flagValue = value; - - if (typeof flagValue === 'string') { - flagValue = /^asf/.test(flagValue) - ? accountSetFlags[flagValue] - : accountSetFlags['asf' + flagValue]; - } - - if (!_.contains(_.values(accountSetFlags), flagValue)) { - throw new Error(name + ' must be a valid AccountSet flag'); - } - - this.tx_json[name] = flagValue; - - return this; -}; - -Transaction.prototype.setSetFlag = function(flag) { - return this.setAccountSetFlag('SetFlag', flag); -}; - -Transaction.prototype.setClearFlag = function(flag) { - return this.setAccountSetFlag('ClearFlag', flag); -}; - -/** - * Set TransferRate for AccountSet - * - * @param {Number} transfer rate - */ - -Transaction.prototype.setTransferRate = -Transaction.prototype.transferRate = function(rate) { - const transferRate = rate; - - if (transferRate === 0) { - // Clear TransferRate - this.tx_json.TransferRate = transferRate; - return this; - } - - // if (rate >= 1 && rate < 2) { - // transferRate *= 1e9; - // } - - return this._setUInt32('TransferRate', transferRate, {min_value: 1e9}); -}; - -/** - * Construct a 'SetRegularKey' transaction - * - * If the RegularKey is set, the private key that corresponds to it can be - * used to sign transactions instead of the master key - * - * The RegularKey must be a valid Ripple Address, or a Hash160 of the public - * key corresponding to the new private signing key. - * - * @param {String} account - * @param {String} regular key - */ - -Transaction.prototype.setRegularKey = function(options_) { - let options; - - if (typeof options_ === 'object') { - options = _.merge({}, options_); - - if (_.isUndefined(options.account)) { - options.account = options.src; - } - } else { - options = { - account: arguments[0], - regular_key: arguments[1] - }; - } - - this.setType('SetRegularKey'); - this.setAccount(options.account); - - if (!_.isUndefined(options.regular_key)) { - this._setAccount('RegularKey', options.regular_key); - } - - return this; -}; - -/** - * Construct a 'TrustSet' transaction - * - * @param {String} account - * @param [Amount] limit - * @param [Number] quality in - * @param [Number] quality out - */ - -Transaction.prototype.trustSet = -Transaction.prototype.rippleLineSet = function(options_) { - let options; - - if (typeof options_ === 'object') { - options = _.merge({}, options_); - - if (_.isUndefined(options.account)) { - options.account = options.src; - } - } else { - options = { - account: arguments[0], - limit: arguments[1], - quality_in: arguments[2], - quality_out: arguments[3] - }; - } - - this.setType('TrustSet'); - this.setAccount(options.account); - - if (!_.isUndefined(options.limit)) { - this.setLimit(options.limit); - } - if (!_.isUndefined(options.quality_in)) { - this.setQualityIn(options.quality_in); - } - if (!_.isUndefined(options.quality_out)) { - this.setQualityOut(options.quality_out); - } - - // XXX Throw an error if nothing is set. - - return this; -}; - -Transaction.prototype.setLimit = function(amount) { - return this._setAmount('LimitAmount', amount, {no_native: true}); -}; - -Transaction.prototype.setQualityIn = function(quality) { - return this._setUInt32('QualityIn', quality); -}; - -Transaction.prototype.setQualityOut = function(quality) { - return this._setUInt32('QualityOut', quality); -}; - -/** - * Construct a 'Payment' transaction - * - * Relevant setters: - * - setPaths() - * - setBuildPath() - * - addPath() - * - setSourceTag() - * - setDestinationTag() - * - setSendMax() - * - setFlags() - * - * @param {String} source account - * @param {String} destination account - * @param {Amount} payment amount - */ - -Transaction.prototype.payment = function(options_) { - let options; - - if (typeof options_ === 'object') { - options = _.merge({}, options_); - - if (_.isUndefined(options.account)) { - options.account = options.src || options.from; - } - if (_.isUndefined(options.destination)) { - options.destination = options.dst || options.to; - } - } else { - options = { - account: arguments[0], - destination: arguments[1], - amount: arguments[2] - }; - } - - this.setType('Payment'); - this.setAccount(options.account); - this.setDestination(options.destination); - this.setAmount(options.amount); - - return this; -}; - -Transaction.prototype.setAmount = function(amount) { - return this._setAmount('Amount', amount); -}; - -Transaction.prototype.setDestination = function(destination) { - return this._setAccount('Destination', destination); -}; - -/** - * Set SendMax for Payment - * - * @param {String|Object} send max amount - */ - -Transaction.prototype.setSendMax = -Transaction.prototype.sendMax = function(send_max) { - return this._setAmount('SendMax', send_max); -}; - -/** - * Set DeliverMin for Payment - * - * @param {String|Object} deliver_min minimum amount to deliver - */ - -Transaction.prototype.setDeliverMin = function(deliver_min) { - return this._setAmount('DeliverMin', deliver_min); -}; - -/** - * Filter invalid properties from path objects in a path array - * - * Valid properties are: - * - account - * - currency - * - issuer - * - type_hex - * - * @param {Array} path - * @return {Array} filtered path - */ - -Transaction._rewritePath = function(path) { - const newPath = path.map(function(node) { - const newNode = { }; - - if (node.hasOwnProperty('account')) { - newNode.account = node.account; - } - - if (node.hasOwnProperty('issuer')) { - newNode.issuer = node.issuer; - } - - if (node.hasOwnProperty('currency')) { - newNode.currency = normalizeCurrency(node.currency); - } - - if (node.hasOwnProperty('type_hex')) { - newNode.type_hex = node.type_hex; - } - - return newNode; - }); - - return newPath; -}; - -/** - * Add a path for Payment transaction - * - * @param {Array} path - */ - -Transaction.prototype.addPath = -Transaction.prototype.pathAdd = function(path) { - if (!Array.isArray(path)) { - throw new Error('Path must be an array'); - } - - this.tx_json.Paths = this.tx_json.Paths || []; - this.tx_json.Paths.push(Transaction._rewritePath(path)); - - return this; -}; - -/** - * Set paths for Payment transaction - * - * @param {Array} paths - */ - -Transaction.prototype.setPaths = -Transaction.prototype.paths = function(paths) { - if (!Array.isArray(paths)) { - throw new Error('Paths must be an array'); - } - - if (paths.length === 0) { - return this; - } - - this.tx_json.Paths = []; - paths.forEach(this.addPath, this); - - return this; -}; - -/** - * Set build_path to have server blindly construct a path for Payment - * - * "blindly" because the sender has no idea of the actual cost must be less - * than send max. - * - * @param {Boolean} build path - */ - -Transaction.prototype.setBuildPath = -Transaction.prototype.buildPath = function(build) { - this._build_path = build === undefined || build; - - return this; -}; - -/** - * Set DestinationTag for Payment transaction - * - * @param {Number} destination tag - */ - -Transaction.prototype.setDestinationTag = -Transaction.prototype.destinationTag = function(tag) { - return this._setUInt32('DestinationTag', tag); -}; - -/** - * Set InvoiceID for Payment transaction - * - * @param {String} id - */ - -Transaction.prototype.setInvoiceID = -Transaction.prototype.invoiceID = function(id) { - return this._setHash256('InvoiceID', id, {pad: true}); -}; - -/** - * Construct an 'OfferCreate transaction - * - * @param {String} account - * @param {Amount} taker pays amount - * @param {Amount} taker gets amount - * @param [Number|Date] expiration - * @param [Number] sequence of an existing offer to replace - */ - -Transaction.prototype.offerCreate = function(options_) { - let options; - - if (typeof options_ === 'object') { - options = _.merge({}, options_); - - if (_.isUndefined(options.account)) { - options.account = options.src; - } - if (_.isUndefined(options.taker_pays)) { - options.taker_pays = options.buy; - } - if (_.isUndefined(options.taker_gets)) { - options.taker_gets = options.sell; - } - if (_.isUndefined(options.offer_sequence)) { - options.offer_sequence = options.cancel_sequence || options.sequence; - } - } else { - options = { - account: arguments[0], - taker_pays: arguments[1], - taker_gets: arguments[2], - expiration: arguments[3], - offer_sequence: arguments[4] - }; - } - - this.setType('OfferCreate'); - this.setAccount(options.account); - this.setTakerGets(options.taker_gets); - this.setTakerPays(options.taker_pays); - - if (!_.isUndefined(options.expiration)) { - this.setExpiration(options.expiration); - } - if (!_.isUndefined(options.offer_sequence)) { - this.setOfferSequence(options.offer_sequence); - } - - return this; -}; - -Transaction.prototype.setTakerGets = function(amount) { - return this._setAmount('TakerGets', amount); -}; - -Transaction.prototype.setTakerPays = function(amount) { - return this._setAmount('TakerPays', amount); -}; - -Transaction.prototype.setExpiration = function(expiration) { - const timeOffset = expiration instanceof Date - ? expiration.getTime() - : expiration; - - return this._setUInt32('Expiration', utils.time.toRipple(timeOffset)); -}; - -Transaction.prototype.setOfferSequence = function(offerSequence) { - return this._setUInt32('OfferSequence', offerSequence); -}; - -/** - * Construct an 'OfferCancel' transaction - * - * @param {String} account - * @param [Number] sequence of an existing offer - */ - -Transaction.prototype.offerCancel = function(options_) { - let options; - - if (typeof options_ === 'object') { - options = _.merge({}, options_); - - if (_.isUndefined(options.account)) { - options.account = options.src; - } - if (_.isUndefined(options.offer_sequence)) { - options.offer_sequence = options.sequence || options.cancel_sequence; - } - } else { - options = { - account: arguments[0], - offer_sequence: arguments[1] - }; - } - - this.setType('OfferCancel'); - this.setAccount(options.account); - this.setOfferSequence(options.offer_sequence); - - return this; -}; - -Transaction._prepareSignerEntry = function(signer) { - const {account, weight} = signer; - - assert(isValidAddress(account), 'Signer account invalid'); - assert(_.isNumber(weight), 'Signer weight missing'); - assert(weight > 0 && weight <= 65535, 'Signer weight must be 1-65535'); - - return { - SignerEntry: { - Account: account, - SignerWeight: weight - } - }; -}; - -Transaction.prototype.setSignerList = function(options = {}) { - this.setType('SignerListSet'); - this.setAccount(options.account); - this.setSignerQuorum(options.signerQuorum); - - if (!_.isEmpty(options.signers)) { - this.tx_json.SignerEntries = - options.signers.map(Transaction._prepareSignerEntry); - } - - return this; -}; - -Transaction.prototype.setSignerQuorum = function(quorum) { - this._setUInt32('SignerQuorum', quorum); -}; - -/** - * Submit transaction to the network - * - * @param [Function] callback - */ - -Transaction.prototype.submit = function(callback = function() {}) { - const self = this; - - this.callback = callback; - - this._errorHandler = function transactionError(error_, message) { - let error = error_; - - if (!(error instanceof RippleError)) { - error = new RippleError(error, message); - } - - self.callback(error); - }; - - this._successHandler = function transactionSuccess(message) { - self.callback(null, message); - }; - - if (!this.remote) { - this.emit('error', new Error('No remote found')); - return this; - } - - this.getManager().submit(this); - - return this; -}; - -Transaction.prototype.abort = function() { - if (!this.finalized) { - this.emit('error', new RippleError('tejAbort', 'Transaction aborted')); - } - - return this; -}; - -/** - * Return summary object containing important information for persistence - * - * @return {Object} transaction summary - */ - -Transaction.prototype.getSummary = -Transaction.prototype.summary = function() { - const txSummary = { - tx_json: this.tx_json, - clientID: this._clientID, - submittedIDs: this.submittedIDs, - submissionAttempts: this.attempts, - submitIndex: this.submitIndex, - initialSubmitIndex: this.initialSubmitIndex, - lastLedgerSequence: this.tx_json.LastLedgerSequence, - state: this.state, - finalized: this.finalized - }; - - if (this.result) { - const transaction_hash = this.result.tx_json - ? this.result.tx_json.hash - : undefined; - - txSummary.result = { - engine_result: this.result.engine_result, - engine_result_message: this.result.engine_result_message, - ledger_hash: this.result.ledger_hash, - ledger_index: this.result.ledger_index, - transaction_hash: transaction_hash - }; - } - - return txSummary; -}; - -/** - * Construct a 'SuspendedPaymentCreate' transaction - * - * Relevant setters: - * - setSourceTag() - * - setFlags() - * - setDigest() - * - setAllowCancelAfter() - * - setAllowExecuteAfter() - * - * @param {String} options.account source account - * @param {String} options.destination account - * @param {Amount} options.amount payment amount - */ - -Transaction.prototype.suspendedPaymentCreate = function(options) { - this.setType('SuspendedPaymentCreate'); - this.setAccount(options.account); - this.setDestination(options.destination); - this.setAmount(options.amount); - return this; -}; - -/** - * Construct a 'SuspendedPaymentFinish' transaction - * - * Relevant setters: - * - setSourceTag() - * - setFlags() - * - setOwner() - * - setOfferSequence() - * - setMethod() - * - setDigest() - * - setProof() - * - * @param {String} options.account source account - * @param {String} options.owner SuspendedPaymentCreate's Account - * @param {Integer} options.paymentSequence SuspendedPaymentCreate's Sequence - */ - -Transaction.prototype.suspendedPaymentFinish = function(options) { - this.setType('SuspendedPaymentFinish'); - this.setAccount(options.account); - this.setOwner(options.owner); - this.setOfferSequence(options.paymentSequence); - return this; -}; - -/** - * Construct a 'SuspendedPaymentCancel' transaction - * - * Relevant setters: - * - setSourceTag() - * - setFlags() - * - setOwner() - * - setOfferSequence() - * - * @param {String} options.account source account - * @param {String} options.owner SuspendedPaymentCreate's Account - * @param {Integer} options.paymentSequence SuspendedPaymentCreate's Sequence - */ - -Transaction.prototype.suspendedPaymentCancel = function(options) { - this.setType('SuspendedPaymentCancel'); - this.setAccount(options.account); - this.setOwner(options.owner); - this.setOfferSequence(options.paymentSequence); - return this; -}; - -Transaction.prototype.setDigest = function(digest) { - return this._setHash256('Digest', digest); -}; - -Transaction.prototype.setAllowCancelAfter = function(after) { - return this._setUInt32('CancelAfter', utils.time.toRipple(after)); -}; - -Transaction.prototype.setAllowExecuteAfter = function(after) { - return this._setUInt32('FinishAfter', utils.time.toRipple(after)); -}; - -Transaction.prototype.setOwner = function(owner) { - return this._setAccount('Owner', owner); -}; - -Transaction.prototype.setMethod = function(method) { - return this._setUInt8('Method', method); -}; - -Transaction.prototype.setProof = function(proof) { - this.tx_json.Proof = convertStringToHex(proof); - return this; -}; - -Transaction.prototype._setUInt8 = function(name, value) { - const isValidUInt8 = typeof value === 'number' && value >= 0 && value < 256; - if (!isValidUInt8) { - throw new Error(name + ' must be a valid UInt8'); - } - this.tx_json[name] = value; - return this; -}; - -Transaction.prototype.setSigners = function(signers) { - if (_.isArray(signers)) { - this.tx_json.Signers = signers; - } - - return this; -}; - -Transaction.prototype.addMultiSigner = function(signer) { - assert(isValidAddress(signer.Account), 'Signer must have a valid Account'); - - if (_.isUndefined(this.tx_json.Signers)) { - this.tx_json.Signers = []; - } - - this.tx_json.Signers.push({Signer: signer}); - - this.tx_json.Signers.sort((a, b) => { - return (new Buffer(decodeAddress(a.Signer.Account))).compare( - new Buffer(decodeAddress(b.Signer.Account))); - }); - - return this; -}; - -Transaction.prototype.hasMultiSigners = function() { - return !_.isEmpty(this.tx_json.Signers); -}; - -Transaction.prototype.getMultiSigners = function() { - return this.tx_json.Signers; -}; - -Transaction.prototype.getMultiSigningJson = function() { - assert(this.tx_json.Sequence, 'Sequence must be set before multi-signing'); - assert(this.tx_json.Fee, 'Fee must be set before multi-signing'); - - if (_.isUndefined(this.tx_json.LastLedgerSequence)) { - // Auto-fill LastLedgerSequence - this.setLastLedgerSequence(); - } - - const cleanedJson = _.omit(this.tx_json, [ - 'SigningPubKey', - 'Signers', - 'TxnSignature' - ]); - - const signingTx = Transaction.from_json(cleanedJson); - signingTx.remote = this.remote; - signingTx.setSigningPubKey(''); - signingTx.setCanonicalFlag(); - - return signingTx.tx_json; -}; - -Transaction.prototype.multiSign = function(account, secret) { - const signingData = this.multiSigningData(account); - const keypair = deriveKeypair(secret); - - const signer = { - Account: account, - TxnSignature: sign(signingData, keypair.privateKey), - SigningPubKey: keypair.publicKey - }; - - return signer; -}; - -exports.Transaction = Transaction; diff --git a/src/core/transactionmanager.js b/src/core/transactionmanager.js deleted file mode 100644 index 4b3a6c16..00000000 --- a/src/core/transactionmanager.js +++ /dev/null @@ -1,757 +0,0 @@ -'use strict'; - -const _ = require('lodash'); -const util = require('util'); -const assert = require('assert'); -const async = require('async'); -const EventEmitter = require('events').EventEmitter; -const Transaction = require('./transaction').Transaction; -const RippleError = require('./rippleerror').RippleError; -const PendingQueue = require('./transactionqueue').TransactionQueue; -const log = require('./log').internal.sub('transactionmanager'); - -/** - * @constructor TransactionManager - * @param {Account} account - */ - -function TransactionManager(account) { - EventEmitter.call(this); - - const self = this; - - this._account = account; - this._accountID = account._address; - this._remote = account._remote; - this._nextSequence = undefined; - this._maxFee = this._remote.max_fee; - this._maxAttempts = this._remote.max_attempts; - this._submissionTimeout = this._remote.submission_timeout; - this._pending = new PendingQueue(); - - this._account.on('transaction-outbound', function(res) { - self._transactionReceived(res); - }); - - this._remote.on('load_changed', function(load) { - self._adjustFees(load); - }); - - function updatePendingStatus(ledger) { - self._updatePendingStatus(ledger); - } - - this._remote.on('ledger_closed', updatePendingStatus); - - function handleReconnect() { - self._handleReconnect(function() { - // Handle reconnect, account_tx procedure first, before - // hooking back into ledger_closed - self._remote.on('ledger_closed', updatePendingStatus); - }); - } - - this._remote.on('disconnect', function() { - self._remote.removeListener('ledger_closed', updatePendingStatus); - self._remote.once('connect', handleReconnect); - }); - - // Query server for next account transaction sequence - this._loadSequence(); -} - -util.inherits(TransactionManager, EventEmitter); - -TransactionManager._isNoOp = function(transaction) { - return (typeof transaction === 'object') - && (typeof transaction.tx_json === 'object') - && (transaction.tx_json.TransactionType === 'AccountSet') - && (transaction.tx_json.Flags === 0); -}; - -TransactionManager._isRemoteError = function(error) { - return (typeof error === 'object') - && (error.error === 'remoteError') - && (typeof error.remote === 'object'); -}; - -TransactionManager._isNotFound = function(error) { - return TransactionManager._isRemoteError(error) - && /^(txnNotFound|transactionNotFound)$/.test(error.remote.error); -}; - -TransactionManager._isTooBusy = function(error) { - return TransactionManager._isRemoteError(error) - && (error.remote.error === 'tooBusy'); -}; - -/** - * Normalize transactions received from account transaction stream and - * account_tx - * - * @param {Transaction} - * @return {Transaction} normalized - * @api private - */ - -TransactionManager.normalizeTransaction = function(tx) { - const transaction = { }; - const keys = Object.keys(tx); - - for (let i = 0; i < keys.length; i++) { - const k = keys[i]; - switch (k) { - case 'transaction': - // Account transaction stream - transaction.tx_json = tx[k]; - break; - case 'tx': - // account_tx response - transaction.engine_result = tx.meta.TransactionResult; - transaction.result = transaction.engine_result; - transaction.tx_json = tx[k]; - transaction.hash = tx[k].hash; - transaction.ledger_index = tx[k].ledger_index; - transaction.type = 'transaction'; - transaction.validated = tx.validated; - break; - case 'meta': - case 'metadata': - transaction.metadata = tx[k]; - break; - case 'mmeta': - // Don't copy mmeta - break; - default: - transaction[k] = tx[k]; - } - } - - return transaction; -}; - -/** - * Handle received transaction from two possible sources - * - * + Account transaction stream (normal operation) - * + account_tx (after reconnect) - * - * @param {Object} transaction - * @api private - */ - -TransactionManager.prototype._transactionReceived = function(tx) { - const transaction = TransactionManager.normalizeTransaction(tx); - - if (!transaction.validated) { - // Transaction has not been validated - return; - } - - if (transaction.tx_json.Account !== this._accountID) { - // Received transaction's account does not match - return; - } - - if (this._remote.trace) { - log.info('transaction received:', transaction.tx_json); - } - - this._pending.addReceivedSequence(transaction.tx_json.Sequence); - - const hash = transaction.tx_json.hash; - const submission = this._pending.getSubmission(hash); - - if (!(submission instanceof Transaction)) { - // The received transaction does not correlate to one submitted - this._pending.addReceivedId(hash, transaction); - return; - } - - // ND: A `success` handler will `finalize` this later - switch (transaction.engine_result) { - case 'tesSUCCESS': - submission.emit('success', transaction); - break; - default: - submission.emit('error', transaction); - } -}; - -/** - * Adjust pending transactions' fees in real-time. This does not resubmit - * pending transactions; they will be resubmitted periodically with an updated - * fee (and as a consequence, a new transaction ID) if not already validated - * - * ND: note, that `Fee` is a component of a transactionID - * - * @api private - */ - -TransactionManager.prototype._adjustFees = function() { - const self = this; - - if (!this._remote.local_fee) { - return; - } - - function maxFeeExceeded(transaction) { - // Don't err until attempting to resubmit - transaction.once('presubmit', function() { - transaction.emit('error', 'tejMaxFeeExceeded'); - }); - } - - this._pending.forEach(function(transaction) { - if (transaction._setFixedFee) { - return; - } - - const oldFee = transaction.tx_json.Fee; - const newFee = transaction._computeFee(); - - if (Number(newFee) > self._maxFee) { - // Max transaction fee exceeded, abort submission - maxFeeExceeded(transaction); - return; - } - - transaction.tx_json.Fee = newFee; - transaction.emit('fee_adjusted', oldFee, newFee); - - if (self._remote.trace) { - log.info('fee adjusted:', transaction.tx_json, oldFee, newFee); - } - }); -}; - -/** - * Get pending transactions - * - * @return {Array} pending transactions - */ - -TransactionManager.prototype.getPending = function() { - return this._pending; -}; - -/** - * Legacy code. Update transaction status after excessive ledgers pass. One of - * either "missing" or "lost" - * - * @param {Object} ledger data - * @api private - */ - -TransactionManager.prototype._updatePendingStatus = function(ledger) { - assert.strictEqual(typeof ledger, 'object'); - assert.strictEqual(typeof ledger.ledger_index, 'number'); - - this._pending.forEach(function(transaction) { - if (transaction.finalized) { - return; - } - - switch (ledger.ledger_index - transaction.submitIndex) { - case 4: - transaction.emit('missing', ledger); - break; - case 8: - transaction.emit('lost', ledger); - break; - } - - if (ledger.ledger_index > transaction.tx_json.LastLedgerSequence) { - // Transaction must fail - transaction.emit('error', new RippleError( - 'tejMaxLedger', 'Transaction LastLedgerSequence exceeded')); - } - }); -}; - -// Fill an account transaction sequence -TransactionManager.prototype._fillSequence = function(tx, callback) { - const self = this; - - function submitFill(sequence, fCallback) { - const fillTransaction = self._remote.createTransaction('AccountSet', { - account: self._accountID - }); - fillTransaction.tx_json.Sequence = sequence; - - // Secrets may be set on a per-transaction basis - if (tx._secret) { - fillTransaction.secret(tx._secret); - } - - fillTransaction.once('submitted', fCallback); - fillTransaction.submit(); - } - - function sequenceLoaded(err, sequence) { - if (typeof sequence !== 'number') { - log.info('fill sequence: failed to fetch account transaction sequence'); - return callback(); - } - - const sequenceDiff = tx.tx_json.Sequence - sequence; - let submitted = 0; - - async.whilst( - function() { - return submitted < sequenceDiff; - }, - function(asyncCallback) { - submitFill(sequence, function(res) { - ++submitted; - if (res.engine_result === 'tesSUCCESS') { - self.emit('sequence_filled', err); - } - asyncCallback(); - }); - }, - function() { - if (callback) { - callback(); - } - } - ); - } - - this._loadSequence(sequenceLoaded); -}; - -/** - * Load account transaction sequence - * - * @param [Function] callback - * @api private - */ - -TransactionManager.prototype._loadSequence = function(callback_) { - const self = this; - const callback = (typeof callback_ === 'function') - ? callback_ - : function() {}; - - function sequenceLoaded(err, sequence) { - if (err || typeof sequence !== 'number') { - if (self._remote.trace) { - log.info('error requesting account transaction sequence', err); - return; - } - } - - self._nextSequence = sequence; - self.emit('sequence_loaded', sequence); - callback(err, sequence); - } - - this._account.getNextSequence(sequenceLoaded); -}; - -/** - * On reconnect, load account_tx in case a pending transaction succeeded while - * disconnected - * - * @param [Function] callback - * @api private - */ - -TransactionManager.prototype._handleReconnect = function(callback_) { - const self = this; - const callback = (typeof callback_ === 'function') - ? callback_ - : function() {}; - - if (!this._pending.length()) { - callback(); - return; - } - - function handleTransactions(err, transactions) { - if (err || typeof transactions !== 'object') { - if (self._remote.trace) { - log.info('error requesting account_tx', err); - } - callback(); - return; - } - - if (Array.isArray(transactions.transactions)) { - // Treat each transaction in account transaction history as received - transactions.transactions.forEach(self._transactionReceived, self); - } - - callback(); - - self._loadSequence(function() { - // Resubmit pending transactions after sequence is loaded - self._resubmit(); - }); - } - - const options = { - account: this._accountID, - ledger_index_min: this._pending.getMinLedger(), - ledger_index_max: -1, - binary: true, - parseBinary: true, - limit: 20 - }; - - this._remote.requestAccountTx(options, handleTransactions); -}; - -/** - * Wait for specified number of ledgers to pass - * - * @param {Number} ledgers - * @param {Function} callback - * @api private - */ - -TransactionManager.prototype._waitLedgers = function(ledgers, callback) { - assert.strictEqual(typeof ledgers, 'number'); - assert.strictEqual(typeof callback, 'function'); - - if (ledgers < 1) { - return callback(); - } - - const self = this; - let closes = 0; - - function ledgerClosed() { - if (++closes === ledgers) { - self._remote.removeListener('ledger_closed', ledgerClosed); - callback(); - } - } - - this._remote.on('ledger_closed', ledgerClosed); -}; - -/** - * Resubmit pending transactions. If a transaction is specified, it will be - * resubmitted. Otherwise, all pending transactions will be resubmitted - * - * @param [Number] ledgers to wait before resubmitting - * @param [Transaction] pending transactions to resubmit - * @api private - */ - -TransactionManager.prototype._resubmit = function(ledgers_, pending_) { - const self = this; - - let ledgers = ledgers_; - let pending = pending_; - - if (arguments.length === 1) { - pending = ledgers; - ledgers = 0; - } - - ledgers = ledgers || 0; - pending = pending instanceof Transaction - ? [pending] - : this.getPending().getQueue(); - - function resubmitTransaction(transaction, next) { - if (!transaction || transaction.finalized) { - // Transaction has been finalized, nothing to do - return; - } - - // Find ID within cache of received (validated) transaction IDs - const received = transaction.findId(self._pending._idCache); - - if (received) { - switch (received.engine_result) { - case 'tesSUCCESS': - transaction.emit('success', received); - break; - default: - transaction.emit('error', received); - } - } - - if (!transaction.isResubmittable()) { - // Rather than resubmit, wait for the transaction to fail due to - // LastLedgerSequence's being exceeded. The ultimate error emitted on - // transaction is 'tejMaxLedger'; should be definitive - return; - } - - while (self._pending.hasSequence(transaction.tx_json.Sequence)) { - // Sequence number has been consumed by another transaction - transaction.tx_json.Sequence += 1; - - if (self._remote.trace) { - log.info('incrementing sequence:', transaction.tx_json); - } - } - - if (self._remote.trace) { - log.info('resubmit:', transaction.tx_json); - } - - transaction.once('submitted', function(m) { - transaction.emit('resubmitted', m); - next(); - }); - - self._request(transaction); - } - - this._waitLedgers(ledgers, function() { - async.eachSeries(pending, resubmitTransaction); - }); -}; - -/** - * Prepare submit request - * - * @param {Transaction} transaction to submit - * @return {Request} submit request - * @api private - */ - -TransactionManager.prototype._prepareRequest = function(tx) { - const submitRequest = this._remote.requestSubmit(); - - if (this._remote.local_signing) { - tx.sign(); - - const serialized = tx.serialize(); - submitRequest.txBlob(serialized); - - const hash = tx.hash(null, serialized); - tx.addId(hash); - } else { - if (tx.hasMultiSigners()) { - submitRequest.message.command = 'submit_multisigned'; - } - - // ND: `build_path` is completely ignored when doing local signing as - // `Paths` is a component of the signed blob, the `tx_blob` is signed, - // sealed and delivered, and the txn unmodified. - // TODO: perhaps an exception should be raised if build_path is attempted - // while local signing - submitRequest.buildPath(tx._build_path); - submitRequest.secret(tx._secret); - submitRequest.txJson(tx.tx_json); - } - - return submitRequest; -}; - -/** - * Send `submit` request, handle response - * - * @param {Transaction} transaction to submit - * @api private - */ - -TransactionManager.prototype._request = function(tx) { - const self = this; - const remote = this._remote; - - if (tx.finalized) { - return; - } - - if (tx.attempts > this._maxAttempts) { - tx.emit('error', new RippleError('tejAttemptsExceeded')); - return; - } - - if (tx.attempts > 0 && !remote.local_signing) { - const errMessage = 'Automatic resubmission requires local signing'; - tx.emit('error', new RippleError('tejLocalSigningRequired', errMessage)); - return; - } - - if (Number(tx.tx_json.Fee) > tx._maxFee) { - tx.emit('error', new RippleError('tejMaxFeeExceeded')); - return; - } - - if (remote.trace) { - log.info('submit transaction:', tx.tx_json); - } - - function transactionFailed(message) { - if (message.engine_result === 'tefPAST_SEQ') { - // Transaction may succeed after Sequence is updated - self._resubmit(1, tx); - } - } - - function transactionRetry() { - // XXX This may no longer be necessary. Instead, update sequence numbers - // after a transaction fails definitively - self._fillSequence(tx, function() { - self._resubmit(1, tx); - }); - } - - function transactionFailedLocal(message) { - if (message.engine_result === 'telINSUF_FEE_P') { - // Transaction may succeed after Fee is updated - self._resubmit(1, tx); - } - } - - function submissionError(error) { - // Either a tem-class error or generic server error such as tooBusy. This - // should be a definitive failure - if (TransactionManager._isTooBusy(error)) { - self._waitLedgers(1, function() { - tx.once('submitted', function(m) { - tx.emit('resubmitted', m); - }); - self._request(tx); - }); - } else { - self._nextSequence--; - tx.emit('error', error); - } - } - - function submitted(message) { - if (tx.finalized) { - return; - } - - // ND: If for some unknown reason our hash wasn't computed correctly this - // is an extra measure. - if (message.tx_json && message.tx_json.hash) { - tx.addId(message.tx_json.hash); - } - - message.result = message.engine_result || ''; - - tx.result = message; - tx.responses += 1; - - if (remote.trace) { - log.info('submit response:', message); - } - - tx.emit('submitted', message); - - switch (message.result.slice(0, 3)) { - case 'tes': - tx.emit('proposed', message); - break; - case 'tec': - break; - case 'ter': - transactionRetry(message); - break; - case 'tef': - transactionFailed(message); - break; - case 'tel': - transactionFailedLocal(message); - break; - default: - // tem - submissionError(message); - } - } - - function requestTimeout() { - // ND: What if the response is just slow and we get a response that - // `submitted` above will cause to have concurrent resubmit logic streams? - // It's simpler to just mute handlers and look out for finalized - // `transaction` messages. - if (tx.finalized) { - return; - } - - tx.emit('timeout'); - - if (remote.isConnected()) { - if (remote.trace) { - log.info('timeout:', tx.tx_json); - } - self._resubmit(1, tx); - } - } - - tx.submitIndex = this._remote.getLedgerSequenceSync() + 1; - - if (tx.attempts === 0) { - tx.initialSubmitIndex = tx.submitIndex; - } - - const submitRequest = this._prepareRequest(tx); - submitRequest.once('error', submitted); - submitRequest.once('success', submitted); - - tx.emit('presubmit'); - - submitRequest.broadcast().request(); - tx.attempts++; - - tx.emit('postsubmit'); - - submitRequest.setTimeout(self._submissionTimeout); - submitRequest.once('timeout', requestTimeout); -}; - -/** - * Entry point for TransactionManager submission - * - * @param {Transaction} tx - */ - -TransactionManager.prototype.submit = function(tx) { - const self = this; - - if (typeof this._nextSequence !== 'number') { - // If sequence number is not yet known, defer until it is. - this.once('sequence_loaded', function() { - self.submit(tx); - }); - return; - } - - if (tx.finalized) { - // Finalized transactions must stop all activity - return; - } - - tx.once('cleanup', function() { - self.getPending().remove(tx); - }); - - if (!_.isNumber(tx.tx_json.Sequence)) { - // Honor manually-set sequences - tx.setSequence(this._nextSequence++); - } - - if (_.isUndefined(tx.tx_json.LastLedgerSequence)) { - tx.setLastLedgerSequence(); - } - - if (tx.hasMultiSigners()) { - tx.setResubmittable(false); - } - - if (!tx.complete()) { - this._nextSequence -= 1; - return; - } - - // ND: this is the ONLY place we put the tx into the queue. The - // TransactionQueue queue is merely a list, so any mutations to tx._hash - // will cause subsequent look ups (eg. inside 'transaction-outbound' - // validated transaction clearing) to fail. - this._pending.push(tx); - this._request(tx); -}; - -exports.TransactionManager = TransactionManager; diff --git a/src/core/transactionqueue.js b/src/core/transactionqueue.js deleted file mode 100644 index ac328ce0..00000000 --- a/src/core/transactionqueue.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; - -var lodash = require('lodash'); -var LRU = require('lru-cache'); -var Transaction = require('./transaction').Transaction; - -/** - * Manager for pending transactions - */ - -function TransactionQueue() { - this._queue = [ ]; - this._idCache = new LRU({max: 200}); - this._sequenceCache = new LRU({max: 200}); -} - -/** - * Store received (validated) sequence - * - * @param {Number} sequence - */ - -TransactionQueue.prototype.addReceivedSequence = function(sequence) { - this._sequenceCache.set(String(sequence), true); -}; - -/** - * Check that sequence number has been consumed by a validated - * transaction - * - * @param {Number} sequence - * @return {Boolean} - */ - -TransactionQueue.prototype.hasSequence = function(sequence) { - return this._sequenceCache.has(String(sequence)); -}; - -/** - * Store received (validated) ID transaction - * - * @param {String} transaction id - * @param {Transaction} transaction - */ - -TransactionQueue.prototype.addReceivedId = function(id, transaction) { - this._idCache.set(id, transaction); -}; - -/** - * Get received (validated) transaction by ID - * - * @param {String} transaction id - * @return {Object} - */ - -TransactionQueue.prototype.getReceived = function(id) { - return this._idCache.get(id); -}; - -/** - * Get a submitted transaction by ID. Transactions - * may have multiple associated IDs. - * - * @param {String} transaction id - * @return {Transaction} - */ - -TransactionQueue.prototype.getSubmission = function(id) { - return lodash.find(this._queue, function(tx) { - return lodash.contains(tx.submittedIDs, id); - }); -}; - -/** - * Get earliest ledger in the pending queue - * - * @return {Number} ledger - */ - -TransactionQueue.prototype.getMinLedger = function() { - if (this.length() < 1) { - return -1; - } - - var result = Infinity; - - for (var i = 0; i < this.length(); i++) { - if (this._queue[i].initialSubmitIndex < result) { - result = this._queue[i].initialSubmitIndex; - } - } - - if (!isFinite(result)) { - result = -1; - } - - return result; -}; - -/** - * Remove a transaction from the queue - * - * @param {String|Transaction} transaction or id - */ - -TransactionQueue.prototype.remove = function(tx) { - // ND: We are just removing the Transaction by identity - var i = this._queue.length; - - if (typeof tx === 'string') { - tx = this.getSubmission(tx); - } - - if (!(tx instanceof Transaction)) { - return; - } - - while (i--) { - if (this._queue[i] === tx) { - this._queue.splice(i, 1); - break; - } - } -}; - -/** - * Add a transaction to pending queue - * - * @param {Transaction} transaction - */ - -TransactionQueue.prototype.push = function(tx) { - this._queue.push(tx); -}; - -/** - * Iterate over pending transactions - * - * @param {Function} iterator - */ - -TransactionQueue.prototype.forEach = function(fn) { - this._queue.forEach(fn); -}; - -/** - * @return {Number} length of pending queue - */ - -TransactionQueue.prototype.length = -TransactionQueue.prototype.getLength = function() { - return this._queue.length; -}; - -/** - * @return {Array} pending queue - */ - -TransactionQueue.prototype.getQueue = function() { - return this._queue; -}; - -exports.TransactionQueue = TransactionQueue; diff --git a/src/core/utils.js b/src/core/utils.js deleted file mode 100644 index 274f0eeb..00000000 --- a/src/core/utils.js +++ /dev/null @@ -1,184 +0,0 @@ -'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 -function getMantissa16FromString(decimalString) { - let mantissa = decimalString.replace(/\./, '') // remove decimal point - .replace(/e.*/, '') // remove scientific notation - .replace(/^0*/, ''); // remove leading zeroes - if (mantissa.length > 16) { - return mantissa.substring(0, 16); - } - while (mantissa.length < 16) { - mantissa += '0'; // add trailing zeroes until length is 16 - } - return mantissa; -} - -function getMantissaDecimalString(bignum) { - return getMantissa16FromString(bignum.toPrecision(16)); -} - -function trace(comment, func) { - return function() { - console.log('%s: %s', comment, arguments.toString); - func(arguments); - }; -} - -function arraySet(count, value) { - const a = new Array(count); - - for (let i = 0; i < count; i++) { - a[i] = value; - } - - return a; -} - -function hexToString(h) { - const a = []; - let i = 0; - - if (h.length % 2) { - a.push(String.fromCharCode(parseInt(h.substring(0, 1), 16))); - i = 1; - } - - for (; i < h.length; i += 2) { - a.push(String.fromCharCode(parseInt(h.substring(i, i + 2), 16))); - } - - return a.join(''); -} - -function stringToHex(s) { - let result = ''; - for (let i = 0; i < s.length; i++) { - const b = s.charCodeAt(i); - result += b < 16 ? '0' + b.toString(16) : b.toString(16); - } - return result; -} - -function stringToArray(s) { - const a = new Array(s.length); - - for (let i = 0; i < a.length; i += 1) { - a[i] = s.charCodeAt(i); - } - - return a; -} - -function hexToArray(h) { - return stringToArray(hexToString(h)); -} - -function arrayToHex(a) { - return a.map(function(byteValue) { - const hex = byteValue.toString(16).toUpperCase(); - return hex.length > 1 ? hex : '0' + hex; - }).join(''); -} - -function chunkString(str, n, leftAlign) { - const ret = []; - let i = 0; - const len = str.length; - - if (leftAlign) { - i = str.length % n; - if (i) { - ret.push(str.slice(0, i)); - } - } - - for (; i < len; i += n) { - ret.push(str.slice(i, n + i)); - } - - return ret; -} - -function assert(assertion, msg) { - if (!assertion) { - throw new Error('Assertion failed' + (msg ? ': ' + msg : '.')); - } -} - -/** - * @param {Array} arr (values) - * @return {Array} unique values (for string representation of value) in `arr` - */ -function arrayUnique(arr) { - const u = {}; - const a = []; - - for (let i = 0, l = arr.length; i < l; i++) { - const k = arr[i]; - if (u[k]) { - continue; - } - a.push(k); - u[k] = true; - } - - return a; -} - -/** - * @param {Number} rpepoch (seconds since 1/1/2000 GMT) - * @return {Number} ms since unix epoch - * - */ -function toTimestamp(rpepoch) { - return (rpepoch + 0x386D4380) * 1000; -} - -/** - * @param {Number|Date} timestamp (ms since unix epoch) - * @return {Number} seconds since ripple epoch ( 1/1/2000 GMT) - */ -function fromTimestamp(timestamp) { - const timestamp_ = timestamp instanceof Date ? - timestamp.getTime() : - timestamp; - return Math.round(timestamp_ / 1000) - 0x386D4380; -} - -function unused() { -} - -exports.time = { - fromRipple: toTimestamp, - toRipple: fromTimestamp -}; - -exports.sha512half = sha512half; -exports.trace = trace; -exports.arraySet = arraySet; -exports.hexToString = hexToString; -exports.hexToArray = hexToArray; -exports.stringToArray = stringToArray; -exports.stringToHex = stringToHex; -exports.arrayToHex = arrayToHex; -exports.chunkString = chunkString; -exports.assert = assert; -exports.arrayUnique = arrayUnique; -exports.toTimestamp = toTimestamp; -exports.fromTimestamp = fromTimestamp; -exports.getMantissaDecimalString = getMantissaDecimalString; -exports.getMantissa16FromString = getMantissa16FromString; -exports.unused = unused; - -// vim:sw=2:sts=2:ts=8:et diff --git a/src/index.js b/src/index.js index 9c7120e0..59c6cb32 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +/* @flow */ 'use strict'; /* eslint-disable max-len */ @@ -6,10 +7,112 @@ /* eslint-enable max-len */ require('babel-core/polyfill'); -const core = require('./core'); -const RippleAPI = require('./api'); +const _ = require('lodash'); +const EventEmitter = require('events').EventEmitter; +const common = require('./common'); +const server = require('./server/server'); +const connect = server.connect; +const disconnect = server.disconnect; +const getServerInfo = server.getServerInfo; +const getFee = server.getFee; +const isConnected = server.isConnected; +const getLedgerVersion = server.getLedgerVersion; +const getTransaction = require('./ledger/transaction'); +const getTransactions = require('./ledger/transactions'); +const getTrustlines = require('./ledger/trustlines'); +const getBalances = require('./ledger/balances'); +const getBalanceSheet = require('./ledger/balance-sheet'); +const getPaths = require('./ledger/pathfind'); +const getOrders = require('./ledger/orders'); +const getOrderbook = require('./ledger/orderbook'); +const getSettings = require('./ledger/settings'); +const getAccountInfo = require('./ledger/accountinfo'); +const preparePayment = require('./transaction/payment'); +const prepareTrustline = require('./transaction/trustline'); +const prepareOrder = require('./transaction/order'); +const prepareOrderCancellation = require('./transaction/ordercancellation'); +const prepareSuspendedPaymentCreation = + require('./transaction/suspended-payment-creation'); +const prepareSuspendedPaymentExecution = + require('./transaction/suspended-payment-execution'); +const prepareSuspendedPaymentCancellation = + require('./transaction/suspended-payment-cancellation'); +const prepareSettings = require('./transaction/settings'); +const sign = require('./transaction/sign'); +const submit = require('./transaction/submit'); +const errors = require('./common').errors; +const generateAddress = common.generateAddressAPI; +const computeLedgerHash = require('./offline/ledgerhash'); +const getLedger = require('./ledger/ledger'); -module.exports = { - RippleAPI, - _DEPRECATED: core // WARNING: this will be removed soon +type APIOptions = { + servers?: Array, + feeCushion?: number, + trace?: boolean, + proxy?: string +} + +class RippleAPI extends EventEmitter { + constructor(options: APIOptions = {}) { + common.validate.apiOptions(options); + super(); + if (options.servers !== undefined) { + const servers: Array = options.servers; + if (servers.length === 1) { + this._feeCushion = options.feeCushion || 1.2; + this.connection = new common.Connection(servers[0], options); + this.connection.on('ledgerClosed', message => { + this.emit('ledgerClosed', server.formatLedgerClose(message)); + }); + } else { + throw new errors.RippleError('Multi-server not implemented'); + } + } + } +} + +_.assign(RippleAPI.prototype, { + connect, + disconnect, + isConnected, + getServerInfo, + getFee, + getLedgerVersion, + + getTransaction, + getTransactions, + getTrustlines, + getBalances, + getBalanceSheet, + getPaths, + getOrders, + getOrderbook, + getSettings, + getAccountInfo, + getLedger, + + preparePayment, + prepareTrustline, + prepareOrder, + prepareOrderCancellation, + prepareSuspendedPaymentCreation, + prepareSuspendedPaymentExecution, + prepareSuspendedPaymentCancellation, + prepareSettings, + sign, + submit, + + generateAddress, + errors +}); + +// these are exposed only for use by unit tests; they are not part of the API +RippleAPI._PRIVATE = { + common, + computeLedgerHash, + RangeSet: require('./common/rangeset').RangeSet, + ledgerUtils: require('./ledger/utils'), + schemaValidator: require('./common/schema-validator') }; + +module.exports.RippleAPI = RippleAPI; diff --git a/src/api/ledger/accountinfo.js b/src/ledger/accountinfo.js similarity index 100% rename from src/api/ledger/accountinfo.js rename to src/ledger/accountinfo.js diff --git a/src/api/ledger/balance-sheet.js b/src/ledger/balance-sheet.js similarity index 100% rename from src/api/ledger/balance-sheet.js rename to src/ledger/balance-sheet.js diff --git a/src/api/ledger/balances.js b/src/ledger/balances.js similarity index 100% rename from src/api/ledger/balances.js rename to src/ledger/balances.js diff --git a/src/api/ledger/ledger.js b/src/ledger/ledger.js similarity index 100% rename from src/api/ledger/ledger.js rename to src/ledger/ledger.js diff --git a/src/api/ledger/orderbook.js b/src/ledger/orderbook.js similarity index 100% rename from src/api/ledger/orderbook.js rename to src/ledger/orderbook.js diff --git a/src/api/ledger/orders.js b/src/ledger/orders.js similarity index 100% rename from src/api/ledger/orders.js rename to src/ledger/orders.js diff --git a/src/api/ledger/parse/account-order.js b/src/ledger/parse/account-order.js similarity index 100% rename from src/api/ledger/parse/account-order.js rename to src/ledger/parse/account-order.js diff --git a/src/api/ledger/parse/account-trustline.js b/src/ledger/parse/account-trustline.js similarity index 87% rename from src/api/ledger/parse/account-trustline.js rename to src/ledger/parse/account-trustline.js index 71dd0b6e..cb8281be 100644 --- a/src/api/ledger/parse/account-trustline.js +++ b/src/ledger/parse/account-trustline.js @@ -4,9 +4,9 @@ const utils = require('./utils'); type Trustline = { account: string, limit: number, currency: string, quality_in: ?number, - quality_out: ?number, no_ripple: boolean, freeze: boolean, authorized: boolean, - limit_peer: string, no_ripple_peer: boolean, freeze_peer: boolean, - peer_authorized: boolean, balance: any + quality_out: ?number, no_ripple: boolean, freeze: boolean, + authorized: boolean, limit_peer: string, no_ripple_peer: boolean, + freeze_peer: boolean, peer_authorized: boolean, balance: any } type TrustlineSpecification = {} diff --git a/src/api/ledger/parse/amount.js b/src/ledger/parse/amount.js similarity index 100% rename from src/api/ledger/parse/amount.js rename to src/ledger/parse/amount.js diff --git a/src/api/ledger/parse/cancellation.js b/src/ledger/parse/cancellation.js similarity index 100% rename from src/api/ledger/parse/cancellation.js rename to src/ledger/parse/cancellation.js diff --git a/src/api/ledger/parse/fields.js b/src/ledger/parse/fields.js similarity index 100% rename from src/api/ledger/parse/fields.js rename to src/ledger/parse/fields.js diff --git a/src/api/ledger/parse/flags.js b/src/ledger/parse/flags.js similarity index 100% rename from src/api/ledger/parse/flags.js rename to src/ledger/parse/flags.js diff --git a/src/api/ledger/parse/ledger.js b/src/ledger/parse/ledger.js similarity index 100% rename from src/api/ledger/parse/ledger.js rename to src/ledger/parse/ledger.js diff --git a/src/api/ledger/parse/order.js b/src/ledger/parse/order.js similarity index 100% rename from src/api/ledger/parse/order.js rename to src/ledger/parse/order.js diff --git a/src/api/ledger/parse/orderbook-order.js b/src/ledger/parse/orderbook-order.js similarity index 100% rename from src/api/ledger/parse/orderbook-order.js rename to src/ledger/parse/orderbook-order.js diff --git a/src/api/ledger/parse/pathfind.js b/src/ledger/parse/pathfind.js similarity index 100% rename from src/api/ledger/parse/pathfind.js rename to src/ledger/parse/pathfind.js diff --git a/src/api/ledger/parse/payment.js b/src/ledger/parse/payment.js similarity index 100% rename from src/api/ledger/parse/payment.js rename to src/ledger/parse/payment.js diff --git a/src/api/ledger/parse/settings.js b/src/ledger/parse/settings.js similarity index 100% rename from src/api/ledger/parse/settings.js rename to src/ledger/parse/settings.js diff --git a/src/api/ledger/parse/suspended-payment-cancellation.js b/src/ledger/parse/suspended-payment-cancellation.js similarity index 100% rename from src/api/ledger/parse/suspended-payment-cancellation.js rename to src/ledger/parse/suspended-payment-cancellation.js diff --git a/src/api/ledger/parse/suspended-payment-creation.js b/src/ledger/parse/suspended-payment-creation.js similarity index 100% rename from src/api/ledger/parse/suspended-payment-creation.js rename to src/ledger/parse/suspended-payment-creation.js diff --git a/src/api/ledger/parse/suspended-payment-execution.js b/src/ledger/parse/suspended-payment-execution.js similarity index 64% rename from src/api/ledger/parse/suspended-payment-execution.js rename to src/ledger/parse/suspended-payment-execution.js index 71159d2c..6b682fbe 100644 --- a/src/api/ledger/parse/suspended-payment-execution.js +++ b/src/ledger/parse/suspended-payment-execution.js @@ -1,14 +1,8 @@ /* @flow */ 'use strict'; const assert = require('assert'); -const sjclcodec = require('sjcl-codec'); const utils = require('./utils'); -function convertHexToString(hexString) { - const bits = sjclcodec.hex.toBits(hexString); - return sjclcodec.utf8String.fromBits(bits); -} - function parseSuspendedPaymentExecution(tx: Object): Object { assert(tx.TransactionType === 'SuspendedPaymentFinish'); @@ -18,7 +12,7 @@ function parseSuspendedPaymentExecution(tx: Object): Object { paymentSequence: tx.OfferSequence, method: tx.Method, digest: tx.Digest, - proof: tx.Proof ? convertHexToString(tx.Proof) : undefined + proof: tx.Proof ? utils.hexToString(tx.Proof) : undefined }); } diff --git a/src/api/ledger/parse/transaction.js b/src/ledger/parse/transaction.js similarity index 100% rename from src/api/ledger/parse/transaction.js rename to src/ledger/parse/transaction.js diff --git a/src/api/ledger/parse/trustline.js b/src/ledger/parse/trustline.js similarity index 100% rename from src/api/ledger/parse/trustline.js rename to src/ledger/parse/trustline.js diff --git a/src/api/ledger/parse/utils.js b/src/ledger/parse/utils.js similarity index 97% rename from src/api/ledger/parse/utils.js rename to src/ledger/parse/utils.js index 250dec6c..4a1e0f31 100644 --- a/src/api/ledger/parse/utils.js +++ b/src/ledger/parse/utils.js @@ -64,7 +64,7 @@ function parseOutcome(tx: Object): ?Object { }; } -function hexToString(hex) { +function hexToString(hex: string): ?string { return hex ? new Buffer(hex, 'hex').toString('utf-8') : undefined; } @@ -84,6 +84,7 @@ function parseMemos(tx: Object): ?Array { module.exports = { parseOutcome, parseMemos, + hexToString, adjustQualityForXRP, dropsToXrp: utils.common.dropsToXrp, constants: utils.common.constants, diff --git a/src/api/ledger/pathfind-types.js b/src/ledger/pathfind-types.js similarity index 100% rename from src/api/ledger/pathfind-types.js rename to src/ledger/pathfind-types.js diff --git a/src/api/ledger/pathfind.js b/src/ledger/pathfind.js similarity index 100% rename from src/api/ledger/pathfind.js rename to src/ledger/pathfind.js diff --git a/src/api/ledger/settings.js b/src/ledger/settings.js similarity index 100% rename from src/api/ledger/settings.js rename to src/ledger/settings.js diff --git a/src/api/ledger/transaction-types.js b/src/ledger/transaction-types.js similarity index 100% rename from src/api/ledger/transaction-types.js rename to src/ledger/transaction-types.js diff --git a/src/api/ledger/transaction.js b/src/ledger/transaction.js similarity index 100% rename from src/api/ledger/transaction.js rename to src/ledger/transaction.js diff --git a/src/api/ledger/transactions.js b/src/ledger/transactions.js similarity index 100% rename from src/api/ledger/transactions.js rename to src/ledger/transactions.js diff --git a/src/api/ledger/trustlines-types.js b/src/ledger/trustlines-types.js similarity index 100% rename from src/api/ledger/trustlines-types.js rename to src/ledger/trustlines-types.js diff --git a/src/api/ledger/trustlines.js b/src/ledger/trustlines.js similarity index 100% rename from src/api/ledger/trustlines.js rename to src/ledger/trustlines.js diff --git a/src/api/ledger/types.js b/src/ledger/types.js similarity index 100% rename from src/api/ledger/types.js rename to src/ledger/types.js diff --git a/src/api/ledger/utils.js b/src/ledger/utils.js similarity index 100% rename from src/api/ledger/utils.js rename to src/ledger/utils.js diff --git a/src/api/offline/ledgerhash.js b/src/offline/ledgerhash.js similarity index 100% rename from src/api/offline/ledgerhash.js rename to src/offline/ledgerhash.js diff --git a/src/api/server/server.js b/src/server/server.js similarity index 100% rename from src/api/server/server.js rename to src/server/server.js diff --git a/src/api/transaction/order.js b/src/transaction/order.js similarity index 100% rename from src/api/transaction/order.js rename to src/transaction/order.js diff --git a/src/api/transaction/ordercancellation.js b/src/transaction/ordercancellation.js similarity index 100% rename from src/api/transaction/ordercancellation.js rename to src/transaction/ordercancellation.js diff --git a/src/api/transaction/payment.js b/src/transaction/payment.js similarity index 100% rename from src/api/transaction/payment.js rename to src/transaction/payment.js diff --git a/src/api/transaction/settings-types.js b/src/transaction/settings-types.js similarity index 100% rename from src/api/transaction/settings-types.js rename to src/transaction/settings-types.js diff --git a/src/api/transaction/settings.js b/src/transaction/settings.js similarity index 100% rename from src/api/transaction/settings.js rename to src/transaction/settings.js diff --git a/src/api/transaction/sign.js b/src/transaction/sign.js similarity index 100% rename from src/api/transaction/sign.js rename to src/transaction/sign.js diff --git a/src/api/transaction/submit.js b/src/transaction/submit.js similarity index 100% rename from src/api/transaction/submit.js rename to src/transaction/submit.js diff --git a/src/api/transaction/suspended-payment-cancellation.js b/src/transaction/suspended-payment-cancellation.js similarity index 100% rename from src/api/transaction/suspended-payment-cancellation.js rename to src/transaction/suspended-payment-cancellation.js diff --git a/src/api/transaction/suspended-payment-creation.js b/src/transaction/suspended-payment-creation.js similarity index 100% rename from src/api/transaction/suspended-payment-creation.js rename to src/transaction/suspended-payment-creation.js diff --git a/src/api/transaction/suspended-payment-execution.js b/src/transaction/suspended-payment-execution.js similarity index 100% rename from src/api/transaction/suspended-payment-execution.js rename to src/transaction/suspended-payment-execution.js diff --git a/src/api/transaction/trustline.js b/src/transaction/trustline.js similarity index 100% rename from src/api/transaction/trustline.js rename to src/transaction/trustline.js diff --git a/src/api/transaction/types.js b/src/transaction/types.js similarity index 100% rename from src/api/transaction/types.js rename to src/transaction/types.js diff --git a/src/api/transaction/utils.js b/src/transaction/utils.js similarity index 100% rename from src/api/transaction/utils.js rename to src/transaction/utils.js diff --git a/test/account-test.js b/test/account-test.js deleted file mode 100644 index e32f7ad0..00000000 --- a/test/account-test.js +++ /dev/null @@ -1,205 +0,0 @@ -/* eslint-disable max-nested-callbacks */ -'use strict'; -const _ = require('lodash'); -const assert = require('assert'); -const Account = require('ripple-lib').Account; - - -function createRemote(remoteOptions = {}) { - return { - on: function() {}, - requestAccountInfo: function(options, callback) { - if (options.account === 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ') { - callback({ - error: 'remoteError', - error_message: 'Remote reported an error.', - remote: { - account: 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ', - error: 'actNotFound', - error_code: 15, - error_message: 'Account not found.', - id: 3, - ledger_current_index: 6391106, - request: { - account: 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ', - command: 'account_info', - id: 3, - ident: 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ' - }, - status: 'error', - type: 'response' - }, - result: 'remoteError', - engine_result: 'remoteError', - result_message: 'Remote reported an error.', - engine_result_message: 'Remote reported an error.', - message: 'Remote reported an error.' - }); - } else if (options.account === 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz') { - callback(null, { - account_data: - _.assign({ - Account: 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz', - Flags: parseInt(65536 | 0x00100000, 10), - LedgerEntryType: 'AccountRoot', - RegularKey: 'rNw4ozCG514KEjPs5cDrqEcdsi31Jtfm5r' - }, remoteOptions) - }); - } - } - }; -} - -describe('Account', function() { - - describe('#_publicKeyToAddress()', function() { - - it('should throw an error if the key is invalid', function() { - try { - Account._publicKeyToAddress('not a real key'); - } catch (e) { - assert(e); - } - }); - - it('should return unchanged a valid UINT160', function() { - assert.strictEqual( - Account._publicKeyToAddress('rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - }); - - it('should parse a hex-encoded public key as a UINT160', function() { - assert.strictEqual( - Account._publicKeyToAddress( - '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332'), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - - assert.strictEqual( - Account._publicKeyToAddress( - '03BFA879C00D58CF55F2B5975FF9B5293008FF49BEFB3EE6BEE2814247BF561A23'), - 'rLpq5RcRzA8FU1yUqEPW4xfsdwon7casuM'); - - assert.strictEqual( - Account._publicKeyToAddress( - '02DF0AB18930B6410CA9F55CB37541F1FED891B8EDF8AB1D01D8F23018A4B204A7'), - 'rP4yWwjoDGF2iZSBdAQAgpC449YDezEbT1'); - - assert( - Account._publicKeyToAddress( - '0310C451A40CAFFD39D6B8A3BD61BF65BCA55246E9DABC3170EBE431D30655B61F'), - 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ'); - }); - - }); - - // XXX: clean up the stubbed out remote methods - - describe('#publicKeyIsActive()', function() { - - it('should respond true if the public key corresponds to the account ' + - ' address and the master key IS NOT disabled', function(done) { - - const options = {Flags: 65536}; - const account = new Account(createRemote(options), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - account.publicKeyIsActive( - '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', - function(err, is_valid) { - - assert(err === null); - assert(is_valid === true); - done(); - }); - - }); - - it('should respond false if the public key corresponds to the account ' + - ' address and the master key IS disabled', function(done) { - - const account = new Account(createRemote(), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - account.publicKeyIsActive( - '025B32A54BFA33FB781581F49B235C0E2820C929FF41E677ADA5D3E53CFBA46332', - function(err, is_valid) { - assert(err === null); - assert(is_valid === false); - done(); - }); - - }); - - it('should respond true if the public key corresponds to the regular key', - function(done) { - - const account = new Account(createRemote(), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - account.publicKeyIsActive( - '02BE53B7ACBB0900E0BB7729C9CAC1033A0137993B17800BD1191BBD1B29D96A8C', - function(err, is_valid) { - - assert(err === null); - assert(is_valid === true); - done(); - - }); - - }); - - it('should respond false if the public key does not correspond to an ' + - ' active public key for the account', function(done) { - - const account = new Account(createRemote(), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - account.publicKeyIsActive( - '032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', - function(err, is_valid) { - assert(err === null); - assert(is_valid === false); - done(); - }); - - }); - - it('should respond false if the public key is invalid', function(done) { - - const account = new Account(createRemote(), - 'rKXCummUHnenhYudNb9UoJ4mGBR75vFcgz'); - account.publicKeyIsActive('not a real public key', err => { - assert(err); - done(); - }); - - }); - - it('should assume the master key is valid for unfunded accounts', done => { - - const account = new Account(createRemote(), - 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ'); - account.publicKeyIsActive( - '0310C451A40CAFFD39D6B8A3BD61BF65BCA55246E9DABC3170EBE431D30655B61F', - function(err, is_valid) { - assert(!err); - assert(is_valid); - done(); - }); - - }); - - it('should respond false if the public key does not correspond to an ' + - ' active public key for the unfunded account', function(done) { - - const account = new Account(createRemote(), - 'rLdfp6eoR948KVxfn6EpaaNTKwfwXhzSeQ'); - account.publicKeyIsActive( - '032ECDA93970BC7E8872EF6582CB52A5557F117244A949EB4FA8AC7688CF24FBC8', - function(err, is_valid) { - assert(err === null); - assert(is_valid === false); - done(); - }); - - }); - - }); - -}); diff --git a/test/amount-test-error.js b/test/amount-test-error.js deleted file mode 100644 index 234e0036..00000000 --- a/test/amount-test-error.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; -const assert = require('assert'); -const Amount = require('ripple-lib').Amount; -const Remote = require('ripple-lib').Remote; - -const data = require('./fixtures/negative-error'); - - -describe.skip('Amount ', function() { - it('Show "Offer total cannot be negative" error', function() { - const a1 = { - currency: 'JPY', - issuer: 'r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN', - value: '66436.33517689175' - }; - const a2 = { - currency: 'JPY', - issuer: 'r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN', - value: '66435.49665972557' - }; - const a1a = Amount.from_json(a1); - const res = a1a.add(a2).subtract(a2).subtract(a1); - - console.log(res.to_human()); - assert(!res.is_negative(), 'Offer total cannot be negative'); - }); - - it('Show Details of "Offer total cannot be negative" error', function() { - const book = new Remote().createOrderBook({ - currency_gets: 'JPY', - issuer_gets: 'r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN', - currency_pays: 'XRP' - }); - book._subscribed = true; - book._synced = true; - book._offers = data._offers; - book._offerCounts = data._offerCounts; - book._ownerFundsUnadjusted = data._ownerFundsUnadjusted; - book._ownerFunds = data._ownerFunds; - book._ownerOffersTotal = data._ownerOffersTotal; - book._issuerTransferRate = 1000000000; - book._remote._handleTransaction(data.message1); - book._remote._handleTransaction(data.lastMessage); - }); - -}); diff --git a/test/amount-test.js b/test/amount-test.js deleted file mode 100644 index 51c49ac3..00000000 --- a/test/amount-test.js +++ /dev/null @@ -1,1190 +0,0 @@ -/* eslint-disable max-len */ -'use strict'; -const assert = require('assert'); -const Amount = require('ripple-lib').Amount; - -describe('Amount', function() { - describe('Negatives', function() { - it('Number 1', function() { - assert.strictEqual(Amount.from_human('0').add(Amount.from_human('-1')).to_human(), '-1'); - }); - }); - describe('Positives', function() { - it('Number 1', function() { - assert(Amount.from_json('1').is_positive()); - }); - }); - describe('Positives', function() { - it('Number 1', function() { - assert(Amount.from_json('1').is_positive()); - }); - }); - // also tested extensively in other cases - describe('to_human', function() { - it('12345.6789 XAU', function() { - assert.strictEqual(Amount.from_human('12345.6789 XAU').to_human(), '12,345.6789'); - }); - it('12345.678901234 XAU', function() { - assert.strictEqual(Amount.from_human('12345.678901234 XAU').to_human(), '12,345.678901234'); - }); - it('to human, precision -1, should be ignored, precision needs to be >= 0', function() { - assert.strictEqual(Amount.from_human('12345.678901234 XAU').to_human({precision: -1}), '12,346'); - }); - it('to human, precision 0', function() { - assert.strictEqual(Amount.from_human('12345.678901234 XAU').to_human({precision: 0}), '12,346'); - }); - it('to human, precision 1', function() { - assert.strictEqual(Amount.from_human('12345.678901234 XAU').to_human({precision: 1}), '12,345.7'); - }); - it('to human, precision 2', function() { - assert.strictEqual(Amount.from_human('12345.678901234 XAU').to_human({precision: 2}), '12,345.68'); - }); - it('to human, precision 3', function() { - assert.strictEqual(Amount.from_human('12345.678901234 XAU').to_human({precision: 3}), '12,345.679'); - }); - it('to human, precision 4', function() { - assert.strictEqual(Amount.from_human('12345.678901234 XAU').to_human({precision: 4}), '12,345.6789'); - }); - it('to human, precision 5', function() { - assert.strictEqual(Amount.from_human('12345.678901234 XAU').to_human({precision: 5}), '12,345.67890'); - }); - it('to human, precision -1, should be ignored, precision needs to be >= 0', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: -1}), '0'); - }); - it('to human, precision 0', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: 0}), '0'); - }); - it('to human, precision 1', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: 1}), '0.0'); - }); - it('to human, precision 2', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: 2}), '0.00'); - }); - it('to human, precision 5', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: 5}), '0.00012'); - }); - it('to human, precision 6', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: 6}), '0.000123'); - }); - it('to human, precision 16', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: 16}), '0.00012345'); - }); - it('to human, precision 16, min_precision 16', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: 16, min_precision: 16}), '0.0001234500000000'); - }); - it('to human, precision 16, min_precision 12', function() { - assert.strictEqual(Amount.from_human('0.00012345 XAU').to_human({precision: 16, min_precision: 12}), '0.000123450000'); - }); - it('to human, precision 0, first decimal 4', function() { - assert.strictEqual(Amount.from_human('0.4 XAU').to_human({precision: 0}), '0'); - }); - it('to human, precision 0, first decimal 5', function() { - assert.strictEqual(Amount.from_human('0.5 XAU').to_human({precision: 0}), '1'); - }); - it('to human, precision 0, first decimal 8', function() { - assert.strictEqual(Amount.from_human('0.8 XAU').to_human({precision: 0}), '1'); - }); - it('to human, precision 0, precision 16', function() { - assert.strictEqual(Amount.from_human('0.0 XAU').to_human({precision: 16}), '0'); - }); - it('to human, precision 0, precision 8, min_precision 16', function() { - assert.strictEqual(Amount.from_human('0.0 XAU').to_human({precision: 8, min_precision: 16}), '0.0000000000000000'); - }); - it('to human, precision 0, first decimal 8', function() { - assert.strictEqual(Amount.from_human('0.8 XAU').to_human({precision: 0}), '1'); - }); - it('to human, precision 6, min_precision 6, max_sig_digits 20', function() { - assert.strictEqual(Amount.from_human('0.0 XAU').to_human({precision: 6, min_precision: 6, max_sig_digits: 20}), '0.000000'); - }); - it('to human, precision 16, min_precision 6, max_sig_digits 20', function() { - assert.strictEqual(Amount.from_human('0.0 XAU').to_human({precision: 16, min_precision: 6, max_sig_digits: 20}), '0.000000'); - }); - it('to human rounding edge case, precision 2, 1', function() { - assert.strictEqual(Amount.from_human('0.99 XAU').to_human({precision: 1}), '1.0'); - }); - it('to human rounding edge case, precision 2, 2', function() { - assert.strictEqual(Amount.from_human('0.99 XAU').to_human({precision: 2}), '0.99'); - }); - it('to human rounding edge case, precision 2, 3', function() { - assert.strictEqual(Amount.from_human('0.99 XAU').to_human({precision: 3}), '0.99'); - }); - it('to human rounding edge case, precision 2, 3 min precision 3', function() { - assert.strictEqual(Amount.from_human('0.99 XAU').to_human({precision: 3, min_precision: 3}), '0.990'); - }); - it('to human rounding edge case, precision 3, 2', function() { - assert.strictEqual(Amount.from_human('0.999 XAU').to_human({precision: 2}), '1.00'); - }); - it('to human very small number', function() { - assert.strictEqual(Amount.from_json('12e-20/USD').to_human(), '0.00000000000000000012'); - }); - it('to human very small number with precision', function() { - assert.strictEqual(Amount.from_json('12e-20/USD').to_human({precision: 20}), '0.00000000000000000012'); - }); - }); - describe('from_human', function() { - it('empty string', function() { - assert.strictEqual(Amount.from_human('').to_text_full(), 'NaN'); - }); - it('missing value', function() { - assert.strictEqual(Amount.from_human('USD').to_text_full(), 'NaN'); - }); - it('1 XRP', function() { - assert.strictEqual(Amount.from_human('1 XRP').to_text_full(), '1/XRP'); - }); - it('1 XRP human', function() { - assert.strictEqual(Amount.from_human('1 XRP').to_human_full(), '1/XRP'); - }); - it('1XRP human', function() { - assert.strictEqual(Amount.from_human('1XRP').to_human_full(), '1/XRP'); - }); - it('0.1 XRP', function() { - assert.strictEqual(Amount.from_human('0.1 XRP').to_text_full(), '0.1/XRP'); - }); - it('0.1 XRP human', function() { - assert.strictEqual(Amount.from_human('0.1 XRP').to_human_full(), '0.1/XRP'); - }); - it('0.1 USD', function() { - assert.strictEqual(Amount.from_human('0.1 USD').to_text_full(), '0.1/USD/NaN'); - }); - it('0.1 USD human', function() { - assert.strictEqual(Amount.from_human('0.1 USD').to_human_full(), '0.1/USD/NaN'); - }); - it('10000 USD', function() { - assert.strictEqual(Amount.from_human('10000 USD').to_text_full(), '10000/USD/NaN'); - }); - it('10000 USD human', function() { - assert.strictEqual(Amount.from_human('10000 USD').to_human_full(), '10,000/USD/NaN'); - }); - it('USD 10000', function() { - assert.strictEqual(Amount.from_human('USD 10000').to_text_full(), '10000/USD/NaN'); - }); - it('USD 10000 human', function() { - assert.strictEqual(Amount.from_human('USD 10000').to_human_full(), '10,000/USD/NaN'); - }); - it('12345.6789 XAU', function() { - assert.strictEqual(Amount.from_human('12345.6789 XAU').to_text_full(), '12345.6789/XAU/NaN'); - }); - it('12345.6789 XAU human', 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/015841551A748AD2C1F76FF6ECB0CCCD00000000/NaN'); - }); - it('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000 human', function() { - 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'); - }); - it('12345.6789 0000000000000000000000005553440000000000 human', function() { - assert.strictEqual(Amount.from_human('12345.6789 0000000000000000000000005553440000000000').to_human_full(), '12,345.6789/USD/NaN'); - }); - it('10 0000000000000000000000005553440000000000', function() { - assert.strictEqual(Amount.from_human('10 0000000000000000000000005553440000000000').to_text_full(), '10/USD/NaN'); - }); - it('10 0000000000000000000000005553440000000000 human', function() { - assert.strictEqual(Amount.from_human('10 0000000000000000000000005553440000000000').to_human_full(), '10/USD/NaN'); - }); - it('100 0000000000000000000000005553440000000000', function() { - assert.strictEqual(Amount.from_human('100 0000000000000000000000005553440000000000').to_text_full(), '100/USD/NaN'); - }); - it('100 0000000000000000000000005553440000000000 human', function() { - assert.strictEqual(Amount.from_human('100 0000000000000000000000005553440000000000').to_human_full(), '100/USD/NaN'); - }); - it('1000 0000000000000000000000005553440000000000', function() { - assert.strictEqual(Amount.from_human('1000 0000000000000000000000005553440000000000').to_text_full(), '1000/USD/NaN'); - }); - it('1000 0000000000000000000000005553440000000000 human', function() { - assert.strictEqual(Amount.from_human('1000 0000000000000000000000005553440000000000').to_human_full(), '1,000/USD/NaN'); - }); - it('-100 0000000000000000000000005553440000000000', function() { - assert.strictEqual(Amount.from_human('-100 0000000000000000000000005553440000000000').to_text_full(), '-100/USD/NaN'); - }); - it('-100 0000000000000000000000005553440000000000 human', function() { - assert.strictEqual(Amount.from_human('-100 0000000000000000000000005553440000000000').to_human_full(), '-100/USD/NaN'); - }); - it('-1000 0000000000000000000000005553440000000000', function() { - assert.strictEqual(Amount.from_human('-1000 0000000000000000000000005553440000000000').to_text_full(), '-1000/USD/NaN'); - }); - it('-1000 0000000000000000000000005553440000000000 human', function() { - assert.strictEqual(Amount.from_human('-1000 0000000000000000000000005553440000000000').to_human_full(), '-1,000/USD/NaN'); - }); - it('-1000.001 0000000000000000000000005553440000000000', function() { - assert.strictEqual(Amount.from_human('-1000.001 0000000000000000000000005553440000000000').to_text_full(), '-1000.001/USD/NaN'); - }); - it('-1000.001 0000000000000000000000005553440000000000 human', function() { - assert.strictEqual(Amount.from_human('-1000.001 0000000000000000000000005553440000000000').to_human_full(), '-1,000.001/USD/NaN'); - }); - it('XAU 12345.6789', function() { - assert.strictEqual(Amount.from_human('XAU 12345.6789').to_text_full(), '12345.6789/XAU/NaN'); - }); - it('XAU 12345.6789 human', function() { - assert.strictEqual(Amount.from_human('XAU 12345.6789').to_human_full(), '12,345.6789/XAU/NaN'); - }); - it('101 12345.6789', function() { - assert.strictEqual(Amount.from_human('101 12345.6789').to_text_full(), '12345.6789/101/NaN'); - }); - it('101 12345.6789 human', function() { - assert.strictEqual(Amount.from_human('101 12345.6789').to_human_full(), '12,345.6789/101/NaN'); - }); - it('12345.6789 101', function() { - assert.strictEqual(Amount.from_human('12345.6789 101').to_text_full(), '12345.6789/101/NaN'); - }); - it('12345.6789 101 human', function() { - assert.strictEqual(Amount.from_human('12345.6789 101').to_human_full(), '12,345.6789/101/NaN'); - }); - }); - describe('from_json', function() { - it('1 XRP', function() { - assert.strictEqual(Amount.from_json('1/XRP').to_text_full(), '1/XRP/NaN'); - }); - it('1 XRP human', function() { - assert.strictEqual(Amount.from_json('1/XRP').to_human_full(), '1/XRP/NaN'); - }); - }); - describe('from_number', function() { - it('Number 1', function() { - 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/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji'); - }); - it('Number 2', function() { - 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/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/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/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'); - }); - it('Multiply 3 USD with 3 "1" human', 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/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/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/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/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji'); - }); - }); - describe('text_full_rewrite', function() { - it('Number 1', function() { - assert.strictEqual('0.000001/XRP', Amount.text_full_rewrite(1)); - }); - }); - describe('json_rewrite', function() { - it('Number 1', function() { - assert.strictEqual('1', Amount.json_rewrite(1)); - }); - }); - describe('Amount validity', function() { - it('is_valid 1', function() { - assert(Amount.is_valid(1)); - }); - it('is_valid "1"', function() { - assert(Amount.is_valid('1')); - }); - it('is_valid "1/XRP"', function() { - assert(Amount.is_valid('1/XRP')); - }); - it('!is_valid NaN', function() { - assert(!Amount.is_valid(NaN)); - }); - it('!is_valid "xx"', function() { - assert(!Amount.is_valid('xx')); - }); - it('!is_valid_full 1', function() { - assert(!Amount.is_valid_full(1)); - }); - it('is_valid_full "1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL"', function() { - assert(Amount.is_valid_full('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL')); - }); - }); - describe('Amount parsing', function() { - it('Parse invalid string', function() { - assert.strictEqual(Amount.from_json('x').to_text(), 'NaN'); - assert.strictEqual(typeof Amount.from_json('x').to_text(), 'string'); - assert(isNaN(Amount.from_json('x').to_text())); - }); - it('parse dem', function() { - 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/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Parse native 0', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').to_text_full()); - }); - it('Parse native 0.0', function() { - assert.throws(function() { - Amount.from_json('0.0'); - }); - }); - it('Parse native -0', function() { - assert.strictEqual('0/XRP', Amount.from_json('-0').to_text_full()); - }); - it('Parse native -0.0', function() { - assert.throws(function() { - Amount.from_json('-0.0'); - }); - }); - it('Parse native 1000', function() { - assert.strictEqual('0.001/XRP', Amount.from_json('1000').to_text_full()); - }); - it('Parse native 12300000', function() { - assert.strictEqual('12.3/XRP', Amount.from_json('12300000').to_text_full()); - }); - it('Parse native -12300000', function() { - assert.strictEqual('-12.3/XRP', Amount.from_json('-12300000').to_text_full()); - }); - it('Parse 123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { - assert.strictEqual('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); - }); - it('Parse 12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { - assert.strictEqual('12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); - }); - it('Parse 12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { - assert.strictEqual('12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); - }); - it('Parse 1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { - assert.strictEqual('1.23/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); - }); - it('Parse -0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); - }); - it('Parse -0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); - }); - it('Parse 0.0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { - assert.strictEqual('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); - }); - it('Parse 0.0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() { - assert.strictEqual('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full()); - }); - it('Parse native 0 human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').to_human_full()); - }); - it('Parse native -0 human', function() { - assert.strictEqual('0/XRP', Amount.from_json('-0').to_human_full()); - }); - it('Parse native 1000 human', function() { - assert.strictEqual('0.001/XRP', Amount.from_json('1000').to_human_full()); - }); - it('Parse native 12300000 human', function() { - assert.strictEqual('12.3/XRP', Amount.from_json('12300000').to_human_full()); - }); - it('Parse native -12300000 human', function() { - assert.strictEqual('-12.3/XRP', Amount.from_json('-12300000').to_human_full()); - }); - it('Parse 123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function() { - assert.strictEqual('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full()); - }); - it('Parse 12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function() { - assert.strictEqual('12,300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full()); - }); - it('Parse 12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function() { - assert.strictEqual('12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full()); - }); - it('Parse 1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function() { - assert.strictEqual('1.23/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full()); - }); - it('Parse -0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full()); - }); - it('Parse -0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full()); - }); - it('Parse 0.0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function() { - assert.strictEqual('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/111/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full()); - }); - it('Parse 0.0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh human', function() { - assert.strictEqual('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/12D/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full()); - }); - }); - describe('Amount to_json', function() { - it('10 USD', function() { - const amount = Amount.from_human('10 USD').to_json(); - assert.strictEqual('10', amount.value); - assert.strictEqual('USD', amount.currency); - }); - it('10 0000000000000000000000005553440000000000', function() { - const amount = Amount.from_human('10 0000000000000000000000005553440000000000').to_json(); - assert.strictEqual('10', amount.value); - assert.strictEqual('USD', amount.currency); - }); - it('10 015841551A748AD2C1F76FF6ECB0CCCD00000000', function() { - const amount = Amount.from_human('10 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_json(); - assert.strictEqual('10', amount.value); - assert.strictEqual('015841551A748AD2C1F76FF6ECB0CCCD00000000', amount.currency); - }); - }); - describe('Amount operations', function() { - it('Negate native 123', function() { - assert.strictEqual('-0.000123/XRP', Amount.from_json('123').negate().to_text_full()); - }); - it('Negate native -123', function() { - assert.strictEqual('0.000123/XRP', Amount.from_json('-123').negate().to_text_full()); - }); - it('Negate non-native 123', function() { - assert.strictEqual('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').negate().to_text_full()); - }); - it('Negate non-native -123', function() { - assert.strictEqual('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').negate().to_text_full()); - }); - it('Clone non-native -123', function() { - assert.strictEqual('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').clone().to_text_full()); - }); - it('Add XRP to XRP', function() { - assert.strictEqual('0.0002/XRP', Amount.from_json('150').add(Amount.from_json('50')).to_text_full()); - }); - it('Add USD to USD', function() { - assert.strictEqual('200.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').add(Amount.from_json('50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Add 0 USD to 1 USD', function() { - assert.strictEqual('1', Amount.from_json('1/USD').add('0/USD').to_text()); - }); - it('Subtract USD from USD', function() { - assert.strictEqual('99.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').subtract(Amount.from_json('50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply 0 XRP with 0 XRP', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('0')).to_text_full()); - }); - it('Multiply 0 USD with 0 XRP', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('0')).to_text_full()); - }); - it('Multiply 0 XRP with 0 USD', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply 1 XRP with 0 XRP', function() { - assert.strictEqual('0/XRP', Amount.from_json('1').multiply(Amount.from_json('0')).to_text_full()); - }); - it('Multiply 1 USD with 0 XRP', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('0')).to_text_full()); - }); - it('Multiply 1 XRP with 0 USD', function() { - assert.strictEqual('0/XRP', Amount.from_json('1').multiply(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply 0 XRP with 1 XRP', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('1')).to_text_full()); - }); - it('Multiply 0 USD with 1 XRP', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('1')).to_text_full()); - }); - it('Multiply 0 XRP with 1 USD', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD', function() { - assert.equal('0.002/XRP', Amount.from_json('200').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD', function() { - assert.strictEqual('0.2/XRP', Amount.from_json('20000').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD', function() { - assert.strictEqual('20/XRP', Amount.from_json('2000000').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD, neg', function() { - assert.strictEqual('-0.002/XRP', Amount.from_json('200').multiply(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD, neg, frac', function() { - assert.strictEqual('-0.222/XRP', Amount.from_json('-6000').multiply(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply USD with USD', function() { - assert.strictEqual('20000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply USD with USD', function() { - assert.strictEqual('200000000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply EUR with USD, result < 1', function() { - assert.strictEqual('100000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply EUR with USD, neg', function() { - assert.strictEqual('-48000000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply EUR with USD, neg, <1', function() { - assert.strictEqual('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply EUR with XRP, factor < 1', function() { - assert.strictEqual('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000')).to_text_full()); - }); - it('Multiply EUR with XRP, neg', function() { - assert.strictEqual('-500/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('5')).to_text_full()); - }); - it('Multiply EUR with XRP, neg, <1', function() { - assert.strictEqual('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000')).to_text_full()); - }); - it('Multiply XRP with XRP', function() { - assert.strictEqual('0.0001/XRP', Amount.from_json('10').multiply(Amount.from_json('10')).to_text_full()); - }); - it('Divide XRP by USD', function() { - assert.strictEqual('0.00002/XRP', Amount.from_json('200').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide XRP by USD', function() { - assert.strictEqual('0.002/XRP', Amount.from_json('20000').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide XRP by USD', function() { - assert.strictEqual('0.2/XRP', Amount.from_json('2000000').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide XRP by USD, neg', function() { - assert.strictEqual('-0.00002/XRP', Amount.from_json('200').divide(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide XRP by USD, neg, frac', function() { - assert.strictEqual('-0.000162/XRP', Amount.from_json('-6000').divide(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide USD by USD', function() { - assert.strictEqual('200/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide USD by USD, fractional', function() { - assert.strictEqual('57142.85714285714/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('35/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide USD by USD', function() { - assert.strictEqual('20/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide EUR by USD, factor < 1', function() { - assert.strictEqual('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide EUR by USD, neg', function() { - assert.strictEqual('-12/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide EUR by USD, neg, <1', function() { - assert.strictEqual('-0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Divide EUR by XRP, result < 1', function() { - assert.strictEqual('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_text_full()); - }); - it('Divide EUR by XRP, neg', function() { - assert.strictEqual('-20/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('5')).to_text_full()); - }); - it('Divide EUR by XRP, neg, <1', function() { - assert.strictEqual('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_text_full()); - }); - it('Negate native 123 human', function() { - assert.strictEqual('-0.000123/XRP', Amount.from_json('123').negate().to_human_full()); - }); - it('Negate native -123 human', function() { - assert.strictEqual('0.000123/XRP', Amount.from_json('-123').negate().to_human_full()); - }); - it('Negate non-native 123 human', function() { - assert.strictEqual('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').negate().to_human_full()); - }); - it('Negate non-native -123 human', function() { - assert.strictEqual('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').negate().to_human_full()); - }); - it('Clone non-native -123 human', function() { - assert.strictEqual('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').clone().to_human_full()); - }); - it('Add XRP to XRP human', function() { - assert.strictEqual('0.0002/XRP', Amount.from_json('150').add(Amount.from_json('50')).to_human_full()); - }); - it('Add USD to USD human', function() { - assert.strictEqual('200.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').add(Amount.from_json('50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Add 0 USD to 1 USD human', function() { - assert.strictEqual('1', Amount.from_json('1/USD').add('0/USD').to_human()); - }); - it('Subtract USD from USD human', function() { - assert.strictEqual('99.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').subtract(Amount.from_json('50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply 0 XRP with 0 XRP human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('0')).to_human_full()); - }); - it('Multiply 0 USD with 0 XRP human', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('0')).to_human_full()); - }); - it('Multiply 0 XRP with 0 USD human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply 1 XRP with 0 XRP human', function() { - assert.strictEqual('0/XRP', Amount.from_json('1').multiply(Amount.from_json('0')).to_human_full()); - }); - it('Multiply 1 USD with 0 XRP human', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('0')).to_human_full()); - }); - it('Multiply 1 XRP with 0 USD human', function() { - assert.strictEqual('0/XRP', Amount.from_json('1').multiply(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply 0 XRP with 1 XRP human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('1')).to_human_full()); - }); - it('Multiply 0 USD with 1 XRP human', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('1')).to_human_full()); - }); - it('Multiply 0 XRP with 1 USD human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD human', function() { - assert.equal('0.002/XRP', Amount.from_json('200').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD human', function() { - assert.strictEqual('0.2/XRP', Amount.from_json('20000').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD human', function() { - assert.strictEqual('20/XRP', Amount.from_json('2000000').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD, neg human', function() { - assert.strictEqual('-0.002/XRP', Amount.from_json('200').multiply(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD, neg, frac human', function() { - assert.strictEqual('-0.222/XRP', Amount.from_json('-6000').multiply(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply USD with USD human', function() { - assert.strictEqual('20,000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply USD with USD human', function() { - assert.strictEqual('200,000,000,000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply EUR with USD, result < 1 human', function() { - assert.strictEqual('100,000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply EUR with USD, neg human', function() { - assert.strictEqual('-48,000,000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply EUR with USD, neg, <1 human', function() { - assert.strictEqual('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply EUR with XRP, factor < 1 human', function() { - assert.strictEqual('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000')).to_human_full()); - }); - it('Multiply EUR with XRP, neg human', function() { - assert.strictEqual('-500/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('5')).to_human_full()); - }); - it('Multiply EUR with XRP, neg, <1 human', function() { - assert.strictEqual('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000')).to_human_full()); - }); - it('Multiply XRP with XRP human', function() { - assert.strictEqual('0.0001/XRP', Amount.from_json('10').multiply(Amount.from_json('10')).to_human_full()); - }); - it('Divide XRP by USD human', function() { - assert.strictEqual('0.00002/XRP', Amount.from_json('200').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide XRP by USD human', function() { - assert.strictEqual('0.002/XRP', Amount.from_json('20000').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide XRP by USD human', function() { - assert.strictEqual('0.2/XRP', Amount.from_json('2000000').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide XRP by USD, neg human', function() { - assert.strictEqual('-0.00002/XRP', Amount.from_json('200').divide(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide XRP by USD, neg, frac human', function() { - assert.strictEqual('-0.000162/XRP', Amount.from_json('-6000').divide(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide USD by USD human', function() { - assert.strictEqual('200/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide USD by USD, fractional human', function() { - assert.strictEqual('57,142.85714285714/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('35/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide USD by USD human', function() { - assert.strictEqual('20/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide EUR by USD, factor < 1 human', function() { - assert.strictEqual('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide EUR by USD, neg human', function() { - assert.strictEqual('-12/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide EUR by USD, neg, <1 human', function() { - assert.strictEqual('-0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Divide EUR by XRP, result < 1 human', function() { - assert.strictEqual('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_human_full()); - }); - it('Divide EUR by XRP, neg human', function() { - assert.strictEqual('-20/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('5')).to_human_full()); - }); - it('Divide EUR by XRP, neg, <1 human', function() { - assert.strictEqual('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_human_full()); - }); - it('Divide by zero should throw', function() { - assert.throws(function() { - Amount.from_json(1).divide(Amount.from_json(0)); - }); - }); - it('Divide zero by number', function() { - assert.strictEqual('0', Amount.from_json(0).divide(Amount.from_json(1)).to_text()); - }); - it('Divide invalid by number', function() { - assert.throws(function() { - Amount.from_json('x').divide(Amount.from_json('1')); - }); - }); - it('Divide number by invalid', function() { - assert.throws(function() { - Amount.from_json('1').divide(Amount.from_json('x')); - }); - }); - it('amount.abs -1 == 1', function() { - assert.strictEqual('1', Amount.from_json(-1).abs().to_text()); - }); - it('amount.copyTo native', function() { - assert(isNaN(Amount.from_json('x').copyTo(new Amount())._value)); - }); - it('amount.copyTo zero', function() { - assert(!(Amount.from_json(0).copyTo(new Amount())._is_negative)); - }); - }); - describe('Amount comparisons', function() { - it('0 USD == 0 USD amount.equals string argument', function() { - const a = '0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'; - assert(Amount.from_json(a).equals(a)); - }); - it('0 USD == 0 USD', function() { - const a = Amount.from_json('0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(a.equals(b)); - assert(!a.not_equals_why(b)); - }); - it('0 USD == -0 USD', function() { - const a = Amount.from_json('0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('-0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(a.equals(b)); - assert(!a.not_equals_why(b)); - }); - it('0 XRP == 0 XRP', function() { - const a = Amount.from_json('0'); - const b = Amount.from_json('0'); - assert(a.equals(b)); - assert(!a.not_equals_why(b)); - }); - it('0 XRP == -0 XRP', function() { - const a = Amount.from_json('0'); - const b = Amount.from_json('-0'); - assert(a.equals(b)); - assert(!a.not_equals_why(b)); - }); - it('10 USD == 10 USD', function() { - const a = Amount.from_json('10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(a.equals(b)); - assert(!a.not_equals_why(b)); - }); - it('123.4567 USD == 123.4567 USD', function() { - const a = Amount.from_json('123.4567/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('123.4567/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(a.equals(b)); - assert(!a.not_equals_why(b)); - }); - it('10 XRP == 10 XRP', function() { - const a = Amount.from_json('10'); - const b = Amount.from_json('10'); - assert(a.equals(b)); - assert(!a.not_equals_why(b)); - }); - it('1.1 XRP == 1.1 XRP', function() { - const a = Amount.from_json('1100000'); - const b = Amount.from_json('11000000').ratio_human('10/XRP'); - assert(a.equals(b)); - assert(!a.not_equals_why(b)); - }); - it('0 USD == 0 USD (ignore issuer)', function() { - const a = Amount.from_json('0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('0/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP'); - assert(a.equals(b, true)); - assert(!a.not_equals_why(b, true)); - }); - it('1.1 USD == 1.10 USD (ignore issuer)', function() { - const a = Amount.from_json('1.1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('1.10/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP'); - assert(a.equals(b, true)); - assert(!a.not_equals_why(b, true)); - }); - // Exponent mismatch - it('10 USD != 100 USD', function() { - const a = Amount.from_json('10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('100/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'Non-XRP value differs.'); - }); - it('10 XRP != 100 XRP', function() { - const a = Amount.from_json('10'); - const b = Amount.from_json('100'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'XRP value differs.'); - }); - // Mantissa mismatch - it('1 USD != 2 USD', function() { - const a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('2/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'Non-XRP value differs.'); - }); - it('1 XRP != 2 XRP', function() { - const a = Amount.from_json('1'); - const b = Amount.from_json('2'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'XRP value differs.'); - }); - it('0.1 USD != 0.2 USD', function() { - const a = Amount.from_json('0.1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('0.2/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'Non-XRP value differs.'); - }); - // Sign mismatch - it('1 USD != -1 USD', function() { - const a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('-1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'Non-XRP sign differs.'); - }); - it('1 XRP != -1 XRP', function() { - const a = Amount.from_json('1'); - const b = Amount.from_json('-1'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'XRP sign differs.'); - }); - it('1 USD != 1 USD (issuer mismatch)', function() { - const a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('1/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'Non-XRP issuer differs: rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - }); - it('1 USD != 1 EUR', function() { - const a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('1/EUR/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'Non-XRP currency differs.'); - }); - it('1 USD != 1 XRP', function() { - const a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - const b = Amount.from_json('1'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'Native mismatch.'); - }); - it('1 XRP != 1 USD', function() { - const a = Amount.from_json('1'); - const b = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'); - assert(!a.equals(b)); - assert.strictEqual(a.not_equals_why(b), 'Native mismatch.'); - }); - }); - - describe('product_human', function() { - it('Multiply 0 XRP with 0 XRP', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0')).to_text_full()); - }); - it('Multiply 0 USD with 0 XRP', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('0')).to_text_full()); - }); - it('Multiply 0 XRP with 0 USD', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply 1 XRP with 0 XRP', function() { - assert.strictEqual('0/XRP', Amount.from_json('1').product_human(Amount.from_json('0')).to_text_full()); - }); - it('Multiply 1 USD with 0 XRP', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('0')).to_text_full()); - }); - it('Multiply 1 XRP with 0 USD', function() { - assert.strictEqual('0/XRP', Amount.from_json('1').product_human(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply 0 XRP with 1 XRP', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('1')).to_text_full()); - }); - it('Multiply 0 USD with 1 XRP', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('1')).to_text_full()); - }); - it('Multiply 0 XRP with 1 USD', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD', function() { - assert.equal('0.002/XRP', Amount.from_json('200').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD', function() { - assert.strictEqual('0.2/XRP', Amount.from_json('20000').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD', function() { - assert.strictEqual('20/XRP', Amount.from_json('2000000').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD, neg', function() { - assert.strictEqual('-0.002/XRP', Amount.from_json('200').product_human(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply XRP with USD, neg, frac', function() { - assert.strictEqual('-0.222/XRP', Amount.from_json('-6000').product_human(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply USD with USD', function() { - assert.strictEqual('20000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply USD with USD', function() { - assert.strictEqual('200000000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply EUR with USD, result < 1', function() { - assert.strictEqual('100000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full()); - }); - it('Multiply EUR with USD, neg', function() { - assert.strictEqual(Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-48000000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Multiply EUR with USD, neg, <1', function() { - assert.strictEqual(Amount.from_json('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Multiply EUR with XRP, factor < 1', function() { - assert.strictEqual(Amount.from_json('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000')).to_text_full(), '0.0001/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Multiply EUR with XRP, neg', function() { - assert.strictEqual(Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('5')).to_text_full(), '-0.0005/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Multiply EUR with XRP, neg, <1', function() { - assert.strictEqual(Amount.from_json('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000')).to_text_full(), '-0.0001/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - 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 0 XRP with 0 XRP human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0')).to_human_full()); - }); - it('Multiply 0 USD with 0 XRP human', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('0')).to_human_full()); - }); - it('Multiply 0 XRP with 0 USD human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply 1 XRP with 0 XRP human', function() { - assert.strictEqual('0/XRP', Amount.from_json('1').product_human(Amount.from_json('0')).to_human_full()); - }); - it('Multiply 1 USD with 0 XRP human', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('0')).to_human_full()); - }); - it('Multiply 1 XRP with 0 USD human', function() { - assert.strictEqual('0/XRP', Amount.from_json('1').product_human(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply 0 XRP with 1 XRP human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('1')).to_human_full()); - }); - it('Multiply 0 USD with 1 XRP human', function() { - assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('1')).to_human_full()); - }); - it('Multiply 0 XRP with 1 USD human', function() { - assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD human', function() { - assert.equal('0.002/XRP', Amount.from_json('200').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD human', function() { - assert.strictEqual('0.2/XRP', Amount.from_json('20000').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD human', function() { - assert.strictEqual('20/XRP', Amount.from_json('2000000').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD, neg human', function() { - assert.strictEqual('-0.002/XRP', Amount.from_json('200').product_human(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply XRP with USD, neg, frac human', function() { - assert.strictEqual('-0.222/XRP', Amount.from_json('-6000').product_human(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply USD with USD human', function() { - assert.strictEqual('20,000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply USD with USD human', function() { - assert.strictEqual('200,000,000,000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply EUR with USD, result < 1 human', function() { - assert.strictEqual('100,000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full()); - }); - it('Multiply EUR with USD, neg human', function() { - assert.strictEqual(Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-48,000,000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Multiply EUR with USD, neg, <1 human', function() { - assert.strictEqual(Amount.from_json('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Multiply EUR with XRP, factor < 1 human', function() { - assert.strictEqual(Amount.from_json('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000')).to_human_full(), '0.0001/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Multiply EUR with XRP, neg human', function() { - assert.strictEqual(Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('5')).to_human_full(), '-0.0005/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Multiply EUR with XRP, neg, <1 human', function() { - assert.strictEqual(Amount.from_json('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('2000')).to_human_full(), '-0.0001/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - 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'); - }); - }); - - describe('ratio_human', function() { - it('Divide USD by XRP', function() { - const a = Amount.from_json({ - value: '0.08161672093323858', - currency: 'USD', - issuer: 'rLFPPebckMYZf3urdomLsaqRGmQ6zHVrrK' - }); - const b = Amount.from_json('15000000'); - const c = a.ratio_human(b); - assert.deepEqual(c.to_json(), {value: '0.005441114728882572', - currency: 'USD', issuer: 'rLFPPebckMYZf3urdomLsaqRGmQ6zHVrrK'}); - }); - }); - - describe('_invert', function() { - it('Invert 1', function() { - assert.strictEqual(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_text_full(), '1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Invert 20', function() { - assert.strictEqual(Amount.from_json('20/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_text_full(), '0.05/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Invert 0.02', function() { - assert.strictEqual(Amount.from_json('0.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_text_full(), '50/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Invert 1 human', function() { - assert.strictEqual(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_human_full(), '1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Invert 20 human', function() { - assert.strictEqual(Amount.from_json('20/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_human_full(), '0.05/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - it('Invert 0.02 human', function() { - assert.strictEqual(Amount.from_json('0.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').invert().to_human_full(), '50/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }); - }); - - describe('from_quality', function() { - it('XRP/XRP', function() { - assert.throws(function() { - Amount.from_quality('7B73A610A009249B0CC0D4311E8BA7927B5A34D86634581C5F0FF9FF678E1000', 'XRP', NaN, {base_currency: 'XRP'}).to_text_full(); - }); - }); - it('BTC/XRP', function() { - assert.strictEqual(Amount.from_quality('7B73A610A009249B0CC0D4311E8BA7927B5A34D86634581C5F0FF9FF678E1000', 'XRP', NaN, {base_currency: 'BTC'}).to_text_full(), '44,970/XRP'); - }); - it('BTC/XRP inverse', function() { - assert.strictEqual(Amount.from_quality('37AAC93D336021AE94310D0430FFA090F7137C97D473488C4A0918D0DEF8624E', 'XRP', NaN, {inverse: true, base_currency: 'BTC'}).to_text_full(), '39,053.954453/XRP'); - }); - it('XRP/USD', function() { - assert.strictEqual(Amount.from_quality('DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4D05DCAA8FE12000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {base_currency: 'XRP'}).to_text_full(), '0.0165/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'); - }); - it('XRP/USD inverse', function() { - assert.strictEqual(Amount.from_quality('4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5C22A840E27DCA9B', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {inverse: true, base_currency: 'XRP'}).to_text_full(), '0.010251/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'); - }); - it('BTC/USD', function() { - assert.strictEqual(Amount.from_quality('6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC9858038D7EA4C68000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {base_currency: 'BTC'}).to_text_full(), '1000/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'); - }); - 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('BTC/XRP human', function() { - assert.strictEqual(Amount.from_quality('7B73A610A009249B0CC0D4311E8BA7927B5A34D86634581C5F0FF9FF678E1000', 'XRP', NaN, {base_currency: 'BTC'}).to_human_full(), '44,970/XRP'); - }); - it('BTC/XRP inverse human', function() { - assert.strictEqual(Amount.from_quality('37AAC93D336021AE94310D0430FFA090F7137C97D473488C4A0918D0DEF8624E', 'XRP', NaN, {inverse: true, base_currency: 'BTC'}).to_human_full(), '39,053.954453/XRP'); - }); - it('XRP/USD human', function() { - assert.strictEqual(Amount.from_quality('DFA3B6DDAB58C7E8E5D944E736DA4B7046C30E4F460FD9DE4D05DCAA8FE12000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {base_currency: 'XRP'}).to_human_full(), '0.0165/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'); - }); - it('XRP/USD inverse human', function() { - assert.strictEqual(Amount.from_quality('4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5C22A840E27DCA9B', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {inverse: true, base_currency: 'XRP'}).to_human_full(), '0.010251/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'); - }); - it('BTC/USD human', function() { - assert.strictEqual(Amount.from_quality('6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC9858038D7EA4C68000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {base_currency: 'BTC'}).to_human_full(), '1,000/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'); - }); - 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'); - }); - }); - - describe('amount limits', function() { - it('max JSON wire limite', function() { - assert.strictEqual(Amount.bi_xns_max, '100000000000000000'); - }); - - it('max JSON wire limite', function() { - assert.strictEqual(Amount.bi_xns_min, '-100000000000000000'); - }); - - it('max mantissa value', function() { - assert.strictEqual(Amount.bi_man_max_value, '9999999999999999'); - }); - - it('min mantissa value', function() { - assert.strictEqual(Amount.bi_man_min_value, '1000000000000000'); - }); - - it('from_json minimum XRP', function() { - const amt = Amount.from_json('-100000000000000000'); - assert.strictEqual(amt.to_json(), '-100000000000000000'); - }); - - it('from_json maximum XRP', function() { - const amt = Amount.from_json('100000000000000000'); - assert.strictEqual(amt.to_json(), '100000000000000000'); - }); - - it('from_json less than minimum XRP', function() { - assert.throws(function() { - Amount.from_json('-100000000000000001'); - }); - }); - - it('from_json more than maximum XRP', function() { - assert.throws(function() { - Amount.from_json('100000000000000001'); - }); - }); - - it('from_json minimum positive IOU', function() { - const amt = Amount.from_json('1e-81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt.to_text(), '1000000000000000e-96'); - assert.strictEqual(amt.to_text(), Amount.min_value); - }); - - it('from_json smallest-absolute-value negative IOU', function() { - const amt = Amount.from_json('-1e-81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt.to_text(), '-1000000000000000e-96'); - assert.strictEqual(amt.negate().to_text(), Amount.min_value); - }); - - it('from_json exceeding minimum positive IOU', function() { - assert.throws(function() { - Amount.from_json('1e-82/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }, 'Exceeding min absolute value of ' + Amount.min_value); - }); - - it('from_json exceeding minimum-absolute-value negative IOU', function() { - assert.throws(function() { - Amount.from_json('-1e-82/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }, 'Exceeding min absolute value of ' + Amount.min_value); - }); - - it('from_json maximum positive IOU', function() { - const amt = Amount.from_json('9999999999999999e80/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt.to_text(), '9999999999999999e80'); - }); - - it('from_json exceed maximum positive IOU', function() { - assert.throws(function() { - Amount.from_json('9999999999999999e81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }, 'Exceeding max value of ' + Amount.max_value); - }); - - it('from_json largest-absolute-value negative IOU', function() { - const amt = Amount.from_json('-9999999999999999e80/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt.to_text(), '-9999999999999999e80'); - }); - - it('from_json exceed largest-absolute-value negative IOU', function() { - assert.throws(function() { - Amount.from_json('-9999999999999999e81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - }, 'Exceeding max value of ' + Amount.max_value); - }); - - it('from_json normalize mantissa to valid max range, lost significant digits', function() { - const amt = Amount.from_json('99999999999999999999999999999999/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt.to_text(), '9999999999999999e16'); - }); - - it('from_json normalize mantissa to min valid range, lost significant digits', function() { - const amt = Amount.from_json('-0.0000000000000000000000001/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt.to_text(), '-1000000000000000e-40'); - }); - }); -}); diff --git a/test/config-example.js b/test/config-example.js deleted file mode 100644 index 1df83b72..00000000 --- a/test/config-example.js +++ /dev/null @@ -1,46 +0,0 @@ -// -// Configuration for unit tests: to be locally customized as needed. -// - -var path = require("path"); -var testconfig = require("./testconfig.js"); - -exports.accounts = testconfig.accounts; - -// Where to find the binary. -exports.rippled = path.resolve("build/rippled"); - -exports.server_default = "alpha"; - -// -// Configuration for servers. -// -// For testing, you might choose to target a persistent server at alternate ports. -// -exports.servers = { - // A local test server. - "alpha" : { - 'trusted' : true, - // "peer_ip" : "0.0.0.0", - // "peer_port" : 51235, - 'rpc_ip' : "0.0.0.0", - 'rpc_port' : 5005, - 'websocket_ip' : "127.0.0.1", - 'websocket_port' : 5006, - 'websocket_ssl' : false, - 'local_sequence' : true, - 'local_fee' : true, - // 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h", - // 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta" - } -}; - -exports.http_servers = { - // A local test server - "zed" : { - "ip" : "127.0.0.1", - "port" : 8088, - } -}; - -// vim:sw=2:sts=2:ts=8:et diff --git a/test/integration/connection-test.js b/test/integration/connection-test.js index 7323345b..ecbb54b0 100644 --- a/test/integration/connection-test.js +++ b/test/integration/connection-test.js @@ -1,5 +1,5 @@ 'use strict'; -const Connection = require('../../src/api/common/connection'); +const Connection = require('../../src/common/connection'); const request1 = { command: 'server_info' diff --git a/test/integration/integration-test.js b/test/integration/integration-test.js index ed9bd177..e8c1ed83 100644 --- a/test/integration/integration-test.js +++ b/test/integration/integration-test.js @@ -3,8 +3,8 @@ 'use strict'; const _ = require('lodash'); const assert = require('assert'); -const errors = require('../../src/api/common/errors'); -const validate = require('../../src/api/common').validate; +const errors = require('../../src/common/errors'); +const validate = require('../../src/common').validate; const wallet = require('./wallet'); const requests = require('../fixtures/api/requests'); const RippleAPI = require('../../src').RippleAPI; diff --git a/test/metadata-test.js b/test/metadata-test.js deleted file mode 100644 index ae3b1ac7..00000000 --- a/test/metadata-test.js +++ /dev/null @@ -1,36 +0,0 @@ -var assert = require('assert'); -var Meta = require('ripple-lib').Meta; - -describe('Meta', function() { - var meta = new Meta(require('./fixtures/payment-iou.json').metadata); - - function callback(el, idx, ary) { - assert.strictEqual(meta.nodes[idx],el); - } - - it('forEach', function() { - meta.forEach(callback); - }); - - it('map', function() { - meta.map(callback); - }); - - it('filter', function() { - meta.filter(callback); - }); - - it('every', function() { - meta.every(callback); - }); - - it('some', function() { - meta.some(callback); - }); - - it('reduce', function() { - meta.reduce(function(prev,curr,idx,ary) { - assert.strictEqual(meta.nodes[idx], curr); - }, []); - }); -}); \ No newline at end of file diff --git a/test/node_modules/ripple-lib b/test/node_modules/ripple-lib deleted file mode 120000 index 67e69782..00000000 --- a/test/node_modules/ripple-lib +++ /dev/null @@ -1 +0,0 @@ -../../src/core \ No newline at end of file diff --git a/test/orderbook-autobridge-test.js b/test/orderbook-autobridge-test.js deleted file mode 100644 index 1a781e2c..00000000 --- a/test/orderbook-autobridge-test.js +++ /dev/null @@ -1,856 +0,0 @@ -/* eslint-disable max-len */ - -'use strict'; - -const _ = require('lodash'); -const assert = require('assert-diff'); -const Remote = require('ripple-lib').Remote; -const OrderbookUtils = require('ripple-lib')._test.OrderbookUtils; -const addresses = require('./fixtures/addresses'); -const fixtures = require('./fixtures/orderbook'); -const IOUValue = require('ripple-lib-value').IOUValue; - -describe('OrderBook Autobridging', function() { - this.timeout(0); - - function createRemote() { - const remote = new Remote(); - - remote._ledger_current_index = 32570; - remote.isConnected = function() { - return true; - }; - - return remote; - } - - it('Initialize IOU/IOU', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - 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) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 1); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); - - assert.strictEqual(book._offersAutobridged[0].taker_gets_funded, '17.07639524223001'); - assert.strictEqual(book._offersAutobridged[0].taker_pays_funded, '58.61727326122974'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - leg one partially funded', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); - - legOneOffers[0].owner_funds = '2105863129'; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 1); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '7.273651248813431'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '24.96789265329184'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - leg two partially funded', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); - - legTwoOffers[0].owner_funds = '10'; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 1); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '10'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '34.32649132449533'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - leg two transfer rate', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1002000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); - - legTwoOffers[0].owner_funds = '10'; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '9.980039920159681'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '34.25797537722665'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - taker funds < leg two in', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); - - legOneOffers[0].owner_funds = '33461561812'; - - legTwoOffers[0].owner_funds = '360'; - legTwoOffers[0].TakerGets.value = '170.7639524223001'; - legTwoOffers[0].TakerPays = '49439476610'; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 1); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '108.6682345172846'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '373.019921005'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - leg one partially funded - owners equal', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); - - legOneOffers[0].owner_funds = '2105863129'; - - legTwoOffers[0].Account = legOneOffers[0].Account; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 1); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - leg one partially funded - owners equal - leg two in > leg one out', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); - - legOneOffers[0].owner_funds = '2105863129'; - - legTwoOffers[0].Account = legOneOffers[0].Account; - legTwoOffers[0].owner_funds = '360'; - legTwoOffers[0].TakerGets.value = '170.7639524223001'; - legTwoOffers[0].TakerPays = '49439476610'; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 1); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '108.6682345172846'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '373.0199210049999'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - leg one consumes leg two fully', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 2)); - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 2); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); - - assert(book._offersAutobridged[0].autobridged); - - assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5.038346688725268'); - assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '314.4026477437702'); - - assert(book._offersAutobridged[1].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - leg two consumes first leg one offer fully', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 2)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 1)); - - legTwoOffers[0].TakerGets.value = '170.7639524223001'; - legTwoOffers[0].TakerPays = '49439476610'; - legTwoOffers[0].owner_funds = '364.0299530003982'; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 2); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '108.6682345172846'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '373.019921005'); - - assert(book._offersAutobridged[0].autobridged); - - assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '62.0957179050155'); - assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '213.1791399943838'); - - assert(book._offersAutobridged[1].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - owners equal', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1002000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 2)); - - legOneOffers[0].owner_funds = '2105863129'; - legTwoOffers[1].owner_funds = '19.32660005780981'; - - legTwoOffers[0].Account = legOneOffers[0].Account; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 2); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); - - assert(book._offersAutobridged[0].autobridged); - - assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '0.4001139945128008'); - assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '24.96789265329184'); - - assert(book._offersAutobridged[1].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - owners equal - leg one overfunded', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1002000000); - - const legOneOffers = _.cloneDeep(fixtures.LEG_ONE_OFFERS.slice(0, 1)); - const legTwoOffers = _.cloneDeep(fixtures.LEG_TWO_OFFERS.slice(0, 2)); - - legOneOffers[0].owner_funds = '41461561812'; - - legTwoOffers[0].Account = legOneOffers[0].Account; - - legTwoOffers[1].owner_funds = '30'; - - book._legOneBook.setOffers(legOneOffers); - book._legTwoBook.setOffers(legTwoOffers); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 2); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '17.07639524223001'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '58.61727326122974'); - - assert(book._offersAutobridged[0].autobridged); - - assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5.038346688725268'); - assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '314.4026477437702'); - - assert(book._offersAutobridged[1].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - TakerPays < Quality * TakerGets', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - book._legOneBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: '75', - TakerPays: { - value: '50', - issuer: addresses.ISSUER, - currency: 'USD' - }, - owner_funds: '50', - quality: '1' - } - ]); - - book._legTwoBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '90', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '90', - owner_funds: '150', - quality: '1' - } - ]); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 1); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '75'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '75'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - update funded amount', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - book._legOneBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: '100', - TakerPays: { - value: '100', - issuer: addresses.ISSUER, - currency: 'USD' - }, - owner_funds: '50', - quality: '1' - }, - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: '50', - TakerPays: { - value: '100', - issuer: addresses.ISSUER, - currency: 'USD' - }, - quality: '2' - } - ]); - - book._legTwoBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '90', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '90', - owner_funds: '150', - quality: '1' - }, - { - Account: addresses.OTHER_ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '30', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '60', - owner_funds: '70', - quality: '2' - } - ]); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 3); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '90'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '90'); - - assert(book._offersAutobridged[0].autobridged); - - assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5'); - assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '10'); - - assert(book._offersAutobridged[1].autobridged); - - assert.strictEqual(book._offersAutobridged[2].TakerGets.value, '20'); - assert.strictEqual(book._offersAutobridged[2].TakerPays.value, '80'); - - assert(book._offersAutobridged[2].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - update funded amount - owners equal', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - book._legOneBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: '100', - TakerPays: { - value: '100', - issuer: addresses.ISSUER, - currency: 'USD' - }, - owner_funds: '50', - quality: '1' - }, - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: '20', - TakerPays: { - value: '100', - issuer: addresses.ISSUER, - currency: 'USD' - }, - quality: '5' - } - ]); - - book._legTwoBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '90', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '90', - owner_funds: '150', - quality: '1' - }, - { - Account: addresses.OTHER_ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '30', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '60', - owner_funds: '70', - quality: '2' - } - ]); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 3); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '90'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '90'); - - assert(book._offersAutobridged[0].autobridged); - - assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5'); - assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '10'); - - assert(book._offersAutobridged[1].autobridged); - - assert.strictEqual(book._offersAutobridged[2].TakerGets.value, '10'); - assert.strictEqual(book._offersAutobridged[2].TakerPays.value, '100'); - - assert(book._offersAutobridged[2].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - update funded amount - first two owners equal', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - book._legOneBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: '100', - TakerPays: { - value: '100', - issuer: addresses.ISSUER, - currency: 'USD' - }, - owner_funds: '50', - quality: '1' - }, - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: '100', - TakerPays: { - value: '200', - issuer: addresses.ISSUER, - currency: 'USD' - }, - quality: '2' - } - ]); - - book._legTwoBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '90', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '90', - owner_funds: '150', - quality: '1' - }, - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '30', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '60', - quality: '2' - }, - { - Account: addresses.OTHER_ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '20', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '40', - owner_funds: '70', - quality: '2' - } - ]); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 4); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '90'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '90'); - - assert(book._offersAutobridged[0].autobridged); - - assert.strictEqual(book._offersAutobridged[1].TakerGets.value, '5'); - assert.strictEqual(book._offersAutobridged[1].TakerPays.value, '10'); - - assert(book._offersAutobridged[1].autobridged); - - assert.strictEqual(book._offersAutobridged[2].TakerGets.value, '25'); - assert.strictEqual(book._offersAutobridged[2].TakerPays.value, '100'); - - assert(book._offersAutobridged[2].autobridged); - - assert.strictEqual(book._offersAutobridged[3].TakerGets.value, '20'); - assert.strictEqual(book._offersAutobridged[3].TakerPays.value, '80'); - - assert(book._offersAutobridged[3].autobridged); - - done(); - }); - - }); - - it('Compute autobridged offers - unfunded offer - owners equal', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'EUR', - issuer_gets: addresses.ISSUER, - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book._legOneBook._issuerTransferRate = new IOUValue(1000000000); - book._legTwoBook._issuerTransferRate = new IOUValue(1000000000); - - book._legOneBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: '75', - TakerPays: { - value: '75', - issuer: addresses.ISSUER, - currency: 'USD' - }, - owner_funds: '0', - quality: '1' - } - ]); - - book._legTwoBook.setOffers([ - { - Account: addresses.ACCOUNT, - BookDirectory: '3B95C29205977C2136BBC70F21895F8C8F471C8522BF446E570463F9CDB31517', - TakerGets: { - value: '90', - issuer: addresses.ISSUER, - currency: 'EUR' - }, - TakerPays: '90', - owner_funds: '150', - quality: '1' - } - ]); - - book._gotOffersFromLegOne = book._gotOffersFromLegTwo = true; - book.computeAutobridgedOffers(() => { - assert.strictEqual(book._offersAutobridged.length, 1); - - assert.strictEqual(book._offersAutobridged[0].TakerGets.value, '75'); - assert.strictEqual(book._offersAutobridged[0].TakerPays.value, '75'); - - assert(book._offersAutobridged[0].autobridged); - - done(); - }); - - }); - - it('convertOfferQualityToHexFromText', function() { - const bookDirectory = - '4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5D06F4C3362FE1D0'; - const quality = '195796912.5171664'; - assert.strictEqual( - OrderbookUtils.convertOfferQualityToHexFromText(quality), - bookDirectory.slice(-16) - ); - }); -}); diff --git a/test/orderbook-test.js b/test/orderbook-test.js deleted file mode 100644 index 7bd525e9..00000000 --- a/test/orderbook-test.js +++ /dev/null @@ -1,2477 +0,0 @@ -/* eslint-disable max-len */ - -'use strict'; - -const assert = require('assert-diff'); -const Remote = require('ripple-lib').Remote; -const Amount = require('ripple-lib').Amount; -const Meta = require('ripple-lib').Meta; -const addresses = require('./fixtures/addresses'); -const fixtures = require('./fixtures/orderbook'); -const IOUValue = require('ripple-lib-value').IOUValue; - -describe('OrderBook', function() { - - function createRemote() { - const remote = new Remote(); - remote._ledger_current_index = 32570; - remote.isConnected = function() { - return true; - }; - - return remote; - } - - it('toJSON', function() { - let book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - assert.deepEqual(book.toJSON(), { - taker_gets: { - currency: 'XRP' - }, - taker_pays: { - currency: 'BTC', - issuer: addresses.ISSUER - } - }); - - book = createRemote().createOrderBook({ - issuer_gets: addresses.ISSUER, - currency_gets: 'BTC', - currency_pays: 'XRP' - }); - - assert.deepEqual(book.toJSON(), { - taker_gets: { - currency: 'BTC', - issuer: addresses.ISSUER - }, - taker_pays: { - currency: 'XRP' - } - }); - }); - - it('Check orderbook validity', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - assert(book.isValid()); - }); - - it('Subscribe', function(done) { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_pays: 'XRP', - issuer_gets: addresses.ISSUER, - currency_gets: '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 = []; - } - - 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) { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book.once('unsubscribe', function() { - done(); - }); - - book.on('model', function() {}); - - book.unsubscribe(); - - assert(!book._subscribed); - assert(!book._shouldConnect); - assert.deepEqual(book.listeners(), []); - }); - - it('Automatic unsubscription - remove all listeners', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book.unsubscribe = function() { - done(); - }; - - book.on('model', function() {}); - book.removeAllListeners('model'); - }); - - it('Automatic unsubscription - once listener', function(done) { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book.unsubscribe = function() { - done(); - }; - - book.once('model', function() {}); - book.emit('model', {}); - }); - - it('Set owner funds', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book._issuerTransferRate = new IOUValue(1000000000); - book.setOwnerFunds(addresses.ACCOUNT, '1'); - - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT).to_text(), '1'); - }); - - it('Set owner funds - unadjusted funds', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - book.setOwnerFunds(addresses.ACCOUNT, '1'); - - assert.strictEqual(book._ownerFundsUnadjusted[addresses.ACCOUNT], '1'); - }); - - it('Set owner funds - invalid account', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - assert.throws(function() { - book.setOwnerFunds('0rrrrrrrrrrrrrrrrrrrrBZbvji', '1'); - }); - }); - - it('Set owner funds - invalid amount', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - assert.throws(function() { - book.setOwnerFunds(addresses.ACCOUNT, null); - }); - }); - - it('Has owner funds', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book._ownerFunds[addresses.ACCOUNT] = '1'; - assert(book.hasOwnerFunds(addresses.ACCOUNT)); - }); - - it('Delete owner funds', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book._ownerFunds[addresses.ACCOUNT] = '1'; - assert(book.hasOwnerFunds(addresses.ACCOUNT)); - - book.deleteOwnerFunds(addresses.ACCOUNT); - assert(!book.hasOwnerFunds(addresses.ACCOUNT)); - }); - - it('Increment owner offer count', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - assert.strictEqual(book.incrementOwnerOfferCount(addresses.ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 1); - }); - - it('Decrement owner offer count', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.incrementOwnerOfferCount(addresses.ACCOUNT); - - assert.strictEqual(book.decrementOwnerOfferCount(addresses.ACCOUNT), 0); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 0); - }); - - it('Decrement owner offer count - no more offers', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.incrementOwnerOfferCount(addresses.ACCOUNT); - - assert.strictEqual(book.decrementOwnerOfferCount(addresses.ACCOUNT), 0); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 0); - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT), undefined); - }); - - it('Subtract owner offer total', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._ownerOffersTotal[addresses.ACCOUNT] = Amount.from_json({ - value: 3, - currency: 'BTC', - issuer: addresses.ISSUER - }); - - const newAmount = book.subtractOwnerOfferTotal(addresses.ACCOUNT, { - value: 2, - currency: 'BTC', - issuer: addresses.ISSUER - }); - - const offerTotal = Amount.from_json({ - value: 1, - currency: 'BTC', - issuer: addresses.ISSUER - }); - - assert(newAmount.equals(offerTotal)); - }); - - it('Subtract owner offer total - negative total', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - assert.throws(function() { - book.subtractOwnerOfferTotal(addresses.ACCOUNT, { - value: 2, - currency: 'BTC', - issuer: addresses.ISSUER - }); - }); - }); - - it('Get owner offer total', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - book._ownerOffersTotal[addresses.ACCOUNT] = Amount.from_json({ - value: 3, - currency: 'BTC', - issuer: addresses.ISSUER - }); - - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '3'); - }); - - it('Get owner offer total - native', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._ownerOffersTotal[addresses.ACCOUNT] = Amount.from_json('3'); - - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '3'); - }); - - it('Get owner offer total - no total', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '0'); - }); - - it('Get owner offer total - native - no total', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - assert(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '0'); - }); - - it('Apply transfer rate - cached transfer rate', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - - assert.strictEqual(book.applyTransferRate('1'), '0.9980039920159681'); - }); - - it('Apply transfer rate - native currency', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book._issuerTransferRate = new IOUValue(1000000000); - - assert.strictEqual(book.applyTransferRate('0.9980039920159681'), '0.9980039920159681'); - }); - - it('Apply transfer rate - invalid balance', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - assert.throws(function() { - book.applyTransferRate('asdf'); - }); - }); - - it('Apply transfer rate - invalid transfer rate', function() { - const book = createRemote().createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - assert.throws(function() { - book.applyTransferRate('1'); - }); - }); - - it('Request transfer rate', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - remote.request = function(request) { - assert.deepEqual(request.message, { - command: 'account_info', - id: undefined, - account: addresses.ISSUER - }); - - request.emit('success', { - account_data: { - TransferRate: 1002000000 - } - }); - }; - - book.requestTransferRate(function(err, rate) { - assert.ifError(err); - assert(rate.equals(new IOUValue(1002000000))); - }); - }); - - it('Request transfer rate - not set', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - remote.request = function(request) { - assert.deepEqual(request.message, { - command: 'account_info', - id: undefined, - account: addresses.ISSUER - }); - - request.emit('success', { - account_data: { - } - }); - }; - - book.requestTransferRate(function(err, rate) { - assert.ifError(err); - assert(rate.equals(new IOUValue(1000000000))); - }); - }); - - it('Request transfer rate - cached transfer rate', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - - remote.request = function() { - assert(false); - }; - - book.requestTransferRate(function(err, rate) { - assert.ifError(err); - assert(rate.equals(new IOUValue(1002000000))); - }); - }); - - it('Request transfer rate - native currency', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - remote.request = function() { - assert(false); - }; - - book.requestTransferRate(function(err, rate) { - assert.ifError(err); - assert(rate.equals(new IOUValue(1000000000))); - assert(book._issuerTransferRate.equals(new IOUValue(1000000000))); - }); - }); - - it('Set offer funded amount - iou/xrp - fully funded', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'BTC', - currency_pays: 'XRP', - issuer_gets: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - - const offer = { - Account: addresses.ACCOUNT, - TakerGets: { - value: '100', - currency: 'BTC', - issuer: addresses.ISSUER - }, - TakerPays: '123456' - }; - - book.setOwnerFunds(addresses.ACCOUNT, '100.1234'); - book.setOfferFundedAmount(offer); - - const expected = { - Account: addresses.ACCOUNT, - TakerGets: offer.TakerGets, - TakerPays: offer.TakerPays, - is_fully_funded: true, - taker_gets_funded: '100', - taker_pays_funded: '123456', - owner_funds: '100.1234' - }; - - assert.deepEqual(offer, expected); - }); - - it('Set offer funded amount - iou/xrp - unfunded', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'BTC', - currency_pays: 'XRP', - issuer_gets: addresses.ISSUER - }); - - book._issuerTransferRate = new IOUValue(1000000000); - - const offer = { - Account: addresses.ACCOUNT, - TakerGets: { - value: '100', - currency: 'BTC', - issuer: addresses.ISSUER - }, - TakerPays: '123456', - quality: '1234.56' - }; - - book.setOwnerFunds(addresses.ACCOUNT, '99'); - book.setOfferFundedAmount(offer); - - const expected = { - Account: addresses.ACCOUNT, - TakerGets: offer.TakerGets, - TakerPays: offer.TakerPays, - is_fully_funded: false, - taker_gets_funded: '99', - taker_pays_funded: '122221', - owner_funds: '99', - quality: '1234.56' - }; - - assert.deepEqual(offer, expected); - }); - - it('Set offer funded amount - xrp/iou - funded', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book._issuerTransferRate = new IOUValue(1000000000); - - const offer = { - Account: addresses.ACCOUNT, - TakerGets: '100', - TakerPays: { - value: '123.456', - currency: 'BTC', - issuer: addresses.ISSUER - } - }; - - book.setOwnerFunds(addresses.ACCOUNT, '100100000'); - book.setOfferFundedAmount(offer); - - const expected = { - Account: addresses.ACCOUNT, - TakerGets: offer.TakerGets, - TakerPays: offer.TakerPays, - is_fully_funded: true, - taker_gets_funded: '100', - taker_pays_funded: '123.456', - owner_funds: '100100000' - }; - - assert.deepEqual(offer, expected); - }); - - it('Set offer funded amount - xrp/iou - unfunded', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book._issuerTransferRate = new IOUValue(1000000000); - - const offer = { - Account: addresses.ACCOUNT, - TakerGets: '100', - TakerPays: { - value: '123.456', - currency: 'BTC', - issuer: addresses.ISSUER - }, - quality: '1.23456' - }; - - book.setOwnerFunds(addresses.ACCOUNT, '99'); - book.setOfferFundedAmount(offer); - - const expected = { - Account: addresses.ACCOUNT, - TakerGets: offer.TakerGets, - TakerPays: offer.TakerPays, - is_fully_funded: false, - taker_gets_funded: '99', - taker_pays_funded: '122.22144', - owner_funds: '99', - quality: '1.23456' - }; - - assert.deepEqual(offer, expected); - }); - - it('Set offer funded amount - zero funds', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - book._issuerTransferRate = new IOUValue(1000000000); - - const offer = { - Account: addresses.ACCOUNT, - TakerGets: { - value: '100', - currency: 'BTC', - issuer: addresses.ISSUER - }, - TakerPays: '123456' - }; - - book.setOwnerFunds(addresses.ACCOUNT, '0'); - book.setOfferFundedAmount(offer); - - assert.deepEqual(offer, { - Account: addresses.ACCOUNT, - TakerGets: offer.TakerGets, - TakerPays: offer.TakerPays, - is_fully_funded: false, - taker_gets_funded: '0', - taker_pays_funded: '0', - owner_funds: '0' - }); - }); - - it('Check is balance change node', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - const meta = new Meta({ - AffectedNodes: [{ - ModifiedNode: { - FinalFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '-1' - }, - Flags: 131072, - HighLimit: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '100' - }, - HighNode: '0000000000000000', - LowLimit: { - currency: 'USD', - issuer: 'r3PDtZSa5LiYp1Ysn1vMuMzB59RzV3W9QH', - value: '0' - }, - LowNode: '0000000000000000' - }, - LedgerEntryType: 'RippleState', - LedgerIndex: 'EA4BF03B4700123CDFFB6EB09DC1D6E28D5CEB7F680FB00FC24BC1C3BB2DB959', - PreviousFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '0' - } - }, - PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8', - PreviousTxnLgrSeq: 343570 - } - }] - }); - - assert(book.isBalanceChangeNode(meta.getNodes()[0])); - }); - - it('Check is balance change node - not balance change', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - const meta = new Meta({ - AffectedNodes: [{ - ModifiedNode: { - FinalFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '-1' - }, - Flags: 131072, - HighLimit: { - currency: 'USD', - issuer: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - value: '100' - }, - HighNode: '0000000000000000', - LowLimit: { - currency: 'USD', - issuer: 'r3PDtZSa5LiYp1Ysn1vMuMzB59RzV3W9QH', - value: '0' - }, - LowNode: '0000000000000000' - }, - LedgerEntryType: 'RippleState', - LedgerIndex: 'EA4BF03B4700123CDFFB6EB09DC1D6E28D5CEB7F680FB00FC24BC1C3BB2DB959', - PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8', - PreviousTxnLgrSeq: 343570 - } - }] - }); - - assert(!book.isBalanceChangeNode(meta.getNodes()[0])); - }); - - it('Check is balance change node - different currency', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'BTC', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - const meta = new Meta({ - AffectedNodes: [{ - ModifiedNode: { - FinalFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '-1' - }, - Flags: 131072, - HighLimit: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '100' - }, - HighNode: '0000000000000000', - LowLimit: { - currency: 'USD', - issuer: 'r3PDtZSa5LiYp1Ysn1vMuMzB59RzV3W9QH', - value: '0' - }, - LowNode: '0000000000000000' - }, - LedgerEntryType: 'RippleState', - LedgerIndex: 'EA4BF03B4700123CDFFB6EB09DC1D6E28D5CEB7F680FB00FC24BC1C3BB2DB959', - PreviousFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '0' - } - }, - PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8', - PreviousTxnLgrSeq: 343570 - } - }] - }); - - assert(!book.isBalanceChangeNode(meta.getNodes()[0])); - }); - - it('Check is balance change node - different issuer', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - const meta = new Meta({ - AffectedNodes: [{ - ModifiedNode: { - FinalFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '-1' - }, - Flags: 131072, - HighLimit: { - currency: 'USD', - issuer: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - value: '100' - }, - HighNode: '0000000000000000', - LowLimit: { - currency: 'USD', - issuer: 'r3PDtZSa5LiYp1Ysn1vMuMzB59RzV3W9QH', - value: '0' - }, - LowNode: '0000000000000000' - }, - LedgerEntryType: 'RippleState', - LedgerIndex: 'EA4BF03B4700123CDFFB6EB09DC1D6E28D5CEB7F680FB00FC24BC1C3BB2DB959', - PreviousFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '0' - } - }, - PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8', - PreviousTxnLgrSeq: 343570 - } - }] - }); - - assert(!book.isBalanceChangeNode(meta.getNodes()[0])); - }); - - it('Check is balance change node - native currency', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - const meta = new Meta({ - AffectedNodes: [{ - ModifiedNode: { - FinalFields: { - Account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - Balance: '9999999990', - Flags: 0, - OwnerCount: 1, - Sequence: 2 - }, - LedgerEntryType: 'AccountRoot', - LedgerIndex: '4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05', - PreviousFields: { - Balance: '10000000000', - OwnerCount: 0, - Sequence: 1 - }, - PreviousTxnID: 'B24159F8552C355D35E43623F0E5AD965ADBF034D482421529E2703904E1EC09', - PreviousTxnLgrSeq: 16154 - } - }] - }); - - assert(book.isBalanceChangeNode(meta.getNodes()[0])); - }); - - it('Check is balance change node - native currency - not balance change', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'BTC' - }); - - const meta = new Meta({ - AffectedNodes: [{ - ModifiedNode: { - FinalFields: { - Account: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV', - Balance: '78991384535796', - Flags: 0, - OwnerCount: 3, - Sequence: 188 - }, - LedgerEntryType: 'AccountRoot', - LedgerIndex: 'B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A', - PreviousTxnID: 'E9E1988A0F061679E5D14DE77DB0163CE0BBDC00F29E396FFD1DA0366E7D8904', - PreviousTxnLgrSeq: 195455 - } - }] - }); - - assert(!book.isBalanceChangeNode(meta.getNodes()[0])); - }); - - it('Parse account balance from node', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - const meta = new Meta({ - AffectedNodes: [ - { - ModifiedNode: { - FinalFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '10' - }, - Flags: 131072, - HighLimit: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '100' - }, - HighNode: '0000000000000000', - LowLimit: { - currency: 'USD', - issuer: 'r3PDtZSa5LiYp1Ysn1vMuMzB59RzV3W9QH', - value: '0' - }, - LowNode: '0000000000000000' - }, - LedgerEntryType: 'RippleState', - LedgerIndex: 'EA4BF03B4700123CDFFB6EB09DC1D6E28D5CEB7F680FB00FC24BC1C3BB2DB959', - PreviousFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '0' - } - }, - PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8', - PreviousTxnLgrSeq: 343570 - } - }, - { - ModifiedNode: { - FinalFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '-10' - }, - Flags: 131072, - HighLimit: { - currency: 'USD', - issuer: 'r3PDtZSa5LiYp1Ysn1vMuMzB59RzV3W9QH', - value: '100' - }, - HighNode: '0000000000000000', - LowLimit: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '0' - }, - LowNode: '0000000000000000' - }, - LedgerEntryType: 'RippleState', - LedgerIndex: 'EA4BF03B4700123CDFFB6EB09DC1D6E28D5CEB7F680FB00FC24BC1C3BB2DB959', - PreviousFields: { - Balance: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '0' - } - }, - PreviousTxnID: '53354D84BAE8FDFC3F4DA879D984D24B929E7FEB9100D2AD9EFCD2E126BCCDC8', - PreviousTxnLgrSeq: 343570 - } - } - ] - }); - - assert.deepEqual(book.parseAccountBalanceFromNode(meta.getNodes()[0]), { - account: 'r3PDtZSa5LiYp1Ysn1vMuMzB59RzV3W9QH', - balance: '10' - }); - - assert.deepEqual(book.parseAccountBalanceFromNode(meta.getNodes()[1]), { - account: 'r3PDtZSa5LiYp1Ysn1vMuMzB59RzV3W9QH', - balance: '10' - }); - }); - - it('Parse account balance from node - native currency', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - const meta = new Meta({ - AffectedNodes: [{ - ModifiedNode: { - FinalFields: { - Account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - Balance: '9999999990', - Flags: 0, - OwnerCount: 1, - Sequence: 2 - }, - LedgerEntryType: 'AccountRoot', - LedgerIndex: '4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05', - PreviousFields: { - Balance: '10000000000', - OwnerCount: 0, - Sequence: 1 - }, - PreviousTxnID: 'B24159F8552C355D35E43623F0E5AD965ADBF034D482421529E2703904E1EC09', - PreviousTxnLgrSeq: 16154 - } - }] - }); - - assert.deepEqual(book.parseAccountBalanceFromNode(meta.getNodes()[0]), { - account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - balance: '9999999990' - }); - }); - - it('Update funded amounts', function(done) { - let receivedChangedEvents = 0; - let receivedFundsChangedEvents = 0; - - const remote = createRemote(); - - const message = fixtures.transactionWithRippleState(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1000000000); - - book._offers = fixtures.fiatOffers(); - - book.on('offer_changed', function() { - receivedChangedEvents += 1; - }); - - book.on('offer_funds_changed', function(offer, previousFunds, newFunds) { - assert.strictEqual(previousFunds, '100'); - assert.strictEqual(newFunds, offer.taker_gets_funded); - assert.notStrictEqual(previousFunds, newFunds); - switch (++receivedFundsChangedEvents) { - case 1: - assert.strictEqual(offer.is_fully_funded, false); - assert.strictEqual(offer.taker_gets_funded, '10'); - assert.strictEqual(offer.taker_pays_funded, '1954238072'); - break; - case 2: - assert.strictEqual(offer.is_fully_funded, false); - assert.strictEqual(offer.taker_gets_funded, '0'); - assert.strictEqual(offer.taker_pays_funded, '0'); - break; - } - }); - - book._ownerFunds[addresses.ACCOUNT] = '20'; - book.updateFundedAmounts(message); - - setImmediate(function() { - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT).to_text(), fixtures.FIAT_BALANCE); - assert.strictEqual(receivedChangedEvents, 2); - assert.strictEqual(receivedFundsChangedEvents, 2); - done(); - }); - }); - - it('Update funded amounts - increase funds', function() { - let receivedFundsChangedEvents = 0; - - const remote = createRemote(); - - const message = fixtures.transactionWithRippleState({ - balance: '50' - }); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers({ - account_funds: '19' - })); - - book.on('offer_funds_changed', function(offer, previousFunds, newFunds) { - assert.strictEqual(newFunds, offer.taker_gets_funded); - assert.notStrictEqual(previousFunds, newFunds); - switch (++receivedFundsChangedEvents) { - case 1: - assert.strictEqual(previousFunds, '19'); - assert.strictEqual(offer.is_fully_funded, true); - assert.strictEqual(offer.taker_gets_funded, fixtures.TAKER_GETS); - assert.strictEqual(offer.taker_pays_funded, fixtures.TAKER_PAYS); - break; - case 2: - assert.strictEqual(previousFunds, '0'); - assert.strictEqual(offer.is_fully_funded, true); - assert.strictEqual(offer.taker_gets_funded, '4.9656112525'); - assert.strictEqual(offer.taker_pays_funded, '972251352'); - break; - } - }); - - book.updateFundedAmounts(message); - }); - - it('Update funded amounts - owner_funds', function(done) { - const remote = createRemote(); - - const message = fixtures.transactionWithRippleState(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - - book._offers = fixtures.fiatOffers(); - - book._ownerFunds[addresses.ACCOUNT] = '100'; - book.updateFundedAmounts(message); - - setImmediate(function() { - assert.strictEqual(book._offers[0].owner_funds, fixtures.FIAT_BALANCE); - assert.strictEqual(book._offers[1].owner_funds, fixtures.FIAT_BALANCE); - - done(); - }); - }); - - it('Update funded amounts - issuer transfer rate set', function(done) { - const remote = createRemote(); - - const message = fixtures.transactionWithRippleState(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - - book._ownerFunds[addresses.ACCOUNT] = '100'; - book._offers = fixtures.fiatOffers(); - - book.updateFundedAmounts(message); - - setImmediate(function() { - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT).to_text(), '9.980039920159681'); - - done(); - }); - }); - - it('Update funded amounts - native currency', function(done) { - let receivedChangedEvents = 0; - let receivedFundsChangedEvents = 0; - - const remote = createRemote(); - - const message = fixtures.transactionWithAccountRoot(); - - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'USD' - }); - - book._offers = fixtures.NATIVE_OFFERS; - - book.on('offer_changed', function() { - receivedChangedEvents += 1; - }); - - book.on('offer_funds_changed', function(offer, previousFunds, newFunds) { - assert.strictEqual(previousFunds, fixtures.NATIVE_BALANCE_PREVIOUS); - assert.strictEqual(newFunds, offer.taker_gets_funded); - assert.notStrictEqual(previousFunds, newFunds); - switch (++receivedFundsChangedEvents) { - case 1: - assert(offer.is_fully_funded); - break; - case 2: - assert(!offer.is_fully_funded); - break; - } - }); - - book._ownerFunds[addresses.ACCOUNT] = fixtures.NATIVE_BALANCE_PREVIOUS; - book.updateFundedAmounts(message); - - setImmediate(function() { - book.getOwnerFunds(addresses.ACCOUNT, fixtures.NATIVE_BALANCE); - assert.strictEqual(receivedChangedEvents, 2); - assert.strictEqual(receivedFundsChangedEvents, 2); - done(); - }); - }); - - it('Update funded amounts - no affected account', function(done) { - const remote = createRemote(); - - const message = fixtures.transactionWithAccountRoot({ - account: addresses.ACCOUNT - }); - - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'USD' - }); - - book._offers = fixtures.NATIVE_OFFERS; - - book._offers.__defineGetter__(0, function() { - assert(false, 'Iteration of offers for unaffected account'); - }); - - book.on('offer_changed', function() { - assert(false, 'offer_changed event emitted'); - }); - - book.on('offer_funds_changed', function() { - assert(false, 'offer_funds_changed event emitted'); - }); - - book.updateFundedAmounts(message); - - setImmediate(done); - }); - - it('Update funded amounts - no balance change', function(done) { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'XRP', - issuer_pays: addresses.ISSUER, - currency_pays: 'USD' - }); - - const message = fixtures.transactionWithInvalidAccountRoot(); - - book._offers = fixtures.NATIVE_OFFERS; - - book.on('offer_changed', function() { - assert(false, 'offer_changed event emitted'); - }); - - book.on('offer_funds_changed', function() { - assert(false, 'offer_funds_changed event emitted'); - }); - - assert.strictEqual(typeof book.parseAccountBalanceFromNode, 'function'); - - book.parseAccountBalanceFromNode = function() { - assert(false, 'getBalanceChange should not be called'); - }; - - book._ownerFunds[addresses.ACCOUNT] = '100'; - book.updateFundedAmounts(message); - - setImmediate(done); - }); - - it('Update funded amounts - deferred TransferRate', function(done) { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - const message = fixtures.transactionWithRippleState(); - - remote.request = function(request) { - assert.deepEqual(request.message, { - command: 'account_info', - id: undefined, - account: addresses.ISSUER - }); - - request.emit('success', fixtures.accountInfoResponse()); - - assert(book._issuerTransferRate.equals(new IOUValue(fixtures.TRANSFER_RATE))); - done(); - }; - - book._ownerFunds[addresses.ACCOUNT] = '100'; - book.updateFundedAmounts(message); - }); - - it('Set offers - issuer transfer rate set - iou/xrp', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - - const offers = fixtures.bookOffersResponse().offers; - - book.setOffers(offers); - - assert.strictEqual(book._offers.length, 5); - - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '275.85192574'); - assert.strictEqual(book.getOwnerOfferTotal(addresses.OTHER_ACCOUNT).to_text(), '24.060765960393'); - assert.strictEqual(book.getOwnerOfferTotal(addresses.THIRD_ACCOUNT).to_text(), '712.60995'); - assert.strictEqual(book.getOwnerOfferTotal(addresses.FOURTH_ACCOUNT).to_text(), '288.08'); - - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 2); - assert.strictEqual(book.getOwnerOfferCount(addresses.OTHER_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferCount(addresses.THIRD_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferCount(addresses.FOURTH_ACCOUNT), 1); - - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT).to_text(), '2006.015671538605'); - assert.strictEqual(book.getOwnerFunds(addresses.OTHER_ACCOUNT).to_text(), '24.01284027983332'); - assert.strictEqual(book.getOwnerFunds(addresses.THIRD_ACCOUNT).to_text(), '9053.294314019701'); - assert.strictEqual(book.getOwnerFunds(addresses.FOURTH_ACCOUNT).to_text(), '7229.594289344439'); - }); - - it('Set offers - issuer transfer rate set - iou/xrp - funded amounts', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - - const offers = fixtures.bookOffersResponse({ - account_funds: '233.13532' - }).offers; - - book.setOffers(offers); - - const offerOneTakerGetsFunded = Amount.from_json({ - value: book._offers[0].taker_gets_funded, - currency: 'USD', - issuer: addresses.ISSUER - }); - - const offerOneTakerGetsFundedExpected = Amount.from_json({ - value: '79.39192374', - currency: 'USD', - issuer: addresses.ISSUER - }); - - assert.strictEqual(offerOneTakerGetsFunded.equals(offerOneTakerGetsFundedExpected), true); - assert.strictEqual(book._offers[0].is_fully_funded, true); - - const offerTwoTakerGetsFunded = Amount.from_json({ - value: book._offers[1].taker_gets_funded, - currency: 'USD', - issuer: addresses.ISSUER - }); - - const offerTwoTakerGetsFundedExpected = Amount.from_json({ - value: '24.01284027983332', - currency: 'USD', - issuer: addresses.ISSUER - }); - - const offerTwoTakerPaysFunded = Amount.from_json(book._offers[1].taker_pays_funded); - - const offerTwoTakerPaysFundedExpected = Amount.from_json('1661400177'); - - assert.strictEqual(offerTwoTakerGetsFunded.equals(offerTwoTakerGetsFundedExpected), true); - assert.strictEqual(offerTwoTakerPaysFunded.equals(offerTwoTakerPaysFundedExpected), true); - assert.strictEqual(book._offers[1].is_fully_funded, false); - - const offerFiveTakerGetsFunded = Amount.from_json({ - value: book._offers[4].taker_gets_funded, - currency: 'USD', - issuer: addresses.ISSUER - }); - - const offerFiveTakerGetsFundedExpected = Amount.from_json({ - value: '153.2780562999202', - currency: 'USD', - issuer: addresses.ISSUER - }); - - const offerFiveTakerPaysFunded = Amount.from_json(book._offers[4].taker_pays_funded); - - const offerFiveTakerPaysFundedExpected = Amount.from_json('10684615137'); - - assert.strictEqual(offerFiveTakerGetsFunded.equals(offerFiveTakerGetsFundedExpected), true); - assert.strictEqual(offerFiveTakerPaysFunded.equals(offerFiveTakerPaysFundedExpected), true); - assert.strictEqual(book._offers[4].is_fully_funded, false); - }); - - it('Set offers - multiple calls', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - - const offers = fixtures.bookOffersResponse().offers; - - book.setOffers(offers); - book.setOffers(offers); - - assert.strictEqual(book._offers.length, 5); - - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '275.85192574'); - assert.strictEqual(book.getOwnerOfferTotal(addresses.OTHER_ACCOUNT).to_text(), '24.060765960393'); - assert.strictEqual(book.getOwnerOfferTotal(addresses.THIRD_ACCOUNT).to_text(), '712.60995'); - assert.strictEqual(book.getOwnerOfferTotal(addresses.FOURTH_ACCOUNT).to_text(), '288.08'); - - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 2); - assert.strictEqual(book.getOwnerOfferCount(addresses.OTHER_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferCount(addresses.THIRD_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferCount(addresses.FOURTH_ACCOUNT), 1); - - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT).to_text(), '2006.015671538605'); - assert.strictEqual(book.getOwnerFunds(addresses.OTHER_ACCOUNT).to_text(), '24.01284027983332'); - assert.strictEqual(book.getOwnerFunds(addresses.THIRD_ACCOUNT).to_text(), '9053.294314019701'); - assert.strictEqual(book.getOwnerFunds(addresses.FOURTH_ACCOUNT).to_text(), '7229.594289344439'); - }); - - it('Set offers - incorrect taker pays funded', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - - const offers = fixtures.DECIMAL_TAKER_PAYS_FUNDED_OFFERS; - - book.setOffers(offers); - - assert.strictEqual(book._offers.length, 1); - - assert.strictEqual(book._offers[0].taker_gets_funded, '9261.514125778347'); - assert.strictEqual(book._offers[0].taker_pays_funded, '1704050437125'); - }); - - it('Notify - created node', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - book._subscribed = book._synced = true; - - const message = fixtures.transactionWithCreatedOffer(); - - book.notify(message); - - assert.strictEqual(book._offers.length, 1); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '1.9951'); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 1); - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT).to_text(), '2006.015671538605'); - }); - - it('Notify - created nodes - correct sorting', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._issuerTransferRate = new IOUValue(1002000000); - book._subscribed = book._synced = true; - - const offer = fixtures.transactionWithCreatedOffer(); - - const lowQualityOffer = fixtures.transactionWithCreatedOffer({ - account: addresses.OTHER_ACCOUNT, - amount: '1.5' - }); - - const highQualityOffer = fixtures.transactionWithCreatedOffer({ - account: addresses.THIRD_ACCOUNT, - amount: '3.83' - }); - - book.notify(offer); - book.notify(lowQualityOffer); - book.notify(highQualityOffer); - - assert.strictEqual(book._offers.length, 3); - assert.strictEqual(book._offers[0].Account, addresses.THIRD_ACCOUNT); - assert.strictEqual(book._offers[1].Account, addresses.ACCOUNT); - assert.strictEqual(book._offers[2].Account, addresses.OTHER_ACCOUNT); - }); - - it('Notify - created nodes - events', function(done) { - let numTransactionEvents = 0; - let numModelEvents = 0; - let numOfferAddedEvents = 0; - - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.on('transaction', function() { - numTransactionEvents += 1; - }); - - book.on('model', function() { - numModelEvents += 1; - }); - - book.on('offer_added', function() { - numOfferAddedEvents += 1; - }); - - book._issuerTransferRate = new IOUValue(1002000000); - book._subscribed = book._synced = true; - - const offer = fixtures.transactionWithCreatedOffer(); - const offer2 = fixtures.transactionWithCreatedOffer(); - const offer3 = fixtures.transactionWithCreatedOffer(); - - remote.emit('ledger_closed', {txn_count: 3}); - - book.notify(offer); - remote.emit('transaction', offer); - book.notify(offer2); - remote.emit('transaction', offer2); - book.notify(offer3); - remote.emit('transaction', offer3); - - - assert.strictEqual(numTransactionEvents, 3); - assert.strictEqual(numOfferAddedEvents, 3); - setTimeout(function() { - assert.strictEqual(numModelEvents, 1); - done(); - }, 300); - }); - - it('Notify - deleted node', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithDeletedOffer(); - - book.notify(message); - - assert.strictEqual(book._offers.length, 2); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '4.9656112525'); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 1); - }); - - it('Notify - deleted node - last offer', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers().slice(0, 1)); - - const message = fixtures.transactionWithDeletedOffer(); - - book.notify(message); - - assert.strictEqual(book._offers.length, 0); - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT), undefined); - }); - - it('Notify - deleted node - events', function(done) { - let numTransactionEvents = 0; - let numModelEvents = 0; - let numTradeEvents = 0; - let numOfferRemovedEvents = 0; - - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.on('transaction', function() { - numTransactionEvents += 1; - }); - - book.on('model', function() { - numModelEvents += 1; - }); - - book.on('trade', function() { - numTradeEvents += 1; - }); - - book.on('offer_removed', function() { - numOfferRemovedEvents += 1; - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithDeletedOffer(); - - remote.emit('ledger_closed', {txn_count: 1}); - - book.notify(message); - remote.emit('transaction', message); - - assert.strictEqual(numTransactionEvents, 1); - assert.strictEqual(numTradeEvents, 1); - assert.strictEqual(numOfferRemovedEvents, 1); - setTimeout(function() { - assert.strictEqual(numModelEvents, 1); - done(); - }, 300); - }); - - it('Notify - deleted node - trade', function(done) { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.on('trade', function(tradePays, tradeGets) { - const expectedTradePays = Amount.from_json(fixtures.TAKER_PAYS); - const expectedTradeGets = Amount.from_json({ - value: fixtures.TAKER_GETS, - currency: 'USD', - issuer: addresses.ISSUER - }); - - assert(tradePays.equals(expectedTradePays)); - assert(tradeGets.equals(expectedTradeGets)); - - done(); - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithDeletedOffer(); - - book.notify(message); - }); - - it('Notify - deleted node - offer cancel', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithDeletedOffer({ - transaction_type: 'OfferCancel' - }); - - book.notify(message); - - assert.strictEqual(book._offers.length, 2); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '4.9656112525'); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 1); - }); - - it('Notify - deleted node - offer cancel - last offer', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers().slice(0, 1)); - - const message = fixtures.transactionWithDeletedOffer({ - transaction_type: 'OfferCancel' - }); - - book.notify(message); - - assert.strictEqual(book._offers.length, 0); - assert.strictEqual(book.getOwnerFunds(addresses.ACCOUNT), undefined); - }); - - it('Notify - modified node', function() { - const remote = createRemote(); - - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithModifiedOffer(); - - book.notify(message); - - assert.strictEqual(book._offers.length, 3); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '23.8114145625'); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 2); - - assert.strictEqual(book._offers[0].is_fully_funded, true); - assert.strictEqual(book._offers[0].taker_gets_funded, fixtures.TAKER_GETS_FINAL); - assert.strictEqual(book._offers[0].taker_pays_funded, fixtures.TAKER_PAYS_FINAL); - - assert.strictEqual(book._offers[1].is_fully_funded, true); - assert.strictEqual(book._offers[1].taker_gets_funded, '4.9656112525'); - assert.strictEqual(book._offers[1].taker_pays_funded, '972251352'); - }); - - it('Notify - modified node - events', function(done) { - let numTransactionEvents = 0; - let numModelEvents = 0; - let numTradeEvents = 0; - let numOfferChangedEvents = 0; - - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.on('transaction', function() { - numTransactionEvents += 1; - }); - - book.on('model', function() { - numModelEvents += 1; - }); - - book.on('trade', function() { - numTradeEvents += 1; - }); - - book.on('offer_changed', function() { - numOfferChangedEvents += 1; - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithModifiedOffer(); - - remote.emit('ledger_closed', {txn_count: 1}); - - book.notify(message); - remote.emit('transaction', message); - - assert.strictEqual(numTransactionEvents, 1); - assert.strictEqual(numTradeEvents, 1); - assert.strictEqual(numOfferChangedEvents, 1); - setTimeout(function() { - assert.strictEqual(numModelEvents, 1); - done(); - }, 300); - }); - - it('Notify - modified node - trade', function(done) { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.on('trade', function(tradePays, tradeGets) { - const expectedTradePays = Amount.from_json('800000000'); - const expectedTradeGets = Amount.from_json({ - value: 1, - currency: 'USD', - issuer: addresses.ISSUER - }); - - assert(tradePays.equals(expectedTradePays)); - assert(tradeGets.equals(expectedTradeGets)); - - done(); - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithModifiedOffer(); - - book.notify(message); - }); - - it('Notify - modified nodes - trade', function(done) { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.on('trade', function(tradePays, tradeGets) { - const expectedTradePays = Amount.from_json('870000000'); - const expectedTradeGets = Amount.from_json({ - value: 2, - currency: 'USD', - issuer: addresses.ISSUER - }); - - assert(tradePays.equals(expectedTradePays)); - assert(tradeGets.equals(expectedTradeGets)); - - done(); - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithModifiedOffers(); - - book.notify(message); - }); - - it('Notify - no nodes', function() { - let numTransactionEvents = 0; - let numModelEvents = 0; - let numTradeEvents = 0; - let numOfferChangedEvents = 0; - - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book.on('transaction', function() { - numTransactionEvents += 1; - }); - - book.on('model', function() { - numModelEvents += 1; - }); - - book.on('trade', function() { - numTradeEvents += 1; - }); - - book.on('offer_changed', function() { - numOfferChangedEvents += 1; - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - const message = fixtures.transactionWithNoNodes(); - - book.notify(message); - - assert.strictEqual(numTransactionEvents, 0); - assert.strictEqual(numModelEvents, 0); - assert.strictEqual(numTradeEvents, 0); - assert.strictEqual(numOfferChangedEvents, 0); - }); - - it('Notify - in disconnected state', function(done) { - this.timeout(100); - - const remote = createRemote(); - const transaction = fixtures.transactionWithDeletedOfferR(); - const offers = { - offers: fixtures.REQUEST_OFFERS_NATIVE - }; - - function handleRequest(request) { - switch (request.message.command) { - case 'book_offers': - request.emit('success', offers); - break; - case 'subscribe': - request.emit('success', {}); - break; - } - } - - remote.request = function(request) { - setImmediate(function() { - handleRequest(request); - }); - }; - - const book = remote.createOrderBook({ - currency_gets: 'XRP', - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book.on('model', function() { - remote.emit('disconnect'); - - // Check cache reset - assert.strictEqual(book._synced, false); - assert.deepEqual(book._ownerFunds, {}); - assert.deepEqual(book._ownerOffersTotal, {}); - - setTimeout(function() { - // Notification should be dropped if not synced - book.notify(transaction); - done(); - }, 10); - }); - }); - it('Delete offer - offer cancel - funded after delete', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers({ - account_funds: '20' - })); - - book.deleteOffer(fixtures.transactionWithDeletedOffer({ - transaction_type: 'OfferCancel' - }).mmeta.getNodes()[0], true); - - assert.strictEqual(book._offers.length, 2); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferCount(addresses.OTHER_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '4.9656112525'); - - assert.strictEqual(book._offers[0].is_fully_funded, true); - }); - - it('Delete offer - offer cancel - not fully funded after delete', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers({ - account_funds: '4.5' - })); - - book.deleteOffer(fixtures.transactionWithDeletedOffer({ - transaction_type: 'OfferCancel' - }).mmeta.getNodes()[0], true); - - assert.strictEqual(book._offers.length, 2); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferCount(addresses.OTHER_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '4.9656112525'); - - assert.strictEqual(book._offers[0].is_fully_funded, false); - assert.strictEqual(book._offers[0].taker_gets_funded, '4.5'); - assert.strictEqual(book._offers[0].taker_pays_funded, '881086106'); - }); - - it('Insert offer - best quality', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.QUALITY_OFFERS); - - book.insertOffer(fixtures.transactionWithCreatedOffer({ - amount: '51.04587961502088' - }).mmeta.getNodes()[0]); - - assert.strictEqual(book._offers.length, 2); - - assert.strictEqual(book._offers[0].taker_gets_funded, '51.04587961502088'); - assert.strictEqual(book._offers[0].taker_pays_funded, fixtures.TAKER_PAYS); - assert.strictEqual(book._offers[0].quality, '75977580.74206542'); - }); - - it('Insert offer - best quality - insufficient funds for all offers', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers()); - - book.insertOffer(fixtures.transactionWithCreatedOffer({ - amount: '298' - }).mmeta.getNodes()[0]); - - assert.strictEqual(book._offers.length, 4); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 3); - assert.strictEqual(book.getOwnerOfferCount(addresses.OTHER_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '322.8114145625'); - - assert.strictEqual(book._offers[0].is_fully_funded, true); - assert.strictEqual(book._offers[0].taker_gets_funded, '298'); - assert.strictEqual(book._offers[0].taker_pays_funded, fixtures.TAKER_PAYS); - - assert.strictEqual(book._offers[1].is_fully_funded, true); - assert.strictEqual(book._offers[1].taker_gets_funded, fixtures.TAKER_GETS); - assert.strictEqual(book._offers[1].taker_pays_funded, fixtures.TAKER_PAYS); - - assert.strictEqual(book._offers[2].is_fully_funded, false); - assert.strictEqual(book._offers[2].taker_gets_funded, '0.5185677538508'); - assert.strictEqual(book._offers[2].taker_pays_funded, '101533965'); - }); - - it('Insert offer - worst quality - insufficient funds for all orders', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers({ - account_funds: '25' - })); - - book.insertOffer(fixtures.transactionWithCreatedOffer({ - amount: '5' - }).mmeta.getNodes()[0]); - - assert.strictEqual(book._offers.length, 4); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 3); - assert.strictEqual(book.getOwnerOfferCount(addresses.OTHER_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '29.8114145625'); - - assert.strictEqual(book._offers[0].is_fully_funded, true); - assert.strictEqual(book._offers[0].taker_gets_funded, fixtures.TAKER_GETS); - assert.strictEqual(book._offers[0].taker_pays_funded, fixtures.TAKER_PAYS); - - assert.strictEqual(book._offers[1].is_fully_funded, true); - assert.strictEqual(book._offers[1].taker_gets_funded, '4.9656112525'); - assert.strictEqual(book._offers[1].taker_pays_funded, '972251352'); - - assert.strictEqual(book._offers[3].is_fully_funded, false); - assert.strictEqual(book._offers[3].taker_gets_funded, '0.1885854375'); - assert.strictEqual(book._offers[3].taker_pays_funded, '146279781'); - }); - - it('Insert offer - middle quality - insufficient funds for all offers', function() { - const remote = createRemote(); - const book = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: addresses.ISSUER, - currency_pays: 'XRP' - }); - - book._subscribed = true; - book._issuerTransferRate = new IOUValue(1000000000); - - book.setOffers(fixtures.fiatOffers({ - account_funds: '30' - })); - - book.insertOffer(fixtures.transactionWithCreatedOffer({ - amount: '19.84080331' - }).mmeta.getNodes()[0]); - - assert.strictEqual(book._offers.length, 4); - assert.strictEqual(book.getOwnerOfferCount(addresses.ACCOUNT), 3); - assert.strictEqual(book.getOwnerOfferCount(addresses.OTHER_ACCOUNT), 1); - assert.strictEqual(book.getOwnerOfferTotal(addresses.ACCOUNT).to_text(), '44.6522178725'); - - assert.strictEqual(book._offers[0].is_fully_funded, true); - assert.strictEqual(book._offers[0].taker_gets_funded, fixtures.TAKER_GETS); - assert.strictEqual(book._offers[0].taker_pays_funded, fixtures.TAKER_PAYS); - - assert.strictEqual(book._offers[1].is_fully_funded, false); - assert.strictEqual(book._offers[1].taker_gets_funded, '10.15419669'); - assert.strictEqual(book._offers[1].taker_pays_funded, '1984871849'); - - assert.strictEqual(book._offers[2].is_fully_funded, false); - assert.strictEqual(book._offers[2].taker_gets_funded, '0'); - assert.strictEqual(book._offers[2].taker_pays_funded, '0'); - }); - - it('Request offers - native currency', function(done) { - const remote = createRemote(); - - const offers = { - offers: fixtures.REQUEST_OFFERS_NATIVE - }; - - const expected = [ - { - Account: addresses.ACCOUNT, - BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711A3A4254F5000', - BookNode: '0000000000000000', - Flags: 131072, - LedgerEntryType: 'Offer', - OwnerNode: '0000000000000000', - Sequence: 195, - TakerGets: '1000', - TakerPays: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '56.06639660617357' - }, - index: 'B6BC3B0F87976370EE11F5575593FE63AA5DC1D602830DC96F04B2D597F044BF', - owner_funds: '600', - is_fully_funded: false, - taker_gets_funded: '600', - taker_pays_funded: '33.6398379637041', - qualityHex: '5711A3A4254F5000', - quality: '.0560663966061735' - }, - { - Account: addresses.OTHER_ACCOUNT, - BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414', - BookNode: '0000000000000000', - Expiration: 461498565, - Flags: 131072, - LedgerEntryType: 'Offer', - OwnerNode: '0000000000000144', - Sequence: 29354, - TakerGets: '2000', - TakerPays: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '99.72233516476456' - }, - index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86', - owner_funds: '4000', - is_fully_funded: true, - taker_gets_funded: '2000', - taker_pays_funded: '99.72233516476456', - qualityHex: '5711B6D8C62EF414', - quality: '0.049861167582382' - }, - { - Account: addresses.THIRD_ACCOUNT, - BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414', - BookNode: '0000000000000000', - Expiration: 461498565, - Flags: 131072, - LedgerEntryType: 'Offer', - OwnerNode: '0000000000000144', - Sequence: 29356, - TakerGets: '2000', - TakerPays: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '99.72233516476456' - }, - index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86', - owner_funds: '3900', - is_fully_funded: true, - taker_gets_funded: '2000', - taker_pays_funded: '99.72233516476456', - qualityHex: '5711B6D8C62EF414', - quality: '0.049861167582382' - }, - { - Account: addresses.THIRD_ACCOUNT, - BookDirectory: '6EAB7C172DEFA430DBFAD120FDC373B5F5AF8B191649EC985711B6D8C62EF414', - BookNode: '0000000000000000', - Expiration: 461498565, - Flags: 131078, - LedgerEntryType: 'Offer', - OwnerNode: '0000000000000144', - Sequence: 29354, - TakerGets: '2000', - TakerPays: { - currency: 'USD', - issuer: addresses.ISSUER, - value: '99.72233516476456' - }, - index: 'A437D85DF80D250F79308F2B613CF5391C7CF8EE9099BC4E553942651CD9FA86', - is_fully_funded: false, - taker_gets_funded: '1900', - taker_pays_funded: '94.7362184065258', - owner_funds: '3900', - qualityHex: '5711B6D8C62EF414', - quality: '0.049861167582382' - } - ]; - - remote.request = function(request) { - switch (request.message.command) { - case 'book_offers': - assert.deepEqual(request.message, { - command: 'book_offers', - id: undefined, - taker_gets: { - currency: 'XRP' - }, - taker_pays: { - currency: 'USD', - issuer: addresses.ISSUER - }, - taker: 'rrrrrrrrrrrrrrrrrrrrBZbvji', - ledger_index: 'validated' - }); - - setImmediate(function() { - request.emit('success', offers); - }); - break; - } - }; - - const book = remote.createOrderBook({ - currency_gets: 'XRP', - currency_pays: 'USD', - issuer_pays: addresses.ISSUER - }); - - book.on('model', function(model) { - assert.deepEqual(model, expected); - assert.strictEqual(book._synced, true); - done(); - }); - }); -}); diff --git a/test/rangeset-test.js b/test/rangeset-test.js index 835e8d6d..bc1b838f 100644 --- a/test/rangeset-test.js +++ b/test/rangeset-test.js @@ -1,6 +1,6 @@ 'use strict'; const assert = require('assert'); -const RangeSet = require('ripple-lib')._test.RangeSet; +const RangeSet = require('ripple-api').RippleAPI._PRIVATE.RangeSet; describe('RangeSet', function() { it('addRange()/addValue()', function() { diff --git a/test/remote-test.js b/test/remote-test.js deleted file mode 100644 index f702fd11..00000000 --- a/test/remote-test.js +++ /dev/null @@ -1,2222 +0,0 @@ -/* eslint-disable no-new, max-len, no-comma-dangle, indent, max-nested-callbacks */ - -'use strict'; - -const assert = require('assert-diff'); -const lodash = require('lodash'); -const Remote = require('ripple-lib').Remote; -const Server = require('ripple-lib').Server; -const Transaction = require('ripple-lib').Transaction; -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; -let callback; - -const ADDRESS = 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS'; -const LEDGER_INDEX = 9592219; -const LEDGER_HASH = - 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'; -const PAGING_MARKER = - '29F992CC252056BF690107D1E8F2D9FBAFF29FF107B62B1D1F4E4E11ADF2CC73'; -const TRANSACTION_HASH = - '14576FFD5D59FFA73CAA90547BE4DE09926AAB59E981306C32CCE04408CBF8EA'; -const TX_JSON = { - Flags: 0, - TransactionType: 'Payment', - Account: ADDRESS, - Destination: ACCOUNT_ONE, - Amount: { - value: '1', - currency: 'USD', - issuer: ADDRESS - } -}; - -function makeServer(url) { - const server = new Server(new process.EventEmitter(), url); - server._connected = true; - return server; -} - -describe('Remote', function() { - const initialLogEngine = Log.getEngine(); - - beforeEach(function() { - options = { - trusted: true, - servers: ['wss://s1.ripple.com:443'] - }; - remote = new Remote(options); - }); - - afterEach(function() { - Log.setEngine(initialLogEngine); - }); - - it('Server initialization -- url object', function() { - remote = new Remote({ - servers: [{host: 's-west.ripple.com', port: 443, secure: true}] - }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); - }); - - it('Server initialization -- url object -- no secure property', function() { - remote = new Remote({ - servers: [{host: 's-west.ripple.com', port: 443}] - }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); - }); - - it('Server initialization -- url object -- secure: false', function() { - remote = new Remote({ - servers: [{host: 's-west.ripple.com', port: 443, secure: false}] - }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443'); - }); - - it('Server initialization -- url object -- string port', function() { - remote = new Remote({ - servers: [{host: 's-west.ripple.com', port: '443', secure: true}] - }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); - }); - - it('Server initialization -- url object -- invalid host', function() { - assert.throws( - function() { - new Remote({ - servers: [{host: '+', port: 443, secure: true}] - }); - }, Error); - }); - - it('Server initialization -- url object -- invalid port', function() { - assert.throws( - function() { - new Remote({ - servers: [{host: 's-west.ripple.com', port: 'null', secure: true}] - }); - }, TypeError); - }); - - it('Server initialization -- url object -- port out of range', function() { - assert.throws( - function() { - new Remote({ - servers: [{host: 's-west.ripple.com', port: 65537, secure: true}] - }); - }, Error); - }); - - it('Server initialization -- url string', function() { - remote = new Remote({ - servers: ['wss://s-west.ripple.com:443'] - }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'wss://s-west.ripple.com:443'); - }); - - it('Server initialization -- url string -- ws://', function() { - remote = new Remote({ - servers: ['ws://s-west.ripple.com:443'] - }); - assert(Array.isArray(remote._servers)); - assert(remote._servers[0] instanceof Server); - assert.strictEqual(remote._servers[0]._url, 'ws://s-west.ripple.com:443'); - }); - - it('Server initialization -- url string -- invalid host', function() { - assert.throws( - function() { - new Remote({ - servers: ['ws://+:443'] - }); - }, Error - ); - }); - - /* - "url" module used in server parses such urls with error, it return - null for port, so in this case default port will be used - - it('Server initialization -- url string -- invalid port', function() { - assert.throws( - function() { - new Remote({ - servers: ['ws://s-west.ripple.com:invalid'] - }); - }, Error - ); - }); - */ - - it('Server initialization -- url string -- port out of range', function() { - assert.throws( - function() { - new Remote({ - servers: ['ws://s-west.ripple.com:65537'] - }); - }, Error - ); - }); - - it('Server initialization -- set max_fee', function() { - remote = new Remote({max_fee: 10}); - assert.strictEqual(remote.max_fee, 10); - remote = new Remote({max_fee: 1234567890}); - assert.strictEqual(remote.max_fee, 1234567890); - }); - - it('Server initialization -- set max_fee -- invalid', function() { - assert.throws(function() { - new Remote({max_fee: '1234567890'}); - }); - }); - - it('Server initialization -- set trusted', function() { - remote = new Remote({trusted: true}); - assert.strictEqual(remote.trusted, true); - }); - it('Server initialization -- set trusted -- invalid', function() { - assert.throws(function() { - new Remote({trusted: '1234567890'}); - }); - }); - - it('Server initialization -- set trace', function() { - remote = new Remote({trace: true}); - assert.strictEqual(remote.trace, true); - }); - it('Server initialization -- set trace -- invalid', function() { - assert.throws(function() { - new Remote({trace: '1234567890'}); - }); - }); - - it('Server initialization -- set allow_partial_history', function() { - remote = new Remote({allow_partial_history: true}); - assert.strictEqual(remote.allow_partial_history, true); - }); - it('Server initialization -- set allow_partial_history -- invalid', - function() { - assert.throws(function() { - new Remote({allow_partial_history: '1234567890'}); - }); - }); - - it('Server initialization -- set max_attempts', function() { - remote = new Remote({max_attempts: 10}); - assert.strictEqual(remote.max_attempts, 10); - }); - it('Server initialization -- set max_attempts -- invalid', function() { - assert.throws(function() { - new Remote({max_attempts: '1234567890'}); - }); - }); - - it('Server initialization -- set fee_cushion', function() { - remote = new Remote({fee_cushion: 1.3}); - assert.strictEqual(remote.fee_cushion, 1.3); - }); - it('Server initialization -- set fee_cushion -- invalid', function() { - assert.throws(function() { - new Remote({fee_cushion: '1234567890'}); - }); - }); - - it('Server initialization -- set local_signing', function() { - remote = new Remote({local_signing: false}); - assert.strictEqual(remote.local_signing, false); - }); - it('Server initialization -- set local_signing -- invalid', function() { - assert.throws(function() { - remote = new Remote({local_signing: '1234567890'}); - }); - }); - it('Server initialization -- set local_fee', function() { - remote = new Remote({local_fee: false}); - assert.strictEqual(remote.local_fee, true); - remote = new Remote({local_signing: false, local_fee: false}); - assert.strictEqual(remote.local_fee, false); - }); - it('Server initialization -- set local_fee -- invalid', function() { - assert.throws(function() { - new Remote({ - local_signing: false, - local_fee: '1234567890' - }); - }); - }); - it('Server initialization -- set local_sequence', function() { - remote = new Remote({local_sequence: false}); - assert.strictEqual(remote.local_sequence, true); - remote = new Remote({local_signing: false, local_sequence: false}); - assert.strictEqual(remote.local_sequence, false); - }); - it('Server initialization -- set local_sequence -- invalid', function() { - assert.throws(function() { - new Remote({ - local_signing: false, - local_sequence: '1234567890' - }); - }); - }); - - it('Server initialization -- set canonical_signing', function() { - assert.strictEqual(new Remote({canonical_signing: false}) - .canonical_signing, false); - }); - it('Server initialization -- set canonical_signing -- invalid', function() { - assert.throws(function() { - new Remote({canonical_signing: '1234567890'}); - }); - }); - - it('Server initialization -- set submission_timeout', function() { - assert.strictEqual(new Remote({submission_timeout: 10}) - .submission_timeout, 10); - }); - it('Server initialization -- set submission_timeout -- invalid', function() { - assert.throws(function() { - new Remote({submission_timeout: '1234567890'}); - }); - }); - - it('Server initialization -- set last_ledger_offset', function() { - assert.strictEqual(new Remote({last_ledger_offset: 10}) - .last_ledger_offset, 10); - }); - it('Server initialization -- set last_ledger_offset -- invalid', function() { - assert.throws(function() { - new Remote({last_ledger_offset: '1234567890'}); - }); - }); - - it('Server initialization -- set servers', function() { - assert.deepEqual(new Remote({servers: []}).servers, []); - }); - it('Server initialization -- set servers -- invalid', function() { - assert.throws(function() { - new Remote({servers: '1234567890'}); - }); - }); - - it('Automatic transactions subscription', function(done) { - let i = 0; - - remote.request = function(request) { - switch (++i) { - case 1: - assert.strictEqual(request.message.command, 'subscribe'); - break; - case 2: - assert.strictEqual(request.message.command, 'unsubscribe'); - done(); - break; - } - assert.deepEqual(request.message.streams, ['transactions']); - }; - - remote.on('transaction', function() {}); - remote.removeAllListeners('transaction'); - }); - - it('Check is valid message', function() { - assert(Remote.isValidMessage({type: 'response'})); - assert(!Remote.isValidMessage({})); - assert(!Remote.isValidMessage('')); - }); - it('Check is valid ledger data', function() { - assert(Remote.isValidLedgerData({ - fee_base: 10, - fee_ref: 10, - ledger_hash: LEDGER_HASH, - ledger_index: 1, - ledger_time: 1, - reserve_base: 10, - reserve_inc: 10 - })); - assert(!Remote.isValidLedgerData({ - fee_base: 10, - fee_ref: 10, - ledger_hash: LEDGER_HASH, - ledger_index: 1, - ledger_time: 1, - reserve_base: 10, - reserve_inc: '10' - })); - assert(!Remote.isValidLedgerData({ - fee_base: 10, - fee_ref: 10, - ledger_hash: LEDGER_HASH, - ledger_index: 1, - reserve_base: 10, - reserve_inc: 10 - })); - }); - it('Check is valid load status', function() { - assert(Remote.isValidLoadStatus({ - load_base: 10, - load_factor: 10 - })); - assert(!Remote.isValidLoadStatus({ - load_base: 10, - load_factor: '10' - })); - assert(!Remote.isValidLoadStatus({ - load_base: 10 - })); - }); - it('Check is validated', function() { - assert(Remote.isValidated({validated: true})); - assert(!Remote.isValidated({validated: false})); - assert(!Remote.isValidated({validated: 'true'})); - assert(!Remote.isValidated({})); - assert(!Remote.isValidated(null)); - }); - - it('Set state', function() { - let i = 0; - remote.on('state', function(state) { - switch (++i) { - case 1: - assert.strictEqual(state, 'online'); - break; - case 2: - assert.strictEqual(state, 'offline'); - break; - } - assert.strictEqual(state, remote.state); - }); - remote._setState('online'); - remote._setState('online'); - remote._setState('offline'); - remote._setState('offline'); - assert.strictEqual(i, 2); - }); - - it('Set trace', function() { - remote.setTrace(true); - assert.strictEqual(remote.trace, true); - remote.setTrace(); - assert.strictEqual(remote.trace, true); - remote.setTrace(false); - assert.strictEqual(remote.trace, false); - }); - - it('Set server fatal', function() { - remote.setServerFatal(); - assert.strictEqual(remote._server_fatal, true); - }); - - it('Add server', function() { - const server = remote.addServer('wss://s1.ripple.com:443'); - assert(server instanceof Server); - - let i = 0; - remote.once('connect', function() { - assert.strictEqual(remote._connection_count, 1); - ++i; - }); - remote.once('disconnect', function() { - assert.strictEqual(remote._connection_count, 0); - ++i; - }); - - server.emit('connect'); - server.emit('disconnect'); - - assert.strictEqual(i, 2, 'Remote did not receive all server events'); - }); - it('Add server -- primary server', function() { - const server = remote.addServer({ - host: 's1.ripple.com', - port: 443, - secure: true, - primary: true - }); - - assert(server instanceof Server); - assert.strictEqual(remote._servers.length, 2); - assert.strictEqual(remote._servers[1], server); - - let i = 0; - remote.once('connect', function() { - assert.strictEqual(remote._connection_count, 1); - assert.strictEqual(remote._primary_server, server); - remote.setPrimaryServer(remote._servers[0]); - assert.strictEqual(server._primary, false); - assert.strictEqual(remote._primary_server, remote._servers[0]); - ++i; - }); - - server.emit('connect'); - - assert.strictEqual(i, 1, 'Remote did not receive all server events'); - }); - - it('Connect', function() { - remote.addServer('wss://s1.ripple.com:443'); - - let i = 0; - remote._servers.forEach(function(s) { - s.connect = function() { - ++i; - }; - }); - - remote.connect(); - - assert.strictEqual(remote._should_connect, true); - assert.strictEqual(i, 2, 'Did not attempt connect to all servers'); - }); - - it('Connect -- with callback', function(done) { - remote.addServer('wss://s1.ripple.com:443'); - - let i = 0; - remote._servers.forEach(function(s) { - s.connect = function() { - ++i; - }; - }); - - remote.connect(done); - - assert.strictEqual(remote._should_connect, true); - assert.strictEqual(i, 2, 'Did not attempt connect to all servers'); - - remote._servers[0].emit('connect'); - }); - - it('Connect -- no servers', function() { - remote._servers = []; - assert.throws(function() { - remote.connect(); - }); - }); - - it('Disconnect', function() { - remote.addServer('wss://s1.ripple.com:443'); - - let i = 0; - remote._servers.forEach(function(s) { - s.disconnect = function() { - ++i; - }; - s.emit('connect'); - }); - - remote.disconnect(); - - assert.strictEqual(remote._should_connect, false); - assert.strictEqual(i, 2, 'Did not attempt disconnect to all servers'); - }); - it('Disconnect -- with callback', function(done) { - remote.addServer('wss://s1.ripple.com:443'); - - let i = 0; - remote._servers.forEach(function(s) { - s.disconnect = function() { - ++i; - }; - s.emit('connect'); - }); - - remote.disconnect(done); - - assert.strictEqual(remote._should_connect, false); - assert.strictEqual(i, 2, 'Did not attempt disconnect to all servers'); - - remote._servers.forEach(function(s) { - s.emit('disconnect'); - }); - }); - it('Disconnect -- unconnected', function(done) { - remote.addServer('wss://s1.ripple.com:443'); - - let i = 0; - remote._servers.forEach(function(s) { - s.disconnect = function() { - ++i; - }; - }); - - remote.disconnect(done); - - assert.strictEqual(i, 0, 'Should not attempt disconnect'); - }); - it('Disconnect -- no servers', function() { - remote._servers = []; - assert.throws(function() { - remote.disconnect(); - }); - }); - - it('Handle server message -- ledger', function() { - const message = { - type: 'ledgerClosed', - fee_base: 10, - fee_ref: 10, - ledger_hash: - 'F824560DD788E5E4B65F5843A6616872873EAB74AA759C73A992355FFDFC4237', - ledger_index: 11368614, - ledger_time: 475696280, - reserve_base: 20000000, - reserve_inc: 5000000, - txn_count: 9, - validated_ledgers: '32570-11368614' - }; - - remote.once('ledger_closed', function(l) { - assert.deepEqual(l, message); - assert.strictEqual(remote.getLedgerHash(), message.ledger_hash); - }); - remote._servers[0].emit('connect'); - remote._servers[0].emit('message', message); - }); - it('Handle server message -- ledger', function(done) { - const message = { - type: 'ledgerClosed', - fee_base: 10, - fee_ref: 10, - ledger_hash: - 'F824560DD788E5E4B65F5843A6616872873EAB74AA759C73A992355FFDFC4237', - ledger_index: 11368614, - ledger_time: 475696280, - reserve_base: 20000000, - reserve_inc: 5000000, - txn_count: 9, - validated_ledgers: '32570-11368614' - }; - - remote.once('ledger_closed', function(l) { - assert.deepEqual(l, message); - done(); - }); - remote._servers[0].emit('message', message); - - setImmediate(function() { - remote._servers[0].emit('connect'); - }); - }); - it('Handle server message -- server status', function() { - const message = { - type: 'serverStatus', - load_base: 256, - load_factor: 256, - server_status: 'full' - }; - - remote.once('server_status', function(l) { - assert.deepEqual(l, message); - }); - remote._servers[0].emit('message', message); - remote._servers[0].emit('connect'); - }); - it('Handle server message -- validation received', function() { - const message = { - type: 'validationReceived', - ledger_hash: - '96D9E225F10C22D5047B87597939F94024F4180609227D1EB7E9D1CE9A428620', - validation_public_key: - 'n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C', - signature: - '304402207E221CF0679B1A52BC07C4B97C56B93392F8BB53DFB52B821828118A740' + - '9F3E302202669AD632D9CD288B20A0A98DBC50DD3961EC50B95B138A9DCBDC11506' + - 'F63646' - }; - - remote.once('validation_received', function(l) { - assert.deepEqual(l, message); - }); - remote._servers[0].emit('message', message); - remote._servers[0].emit('connect'); - }); - it('Handle server message -- transaction', function() { - const message = require('./fixtures/transaction'); - - remote.once('transaction', function(l) { - assert.deepEqual(l, message); - }); - remote._servers[0].emit('connect'); - remote._servers[0].emit('message', message); - }); - it('Handle server message -- transaction -- duplicate hashes', function() { - const message = require('./fixtures/transaction'); - let i = 0; - - remote.once('transaction', function(l) { - assert.deepEqual(l, message); - ++i; - }); - - remote._servers[0].emit('connect'); - remote._servers[0].emit('message', message); - remote._servers[0].emit('message', message); - remote._servers[0].emit('message', message); - assert.strictEqual(i, 1); - }); - it('Handle server message -- ' - + 'transaction -- with account notification', function() { - const message = require('./fixtures/transaction'); - let i = 0; - const account = remote.addAccount(message.transaction.Account); - - account.once('transaction', function(t) { - assert.deepEqual(t, message); - ++i; - }); - - remote.once('transaction', function(l) { - assert.deepEqual(l, message); - ++i; - }); - - remote._servers[0].emit('connect'); - remote._servers[0].emit('message', message); - assert.strictEqual(i, 2); - }); - it('Handle server message -- ' - + 'transaction proposed -- with account notification', function() { - const message = require('./fixtures/transaction-proposed'); - let i = 0; - const account = remote.addAccount(message.transaction.Account); - - account.once('transaction', function(t) { - assert.deepEqual(t, message); - ++i; - }); - - remote.once('transaction', function(l) { - assert.deepEqual(l, message); - ++i; - }); - - remote._servers[0].emit('connect'); - remote._servers[0].emit('message', message); - assert.strictEqual(i, 2); - }); - it('Handle server message -- transaction -- with orderbook notification', - function() { - const message = require('./fixtures/transaction-offercreate'); - let i = 0; - remote._ledger_current_index = 32570; - const orderbook = remote.createOrderBook({ - currency_gets: 'USD', - issuer_gets: 'rJy64aCJLP3vf8o3WPKn4iQKtfpjh6voAR', - currency_pays: 'XRP' - }); - - orderbook._subscribed = true; - orderbook._synced = true; - - orderbook.once('transaction', function(t) { - assert.deepEqual(t.transaction, message.transaction); - assert.deepEqual(t.meta, message.meta); - ++i; - }); - - remote.once('transaction', function(l) { - assert.deepEqual(l, message); - ++i; - }); - - remote._servers[0].emit('connect'); - remote._servers[0].emit('message', message); - assert.strictEqual(i, 2); - }); - it('Handle server message -- path find', function() { - const message = require('./fixtures/pathfind'); - let i = 0; - - const amount = Amount.from_json({ - currency: 'USD', - issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', - value: '0.001' - }); - const path = new PathFind(remote, - 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - amount - ); - - path.once('update', function(p) { - assert.deepEqual(p, message); - ++i; - }); - remote.once('path_find_all', function(p) { - assert.deepEqual(p, message); - ++i; - }); - - remote._cur_path_find = path; - remote._servers[0].emit('connect'); - remote._servers[0].emit('message', message); - - assert.strictEqual(i, 2); - }); - it('Handle server message -- invalid message', function() { - // Silence error log - Log.setEngine(Log.engines.none); - - require('./fixtures/pathfind'); - let i = 0; - - remote.on('error', function(e) { - assert(/^Unexpected response from remote/.test(e.message)); - ++i; - }); - remote._servers[0].emit('message', '1'); - remote._servers[0].emit('message', {}); - remote._servers[0].emit('message', {type: 'response'}); - remote._servers[0].emit('message', JSON.stringify({type: 'response'})); - - assert.strictEqual(i, 3, 'Failed to receive all invalid message errors'); - }); - - it('Get server', function() { - remote.addServer('wss://sasdf.ripple.com:443'); - - remote._servers.concat(remote).forEach(s => s._connected = true); - - const message = { - type: 'ledgerClosed', - fee_base: 10, - fee_ref: 10, - ledger_hash: - 'F824560DD788E5E4B65F5843A6616872873EAB74AA759C73A992355FFDFC4237', - ledger_index: 1, - ledger_time: 475696280, - reserve_base: 20000000, - reserve_inc: 5000000, - txn_count: 9, - validated_ledgers: '32570-11368614' - }; - - remote._servers[0].emit('message', message); - assert.strictEqual(remote.getServer(), remote._servers[0]); - - message.ledger_index += 1; - - remote._servers[1].emit('message', message); - assert.strictEqual(remote.getServer(), remote._servers[1]); - }); - it('Get server -- no servers', function() { - assert.strictEqual(new Remote().getServer(), null); - }); - it('Get server -- no connected servers', function() { - remote.addServer('wss://sasdf.ripple.com:443'); - assert.strictEqual(remote._servers.length, 2); - assert.strictEqual(remote.getServer(), null); - }); - - it('Parse binary transaction', function() { - const binaryTransaction = require('./fixtures/binary-transaction.json'); - - const parsedSourceTag = Remote.parseBinaryTransaction( - binaryTransaction.PaymentWithSourceTag.binary); - assert.deepEqual(parsedSourceTag, - binaryTransaction.PaymentWithSourceTag.parsed); - - const parsedMemosAndPaths = Remote.parseBinaryTransaction( - binaryTransaction.PaymentWithMemosAndPaths.binary); - assert.deepEqual(parsedMemosAndPaths, - binaryTransaction.PaymentWithMemosAndPaths.parsed); - - const parsedPartialPayment = Remote.parseBinaryTransaction( - binaryTransaction.PartialPayment.binary); - assert.deepEqual(parsedPartialPayment, - binaryTransaction.PartialPayment.parsed); - - const parsedOfferCreate = Remote.parseBinaryTransaction( - binaryTransaction.OfferCreate.binary); - assert.deepEqual(parsedOfferCreate, binaryTransaction.OfferCreate.parsed); - - const parsedPartialPaymentWithXRPDelieveredAmount = - Remote.parseBinaryTransaction( - binaryTransaction.PartialPaymentWithXRPDeliveredAmount.binary); - - assert.deepEqual(parsedPartialPaymentWithXRPDelieveredAmount, - binaryTransaction - .PartialPaymentWithXRPDeliveredAmount - .parsed); - }); - - it('Parse binary account transaction', function() { - const binaryAccountTransaction = - require('./fixtures/binary-account-transaction.json'); - - const parsed = Remote.parseBinaryAccountTransaction( - binaryAccountTransaction.OfferCreate.binary); - assert.deepEqual(parsed, binaryAccountTransaction.OfferCreate.parsed); - - const parsedPartialPayment = Remote.parseBinaryAccountTransaction( - binaryAccountTransaction.PartialPayment.binary); - assert.deepEqual(parsedPartialPayment, - binaryAccountTransaction.PartialPayment.parsed); - - const parsedPayment = Remote.parseBinaryAccountTransaction( - binaryAccountTransaction.Payment.binary); - assert.deepEqual(parsedPayment, binaryAccountTransaction.Payment.parsed); - }); - - it('Parse binary ledger', function() { - const binaryLedgerData = require('./fixtures/binary-ledger-data.json'); - - const parsedAccountRoot = - Remote.parseBinaryLedgerData(binaryLedgerData.AccountRoot.binary); - assert.deepEqual(parsedAccountRoot, binaryLedgerData.AccountRoot.parsed); - - const parsedOffer = - Remote.parseBinaryLedgerData(binaryLedgerData.Offer.binary); - assert.deepEqual(parsedOffer, binaryLedgerData.Offer.parsed); - - const parsedDirectoryNode = - Remote.parseBinaryLedgerData(binaryLedgerData.DirectoryNode.binary); - assert.deepEqual(parsedDirectoryNode, - binaryLedgerData.DirectoryNode.parsed); - - const parsedRippleState = - Remote.parseBinaryLedgerData(binaryLedgerData.RippleState.binary); - assert.deepEqual(parsedRippleState, binaryLedgerData.RippleState.parsed); - }); - - it('Prepare currency', function() { - assert.deepEqual(Remote.prepareCurrencies({ - issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - currency: 'USD', - value: 1 - }), { - issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - currency: 'USD' - }); - }); - - it('Get transaction fee', function() { - remote._connected = true; - remote._servers[0]._connected = true; - assert.strictEqual(remote.feeTx(10).to_json(), '12'); - remote._servers = []; - assert.throws(function() { - remote.feeTx(10).to_json(); - }); - }); - it('Get transaction fee units', function() { - remote._connected = true; - remote._servers[0]._connected = true; - assert.strictEqual(remote.feeTxUnit(), 1.2); - remote._servers = []; - assert.throws(function() { - remote.feeTxUnit(10).to_json(); - }); - }); - it('reserve() before reserve rate known', function() { - remote._connected = true; - remote._servers[0]._connected = true; - // Throws because the server has not had reserve_inc, reserve_base set - assert.throws(function() { - remote.reserve(10).to_json(); - }); - }); - - it('Initiate request', function() { - const request = remote.requestServerInfo(); - - assert.deepEqual(request.message, { - command: 'server_info', - id: undefined - }); - - let i = 0; - remote._connected = true; - remote._servers[0]._connected = true; - remote._servers[0]._request = function() { - ++i; - }; - remote.request(request); - - assert.strictEqual(i, 1, 'Did not initiate request'); - }); - it('Initiate request -- with request name', function() { - const request = remote.request('server_info'); - - assert.deepEqual(request.message, { - command: 'server_info', - id: undefined - }); - - let i = 0; - remote._connected = true; - remote._servers[0]._connected = true; - remote._servers[0]._request = function() { - ++i; - }; - remote.request(request); - - assert.strictEqual(i, 1, 'Did not initiate request'); - }); - it('Initiate request -- with invalid request name', function() { - assert.throws(function() { - remote.request('server_infoz'); - }); - }); - it('Initiate request -- with invalid request', function() { - assert.throws(function() { - remote.request({}); - }); - assert.throws(function() { - remote.request({command: 'server_info', id: 1}); - }); - }); - it('Initiate request -- set non-existent servers', function() { - const request = remote.requestServerInfo(); - request.setServer('wss://s-east.ripple.com:443'); - assert.strictEqual(request.server, null); - assert.throws(function() { - remote._connected = true; - remote.request(request); - }); - }); - - it('Construct ledger request', function() { - const request = remote.requestLedger(); - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined - }); - }); - it('Construct ledger request -- with ledger index', function() { - let request = remote.requestLedger({ledger: 1}); - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_index: 1 - }); - request = remote.requestLedger({ledger_index: 1}); - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_index: 1 - }); - request = remote.requestLedger(1); - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_index: 1 - }); - request = remote.requestLedger(null); - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined - }); - }); - it('Construct ledger request -- with ledger hash', function() { - let request = remote.requestLedger({ledger: LEDGER_HASH}); - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_hash: LEDGER_HASH - }); - - request = remote.requestLedger({ledger_hash: LEDGER_HASH}); - - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_hash: LEDGER_HASH - }); - - request = remote.requestLedger(LEDGER_HASH); - - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_hash: LEDGER_HASH - }); - }); - it('Construct ledger request -- with ledger identifier', function() { - let request = remote.requestLedger({ledger: 'validated'}); - - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_index: 'validated' - }); - - request = remote.requestLedger({ledger: 'current'}); - - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_index: 'current' - }); - - request = remote.requestLedger('validated'); - - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_index: 'validated' - }); - - request = remote.requestLedger({validated: true}); - - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_index: 'validated' - }); - }); - it('Construct ledger request -- with transactions', function() { - const request = remote.requestLedger({ - ledger: 'validated', - transactions: true - }); - assert.deepEqual(request.message, { - command: 'ledger', - id: undefined, - ledger_index: 'validated', - transactions: true - }); - }); - - it('Construct ledger_closed request', function() { - const request = remote.requestLedgerClosed(); - assert.deepEqual(request.message, { - command: 'ledger_closed', - id: undefined - }); - }); - it('Construct ledger_header request', function() { - const request = remote.requestLedgerHeader(); - assert.deepEqual(request.message, { - command: 'ledger_header', - id: undefined - }); - }); - it('Construct ledger_current request', function() { - const request = remote.requestLedgerCurrent(); - assert.deepEqual(request.message, { - command: 'ledger_current', - id: undefined - }); - }); - - it('Construct ledger_data request -- with ledger hash', function() { - const request = remote.requestLedgerData({ - ledger: LEDGER_HASH, - limit: 5 - }); - - assert.deepEqual(request.message, { - command: 'ledger_data', - id: undefined, - binary: true, - ledger_hash: LEDGER_HASH, - limit: 5 - }); - }); - - it('Construct ledger_data request -- with ledger index', function() { - const request = remote.requestLedgerData({ - ledger: LEDGER_INDEX, - limit: 5 - }); - - assert.deepEqual(request.message, { - command: 'ledger_data', - id: undefined, - binary: true, - ledger_index: LEDGER_INDEX, - limit: 5 - }); - }); - - it('Construct ledger_data request -- no binary', function() { - const request = remote.requestLedgerData({ - ledger: LEDGER_HASH, - limit: 5, - binary: false - }); - - assert.deepEqual(request.message, { - command: 'ledger_data', - id: undefined, - binary: false, - ledger_hash: LEDGER_HASH, - limit: 5 - }); - }); - - it('Construct server_info request', function() { - const request = remote.requestServerInfo(); - assert.deepEqual(request.message, { - command: 'server_info', - id: undefined - }); - }); - - it('Construct peers request', function() { - const request = remote.requestPeers(); - assert.deepEqual(request.message, { - command: 'peers', - id: undefined - }); - }); - - it('Construct connection request', function() { - const request = remote.requestConnect('0.0.0.0', '443'); - assert.deepEqual(request.message, { - command: 'connect', - id: undefined, - ip: '0.0.0.0', - port: '443' - }); - }); - - it('Construct unl_add request', function() { - const request = remote.requestUnlAdd('0.0.0.0'); - assert.deepEqual(request.message, { - command: 'unl_add', - node: '0.0.0.0', - id: undefined - }); - }); - - it('Construct unl_list request', function() { - const request = remote.requestUnlList(); - assert.deepEqual(request.message, { - command: 'unl_list', - id: undefined - }); - }); - - it('Construct unl_delete request', function() { - const request = remote.requestUnlDelete('0.0.0.0'); - assert.deepEqual(request.message, { - command: 'unl_delete', - node: '0.0.0.0', - id: undefined - }); - }); - - it('Construct subscribe request', function() { - const request = remote.requestSubscribe(['server', 'ledger']); - assert.deepEqual(request.message, { - command: 'subscribe', - id: undefined, - streams: ['server', 'ledger'] - }); - }); - it('Construct unsubscribe request', function() { - const request = remote.requestUnsubscribe(['server', 'ledger']); - assert.deepEqual(request.message, { - command: 'unsubscribe', - id: undefined, - streams: ['server', 'ledger'] - }); - }); - - it('Construct ping request', function() { - const request = remote.requestPing(); - assert.deepEqual(request.message, { - command: 'ping', - id: undefined - }); - }); - it('Construct ping request -- with server', function() { - const request = remote.requestPing('wss://s1.ripple.com:443'); - assert.strictEqual(request.server, remote._servers[0]); - assert.deepEqual(request.message, { - command: 'ping', - id: undefined - }); - }); - it('Construct account_currencies request', function() { - const request = remote.requestAccountCurrencies({ - account: ADDRESS - }, lodash.noop); - - assert.strictEqual(request.message.command, 'account_currencies'); - assert.strictEqual(request.message.account, ADDRESS); - assert.strictEqual(request.requested, true); - }); - - it('Construct account_info request', function() { - const request = remote.requestAccountInfo({ - account: ADDRESS - }, lodash.noop); - - assert.strictEqual(request.message.command, 'account_info'); - assert.strictEqual(request.message.account, ADDRESS); - assert.strictEqual(request.requested, true); - }); - - it('Construct account_info request -- with ledger index', function() { - const request = remote.requestAccountInfo({ - account: ADDRESS, - ledger: 9592219 - }, lodash.noop); - assert.strictEqual(request.message.command, 'account_info'); - assert.strictEqual(request.message.account, ADDRESS); - assert.strictEqual(request.message.ledger_index, 9592219); - assert.strictEqual(request.requested, true); - }); - - it('Construct account_info request -- with ledger hash', function() { - const request = remote.requestAccountInfo({ - account: ADDRESS, - ledger: LEDGER_HASH - }, lodash.noop); - assert.strictEqual(request.message.command, 'account_info'); - assert.strictEqual(request.message.account, ADDRESS); - assert.strictEqual(request.message.ledger_hash, LEDGER_HASH); - assert.strictEqual(request.requested, true); - }); - it('Construct account_info request -- with ledger identifier', function() { - const request = remote.requestAccountInfo({ - account: ADDRESS, - ledger: 'validated' - }, lodash.noop); - assert.strictEqual(request.message.command, 'account_info'); - assert.strictEqual(request.message.account, ADDRESS); - assert.strictEqual(request.message.ledger_index, 'validated'); - assert.strictEqual(request.requested, true); - }); - - it('Construct account balance request -- with ledger index', function() { - const request = remote.requestAccountBalance({ - account: ADDRESS, - ledger: 9592219 - }, lodash.noop); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.account_root, ADDRESS); - assert.strictEqual(request.message.ledger_index, 9592219); - assert.strictEqual(request.requested, true); - }); - it('Construct account balance request -- with ledger hash', function() { - const request = remote.requestAccountBalance({ - account: ADDRESS, - ledger: LEDGER_HASH - }, lodash.noop); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.account_root, ADDRESS); - assert.strictEqual(request.message.ledger_hash, LEDGER_HASH); - assert.strictEqual(request.requested, true); - }); - it('Construct account balance request -- with ledger identifier', function() { - const request = remote.requestAccountBalance({ - account: ADDRESS, - ledger: 'validated' - }, lodash.noop); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.account_root, ADDRESS); - assert.strictEqual(request.message.ledger_index, 'validated'); - assert.strictEqual(request.requested, true); - }); - - it('Construct account flags request', function() { - const request = remote.requestAccountFlags({account: ADDRESS}, lodash.noop); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.account_root, ADDRESS); - assert.strictEqual(request.requested, true); - }); - it('Construct account owner count request', function() { - const request = remote.requestOwnerCount({account: ADDRESS}, lodash.noop); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.account_root, ADDRESS); - assert.strictEqual(request.requested, true); - }); - - it('Construct account_lines request', function() { - const request = remote.requestAccountLines({account: ADDRESS}, lodash.noop); - assert.deepEqual(request.message, { - command: 'account_lines', - id: undefined, - account: ADDRESS - }); - assert.strictEqual(request.requested, true); - }); - it('Construct account_lines request -- with peer', function() { - const request = remote.requestAccountLines({ - account: ADDRESS, - peer: ADDRESS - }, lodash.noop); - assert.deepEqual(request.message, { - command: 'account_lines', - id: undefined, - account: ADDRESS, - peer: ADDRESS - }); - assert.strictEqual(request.requested, true); - }); - it('Construct account_lines request -- with limit', function() { - const request = remote.requestAccountLines({ - account: ADDRESS, - limit: 100 - }, lodash.noop); - assert.deepEqual(request.message, { - command: 'account_lines', - id: undefined, - account: ADDRESS, - limit: 100 - }); - assert.strictEqual(request.requested, true); - }); - it('Construct account_lines request -- with limit and marker', function() { - const request = remote.requestAccountLines({ - account: ADDRESS, - limit: 100, - marker: PAGING_MARKER, - ledger: 9592219 - }, lodash.noop); - assert.deepEqual(request.message, { - command: 'account_lines', - id: undefined, - account: ADDRESS, - limit: 100, - marker: PAGING_MARKER, - ledger_index: 9592219 - }); - assert.strictEqual(request.requested, true); - }); - it('Construct account_lines request -- with min limit', function() { - assert.strictEqual(remote.requestAccountLines({ - account: ADDRESS, limit: 0 - }).message.limit, 0); - assert.strictEqual(remote.requestAccountLines({ - account: ADDRESS, limit: -1 - }).message.limit, 0); - assert.strictEqual(remote.requestAccountLines({ - account: ADDRESS, limit: -1e9 - }).message.limit, 0); - assert.strictEqual(remote.requestAccountLines({ - account: ADDRESS, limit: -1e24 - }).message.limit, 0); - }); - it('Construct account_lines request -- with max limit', function() { - assert.strictEqual(remote.requestAccountLines({ - account: ADDRESS, limit: 1e9 - }).message.limit, 1e9); - assert.strictEqual(remote.requestAccountLines({ - account: ADDRESS, limit: 1e9 + 1 - }).message.limit, 1e9); - assert.strictEqual(remote.requestAccountLines({ - account: ADDRESS, limit: 1e10 - }).message.limit, 1e9); - assert.strictEqual(remote.requestAccountLines({ - account: ADDRESS, limit: 1e24 - }).message.limit, 1e9); - }); - - it('Construct account_lines request -- with marker -- missing ledger', - function() { - assert.throws(function() { - remote.requestAccountLines({account: ADDRESS, marker: PAGING_MARKER}); - }, 'A ledger_index or ledger_hash must be provided when using a marker'); - - assert.throws(function() { - remote.requestAccountLines({ - account: ADDRESS, - marker: PAGING_MARKER, - ledger: 'validated' - }); - }, 'A ledger_index or ledger_hash must be provided when using a marker'); - - assert.throws(function() { - remote.requestAccountLines({ - account: ADDRESS, - marker: PAGING_MARKER, - ledger: NaN - }); - }, 'A ledger_index or ledger_hash must be provided when using a marker'); - - assert.throws(function() { - remote.requestAccountLines({ - account: ADDRESS, - marker: PAGING_MARKER, - ledger: LEDGER_HASH.substr(0, 63) - }); - }, 'A ledger_index or ledger_hash must be provided when using a marker'); - - assert.throws(function() { - remote.requestAccountLines({ - account: ADDRESS, marker: PAGING_MARKER, ledger: LEDGER_HASH + 'F' - }); - }, 'A ledger_index or ledger_hash must be provided when using a marker'); - }); - it('Construct account_lines request -- with callback', function() { - const request = remote.requestAccountLines({ - account: ADDRESS - }, callback); - - assert.deepEqual(request.message, { - command: 'account_lines', - id: undefined, - account: ADDRESS - }); - }); - - it('Construct account_tx request', function() { - let request = remote.requestAccountTransactions({ - account: ACCOUNT_ONE, - ledger_index_min: -1, - ledger_index_max: -1, - limit: 5, - forward: true, - marker: PAGING_MARKER - }); - - assert.deepEqual(request.message, { - command: 'account_tx', - id: undefined, - account: ACCOUNT_ONE, - ledger_index_min: -1, - ledger_index_max: -1, - binary: true, - forward: true, - limit: 5, - marker: PAGING_MARKER - }); - - request = remote.requestAccountTransactions({ - account: ACCOUNT_ONE, - min_ledger: -1, - max_ledger: -1 - }); - assert.deepEqual(request.message, { - command: 'account_tx', - id: undefined, - account: ACCOUNT_ONE, - binary: true, - ledger_index_min: -1, - ledger_index_max: -1 - }); - }); - it('Construct account_tx request -- no binary', function() { - const request = remote.requestAccountTransactions({ - account: ACCOUNT_ONE, - ledger_index_min: -1, - ledger_index_max: -1, - limit: 5, - forward: true, - binary: false, - marker: PAGING_MARKER - }); - - assert.deepEqual(request.message, { - command: 'account_tx', - id: undefined, - account: ACCOUNT_ONE, - ledger_index_min: -1, - ledger_index_max: -1, - binary: false, - forward: true, - limit: 5, - marker: PAGING_MARKER - }); - }); - - it('Construct account_offers request -- no binary', function() { - const request = remote.requestAccountOffers({account: ADDRESS}); - assert.deepEqual(request.message, { - command: 'account_offers', - id: undefined, - account: ADDRESS - }); - }); - - - it('Construct offer request -- with ledger index', function() { - const request = remote.requestOffer({ - index: TRANSACTION_HASH, ledger: LEDGER_INDEX - }); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.offer, TRANSACTION_HASH); - assert.strictEqual(request.message.ledger_index, LEDGER_INDEX); - }); - it('Construct offer request -- with ledger index and sequence', function() { - const request = remote.requestOffer({ - account: ADDRESS, ledger: LEDGER_INDEX, sequence: 5 - }); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.offer.account, ADDRESS); - assert.strictEqual(request.message.offer.seq, 5); - assert.strictEqual(request.message.ledger_index, LEDGER_INDEX); - }); - it('Construct offer request -- with ledger hash', function() { - const request = remote.requestOffer({ - account: ADDRESS, ledger: LEDGER_HASH, sequence: 5 - }); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.offer.account, ADDRESS); - assert.strictEqual(request.message.offer.seq, 5); - assert.strictEqual(request.message.ledger_hash, LEDGER_HASH); - }); - it('Construct offer request -- with ledger identifier and sequence', - function() { - const request = remote.requestOffer({ - account: ADDRESS, ledger: 'validated', sequence: 5 - }); - assert.strictEqual(request.message.command, 'ledger_entry'); - assert.strictEqual(request.message.offer.account, ADDRESS); - assert.strictEqual(request.message.offer.seq, 5); - assert.strictEqual(request.message.ledger_index, 'validated'); - }); - - it('Construct book_offers request', function() { - const request = remote.requestBookOffers({ - taker_gets: { - currency: 'USD', - issuer: ADDRESS - }, - taker_pays: { - currency: 'XRP' - } - }); - - assert.deepEqual(request.message, { - command: 'book_offers', - id: undefined, - taker_gets: { - currency: 'USD', - issuer: ADDRESS - }, - taker_pays: { - currency: 'XRP' - }, - taker: ACCOUNT_ONE - }); - }); - - it('Construct book_offers request -- with ledger and limit', function() { - const request = remote.requestBookOffers({ - gets: { - currency: 'USD', - issuer: ADDRESS - }, - pays: { - currency: 'XRP' - }, - ledger: LEDGER_HASH, - limit: 10 - }); - - assert.deepEqual(request.message, { - command: 'book_offers', - id: undefined, - taker_gets: { - currency: 'USD', - issuer: ADDRESS - }, - taker_pays: { - currency: 'XRP' - }, - taker: ACCOUNT_ONE, - ledger_hash: LEDGER_HASH, - limit: 10 - }); - }); - - it('Construct tx request', function() { - const request = remote.requestTransaction({ - hash: TRANSACTION_HASH - }); - - assert.deepEqual(request.message, { - command: 'tx', - id: undefined, - binary: true, - transaction: TRANSACTION_HASH - }); - }); - it('Construct tx request -- no binary', function() { - const request = remote.requestTransaction({ - hash: TRANSACTION_HASH, - binary: false - }); - - assert.deepEqual(request.message, { - command: 'tx', - id: undefined, - binary: false, - transaction: TRANSACTION_HASH - }); - }); - - it('Construct transaction_entry request', function() { - const request = remote.requestTransactionEntry({ - hash: TRANSACTION_HASH - }); - - assert.deepEqual(request.message, { - command: 'transaction_entry', - id: undefined, - tx_hash: TRANSACTION_HASH, - ledger_index: 'validated' - }); - }); - it('Construct transaction_entry request -- with ledger index', function() { - const request = remote.requestTransactionEntry({ - hash: TRANSACTION_HASH, - ledger: 1 - }); - - assert.deepEqual(request.message, { - command: 'transaction_entry', - id: undefined, - tx_hash: TRANSACTION_HASH, - ledger_index: 1 - }); - }); - it('Construct transaction_entry request -- with ledger hash', function() { - const request = remote.requestTransactionEntry({ - hash: TRANSACTION_HASH, - ledger: LEDGER_HASH - }); - - assert.deepEqual(request.message, { - command: 'transaction_entry', - id: undefined, - tx_hash: TRANSACTION_HASH, - ledger_hash: LEDGER_HASH - }); - }); - it('Construct transaction_entry request -- with invalid ledger', function() { - assert.throws(function() { - remote.requestTransactionEntry({ - hash: TRANSACTION_HASH, - ledger: {} - }); - }); - }); - - it('Construct tx_history request', function() { - const request = remote.requestTransactionHistory({ - start: 1 - }); - - assert.deepEqual(request.message, { - command: 'tx_history', - id: undefined, - start: 1 - }); - }); - - it('Construct wallet_accounts request', function() { - const request = remote.requestWalletAccounts({ - seed: 'shmnpxY42DaoyNbNQDoGuymNT1T9U' - }); - - assert.deepEqual(request.message, { - command: 'wallet_accounts', - id: undefined, - seed: 'shmnpxY42DaoyNbNQDoGuymNT1T9U' - }); - }); - it('Construct wallet_accounts request -- untrusted', function() { - remote.trusted = false; - - assert.throws(function() { - remote.requestWalletAccounts({ - seed: 'shmnpxY42DaoyNbNQDoGuymNT1T9U' - }); - }); - }); - - it('Construct sign request', function() { - const request = remote.requestSign({ - secret: 'shmnpxY42DaoyNbNQDoGuymNT1T9U', - tx_json: { - Flags: 0, - TransactionType: 'AccountSet', - Account: 'rwLZs9MUVv28XZdYXDk9uNRUpAh1c6jij8' - } - }); - - assert.deepEqual(request.message, { - command: 'sign', - id: undefined, - secret: 'shmnpxY42DaoyNbNQDoGuymNT1T9U', - tx_json: { - Flags: 0, - TransactionType: 'AccountSet', - Account: 'rwLZs9MUVv28XZdYXDk9uNRUpAh1c6jij8' - } - }); - }); - it('Construct sign request -- untrusted', function() { - remote.trusted = false; - - assert.throws(function() { - remote.requestSign({ - secret: 'shmnpxY42DaoyNbNQDoGuymNT1T9U', - tx_json: { - Flags: 0, - TransactionType: 'AccountSet', - Account: 'rwLZs9MUVv28XZdYXDk9uNRUpAh1c6jij8' - } - }); - }); - }); - - it('Construct submit request', function() { - const request = remote.requestSubmit(); - assert.deepEqual(request.message, { - command: 'submit', - id: undefined - }); - }); - - it('Construct transaction', function() { - let tx = remote.createTransaction('AccountSet', { - account: 'rwLZs9MUVv28XZdYXDk9uNRUpAh1c6jij8', - flags: 0 - }); - assert(tx instanceof Transaction); - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'AccountSet', - Account: 'rwLZs9MUVv28XZdYXDk9uNRUpAh1c6jij8' - }); - - tx = remote.createTransaction(); - assert(tx instanceof Transaction); - assert.deepEqual(tx.tx_json, { - Flags: 0 - }); - }); - it('Construct transaction -- invalid type', function() { - assert.throws(function() { - remote.createTransaction('AccountSetz', { - account: 'rwLZs9MUVv28XZdYXDk9uNRUpAh1c6jij8', - flags: 0 - }); - }); - }); - - it('Construct ledger_accept request', function() { - remote._stand_alone = true; - const request = remote.requestLedgerAccept(); - - assert.deepEqual(request.message, { - command: 'ledger_accept', - id: undefined - }); - - remote._servers[0].emit('connect'); - remote._servers[0].emit('message', { - type: 'ledgerClosed', - fee_base: 10, - fee_ref: 10, - ledger_hash: - 'F824560DD788E5E4B65F5843A6616872873EAB74AA759C73A992355FFDFC4237', - ledger_index: 11368614, - ledger_time: 475696280, - reserve_base: 20000000, - reserve_inc: 5000000, - txn_count: 9, - validated_ledgers: '32570-11368614' - }); - }); - it('Construct ledger_accept request -- not standalone', function() { - assert.throws(function() { - remote.requestLedgerAccept(); - }); - }); - - it('Construct ripple balance request', function() { - const request = remote.requestRippleBalance({ - account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6', - ledger: 1, - currency: 'USD' - }); - - assert.deepEqual(request.message, { - command: 'ledger_entry', - id: undefined, - ripple_state: { - currency: 'USD', - accounts: [ - 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6' - ] - }, - ledger_index: 1 - }); - }); - - it('Construct ripple_path_find request', function() { - const request = remote.requestRipplePathFind({ - source_account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - destination_account: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6', - destination_amount: '1/USD/rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - source_currencies: [{ - currency: 'BTC', issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6' - }] - }); - - assert.deepEqual(request.message, { - command: 'ripple_path_find', - id: undefined, - source_account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - destination_account: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6', - destination_amount: { - value: '1', - currency: 'USD', - issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54' - }, - source_currencies: [{ - issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6', - currency: 'BTC' - }] - }); - }); - - 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(); - }); - - }); - - 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() { - const request = remote.requestPathFindCreate({ - source_account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - destination_account: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6', - destination_amount: '1/USD/rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - source_currencies: [{ - currency: 'BTC', issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6' - }] - }); - - assert.deepEqual(request.message, { - command: 'path_find', - id: undefined, - subcommand: 'create', - source_account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - destination_account: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6', - destination_amount: { - value: '1', - currency: 'USD', - issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54' - }, - source_currencies: [{ - issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6', - currency: 'BTC' - }] - }); - }); - - it('Construct path_find close request', function() { - const request = remote.requestPathFindClose(); - - assert.deepEqual(request.message, { - command: 'path_find', - id: undefined, - subcommand: 'close' - }); - }); - - it('Construct gateway_balances request', function() { - const request = remote.requestGatewayBalances({ - account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - hotwallet: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5', - strict: true - }); - - assert.deepEqual(request.message, { - command: 'gateway_balances', - id: undefined, - account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', - hotwallet: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5', - strict: true - }); - }); - - it('Construct Payment transaction', function() { - const tx = remote.createTransaction('Payment', { - account: TX_JSON.Account, - destination: TX_JSON.Destination, - amount: TX_JSON.Amount - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'Payment', - Account: TX_JSON.Account, - Destination: TX_JSON.Destination, - Amount: TX_JSON.Amount - }); - }); - it('Construct AccountSet transaction', function() { - const tx = remote.createTransaction('AccountSet', { - account: TX_JSON.Account, - set: 'asfRequireDest' - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'AccountSet', - Account: TX_JSON.Account, - SetFlag: 1 - }); - }); - it('Construct TrustSet transaction', function() { - const tx = remote.createTransaction('TrustSet', { - account: TX_JSON.Account, - limit: '1/USD/' + TX_JSON.Destination - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'TrustSet', - Account: TX_JSON.Account, - LimitAmount: { - value: '1', - currency: 'USD', - issuer: TX_JSON.Destination - } - }); - }); - it('Construct OfferCreate transaction', function() { - const tx = remote.createTransaction('OfferCreate', { - account: TX_JSON.Account, - taker_gets: '1/USD/' + TX_JSON.Destination, - taker_pays: '1/BTC/' + TX_JSON.Destination - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'OfferCreate', - Account: TX_JSON.Account, - TakerGets: { - value: '1', - currency: 'USD', - issuer: TX_JSON.Destination - }, - TakerPays: { - value: '1', - currency: 'BTC', - issuer: TX_JSON.Destination - } - }); - }); - it('Construct OfferCancel transaction', function() { - const tx = remote.createTransaction('OfferCancel', { - account: TX_JSON.Account, - offer_sequence: 1 - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'OfferCancel', - Account: TX_JSON.Account, - OfferSequence: 1 - }); - }); - it('Construct SetRegularKey transaction', function() { - const tx = remote.createTransaction('SetRegularKey', { - account: TX_JSON.Account, - regular_key: TX_JSON.Destination - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'SetRegularKey', - Account: TX_JSON.Account, - RegularKey: TX_JSON.Destination - }); - }); - - it('Construct SignerListSet transaction', function() { - const tx = remote.createTransaction('SignerListSet', { - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - signerQuorum: 3, - signers: [ - { - account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW', - weight: 1 - }, - { - account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK', - weight: 2 - } - ] - }); - assert(tx instanceof Transaction); - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'SignerListSet', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - SignerQuorum: 3, - SignerEntries: [ - { - SignerEntry: { - Account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW', - SignerWeight: 1 - } - }, - { - SignerEntry: { - Account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK', - SignerWeight: 2 - } - } - ] - }); - }); - - it('Construct SuspendedPaymentCreate transaction', function() { - const tx = remote.createTransaction('SuspendedPaymentCreate', { - account: TX_JSON.Account, - destination: TX_JSON.Destination, - amount: TX_JSON.Amount - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'SuspendedPaymentCreate', - Account: TX_JSON.Account, - Destination: TX_JSON.Destination, - Amount: TX_JSON.Amount - }); - }); - it('Construct SuspendedPaymentFinish transaction', function() { - const tx = remote.createTransaction('SuspendedPaymentFinish', { - account: TX_JSON.Account, - owner: TX_JSON.Account, - paymentSequence: 1234 - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'SuspendedPaymentFinish', - Account: TX_JSON.Account, - Owner: TX_JSON.Account, - OfferSequence: 1234 - }); - }); - it('Construct SuspendedPaymentCancel transaction', function() { - const tx = remote.createTransaction('SuspendedPaymentCancel', { - account: TX_JSON.Account, - owner: TX_JSON.Account, - paymentSequence: 1234 - }); - - assert.deepEqual(tx.tx_json, { - Flags: 0, - TransactionType: 'SuspendedPaymentCancel', - Account: TX_JSON.Account, - Owner: TX_JSON.Account, - OfferSequence: 1234 - }); - }); -}); diff --git a/test/request-test.js b/test/request-test.js deleted file mode 100644 index 29d8a82e..00000000 --- a/test/request-test.js +++ /dev/null @@ -1,1182 +0,0 @@ -'use strict'; -const assert = require('assert'); -const Request = require('ripple-lib').Request; -const Remote = require('ripple-lib').Remote; -const Server = require('ripple-lib').Server; -const RippleError = require('ripple-lib').RippleError; - -function makeServer(url) { - const server = new Server(new process.EventEmitter(), url); - server._connected = true; - return server; -} - -const SERVER_INFO = { - 'info': { - 'build_version': '0.25.2-rc1', - 'complete_ledgers': '32570-7016339', - 'hostid': 'LIED', - 'io_latency_ms': 1, - 'last_close': { - 'converge_time_s': 2.013, - 'proposers': 5 - }, - 'load_factor': 1, - 'peers': 42, - 'pubkey_node': 'n9LpxYuMx4Epz4Wz8Kg2kH3eBTx1mUtHnYwtCdLoj3HC85L2pvBm', - 'server_state': 'full', - 'validated_ledger': { - 'age': 0, - 'base_fee_xrp': 0.00001, - 'hash': - 'E43FD49087B18031721D9C3C4743FE1692C326AFF7084A2C01B355CE65A4C699', - 'reserve_base_xrp': 20, - 'reserve_inc_xrp': 5, - 'seq': 7016339 - }, - 'validation_quorum': 3 - } -}; - -describe('Request', function() { - it('Send request', function() { - const remote = { - request: function(req) { - assert(req instanceof Request); - assert.strictEqual(typeof req.message, 'object'); - assert.strictEqual(req.message.command, 'server_info'); - }, - on: function() { - }, - once: function() { - }, - removeListener: function() { - }, - isConnected: function() { - return true; - } - }; - - const request = new Request(remote, 'server_info'); - - request.request(); - - // Should only request once - assert.throws(function() { - request.request(); - }, Error); - - }); - - it('Send request - reconnect', function(done) { - const server = makeServer('wss://localhost:5006'); - let emitted = 0; - - const remote = new Remote(); - remote._connected = true; - remote._servers = [server]; - - server._request = function(req) { - assert(req instanceof Request); - assert.strictEqual(typeof req.message, 'object'); - assert.strictEqual(req.message.command, 'server_info'); - if (++emitted === 1) { - setTimeout(function() { - remote.emit('connected'); - }, 2); - } if (emitted === 2) { - setTimeout(function() { - req.emit('success', SERVER_INFO); - req.emit('response', SERVER_INFO); - }, 2); - } - }; - - const request = new Request(remote, 'server_info'); - - request.callback(function() { - assert.strictEqual(emitted, 2); - done(); - }); - }); - - describe('Broadcast', function() { - - 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'); - } - - function isSuccessResponse(res) { - return res - && typeof res === 'object' - && !res.hasOwnProperty('error'); - } - - it('Send request', function(done) { - const servers = [ - makeServer('wss://localhost:5006'), - makeServer('wss://localhost:5007') - ]; - - let requests = 0; - - - servers[0]._request = function(req) { - ++requests; - checkRequest(req); - setTimeout(() => { - req.emit('error', errorResponse, this); - }, 2); - }; - - servers[1]._request = function(req) { - ++requests; - checkRequest(req); - setTimeout(() => { - req.emit('success', successResponse, this); - }, 10); - }; - - 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); - assert.strictEqual(requests, 2, 'Failed to broadcast'); - assert.deepEqual(res, successResponse); - done(); - }); - }); - - it('Send request -- timeout', function(done) { - const servers = [ - makeServer('wss://localhost:5006'), - makeServer('wss://localhost:5007') - ]; - - let requests = 0; - - servers[0]._request = function(req) { - ++requests; - checkRequest(req); - setTimeout(() => { - req.emit('error', errorResponse, this); - }, 15); - }; - - servers[1]._request = function(req) { - ++requests; - checkRequest(req); - setTimeout(() => { - req.emit('success', successResponse, this); - }, 15); - }; - - const remote = new Remote(); - remote._connected = true; - remote._servers = servers; - - const request = new Request(remote, 'account_info'); - - request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC'; - request.setTimeout(10); - - request.filter(isSuccessResponse); - - 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); - }); - }); - - 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); - }); - }); - }); - - it('Events API', function(done) { - const server = makeServer('wss://localhost:5006'); - - server._request = function(req) { - assert(req instanceof Request); - assert.strictEqual(typeof req.message, 'object'); - assert.strictEqual(req.message.command, 'server_info'); - req.emit('success', SERVER_INFO); - }; - - const remote = new Remote(); - remote._connected = true; - remote._servers = [server]; - - const request = new Request(remote, 'server_info'); - - request.once('success', function(res) { - assert.deepEqual(res, SERVER_INFO); - done(); - }); - - request.request(); - }); - - it('Callback API', function(done) { - const server = makeServer('wss://localhost:5006'); - - server._request = function(req) { - assert(req instanceof Request); - assert.strictEqual(typeof req.message, 'object'); - assert.strictEqual(req.message.command, 'server_info'); - req.emit('success', SERVER_INFO); - }; - - const remote = new Remote(); - remote._connected = true; - remote._servers = [server]; - - const request = new Request(remote, 'server_info'); - - request.callback(function(err, res) { - assert.ifError(err); - assert.deepEqual(res, SERVER_INFO); - done(); - }); - }); - - it('Timeout', function(done) { - const server = makeServer('wss://localhost:5006'); - let successEmitted = false; - - server._request = function(req) { - assert(req instanceof Request); - assert.strictEqual(typeof req.message, 'object'); - assert.strictEqual(req.message.command, 'server_info'); - setTimeout(function() { - successEmitted = true; - req.emit('success', SERVER_INFO); - req.emit('response', SERVER_INFO); - }, 200); - }; - - const remote = new Remote(); - remote._connected = true; - remote._servers = [server]; - - const request = new Request(remote, 'server_info'); - request.setTimeout(10); - - request.on('timeout', function() { - setTimeout(function() { - assert(successEmitted); - done(); - }, 200); - }); - - request.callback(function() { - assert(false, 'Callback should not be called'); - }); - }); - - it('Timeout - satisfied', function(done) { - const server = makeServer('wss://localhost:5006'); - - server._request = function(req) { - assert(req instanceof Request); - assert.strictEqual(typeof req.message, 'object'); - assert.strictEqual(req.message.command, 'server_info'); - setTimeout(function() { - req.emit('success', SERVER_INFO); - req.emit('response', SERVER_INFO); - }, 20); - }; - - const remote = new Remote(); - remote._connected = true; - remote._servers = [server]; - - const request = new Request(remote, 'server_info'); - - let timedOut = false; - - request.once('timeout', function() { - timedOut = true; - }); - - request.setTimeout(100); - - request.callback(function(err, res) { - setTimeout(function() { - assert(!timedOut, 'must not timeout'); - assert.ifError(err); - assert.deepEqual(res, SERVER_INFO); - done(); - }, 100); - }); - }); - - it('Set server', function(done) { - const servers = [ - makeServer('wss://localhost:5006'), - makeServer('wss://localhost:5007') - ]; - - servers[1]._request = function(req) { - assert(req instanceof Request); - assert.strictEqual(typeof req.message, 'object'); - assert.strictEqual(req.message.command, 'server_info'); - done(); - }; - - const remote = new Remote(); - remote._connected = true; - remote._servers = servers; - - remote.getServer = function() { - return servers[0]; - }; - - const request = new Request(remote, 'server_info'); - request.setServer(servers[1]); - - assert.strictEqual(request.server, servers[1]); - - request.request(); - }); - - it('Set server - by URL', function(done) { - const servers = [ - makeServer('wss://localhost:5006'), - makeServer('wss://127.0.0.1:5007') - ]; - - servers[1]._request = function(req) { - assert(req instanceof Request); - assert.strictEqual(typeof req.message, 'object'); - assert.strictEqual(req.message.command, 'server_info'); - done(); - }; - - const remote = new Remote(); - remote._connected = true; - remote._servers = servers; - - remote.getServer = function() { - return servers[0]; - }; - - const request = new Request(remote, 'server_info'); - request.setServer('wss://127.0.0.1:5007'); - - assert.strictEqual(request.server, servers[1]); - - request.request(); - }); - - it('Set build path', function() { - const remote = new Remote(); - remote._connected = true; - remote.local_signing = false; - - const request = new Request(remote, 'server_info'); - request.buildPath(true); - assert.strictEqual(request.message.build_path, true); - }); - - it('Remove build path', function() { - const remote = new Remote(); - remote._connected = true; - remote.local_signing = false; - - const request = new Request(remote, 'server_info'); - request.buildPath(false); - assert(!request.message.hasOwnProperty('build_path')); - }); - - it('Set build path with local signing', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - assert.throws(function() { - request.buildPath(true); - }, Error); - }); - - it('Set ledger hash', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.ledgerHash( - 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); - assert.strictEqual(request.message.ledger_hash, - 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); - }); - - it('Set ledger index', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.ledgerIndex(7016915); - assert.strictEqual(request.message.ledger_index, 7016915); - }); - - it('Select cached ledger - index', function() { - const remote = new Remote(); - remote._connected = true; - remote._ledger_current_index = 1; - remote._ledger_hash = - 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'; - - const request = new Request(remote, 'server_info'); - request.ledgerChoose(true); - assert.strictEqual(request.message.ledger_index, 1); - }); - - it('Select cached ledger - hash', function() { - const remote = new Remote(); - remote._connected = true; - remote._ledger_current_index = 1; - remote._ledger_hash = - 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'; - - const request = new Request(remote, 'server_info'); - request.ledgerChoose(); - assert.strictEqual(request.message.ledger_hash, - 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); - assert.strictEqual(request.message.ledger_index, undefined); - }); - - it('Select ledger - identifier', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.ledgerSelect('validated'); - assert.strictEqual(request.message.ledger_index, 'validated'); - assert.strictEqual(request.message.ledger_hash, undefined); - }); - - it('Select ledger - index', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.ledgerSelect(7016915); - assert.strictEqual(request.message.ledger_index, 7016915); - assert.strictEqual(request.message.ledger_hash, undefined); - }); - - it('Select ledger - index (String)', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.ledgerSelect('7016915'); - assert.strictEqual(request.message.ledger_index, 7016915); - assert.strictEqual(request.message.ledger_hash, undefined); - }); - - it('Select ledger - hash', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.ledgerSelect( - 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); - assert.strictEqual(request.message.ledger_hash, - 'B4FD84A73DBD8F0DA9E320D137176EBFED969691DC0AAC7882B76B595A0841AE'); - assert.strictEqual(request.message.ledger_index, undefined); - }); - - it('Select ledger - undefined', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.ledgerSelect(); - assert.strictEqual(request.message.ledger_hash, undefined); - assert.strictEqual(request.message.ledger_index, undefined); - request.ledgerSelect(null); - assert.strictEqual(request.message.ledger_hash, undefined); - assert.strictEqual(request.message.ledger_index, undefined); - request.ledgerSelect(NaN); - assert.strictEqual(request.message.ledger_hash, undefined); - assert.strictEqual(request.message.ledger_index, undefined); - }); - - it('Set account_root', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.accountRoot('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'); - assert.strictEqual(request.message.account_root, - 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'); - }); - - it('Set index', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.index(1); - assert.strictEqual(request.message.index, 1); - }); - - it('Set offer ID', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.offerId('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', 1337); - assert.deepEqual(request.message.offer, { - account: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - seq: 1337 - }); - }); - - it('Set offer index', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.offerIndex(1337); - assert.strictEqual(request.message.offer, 1337); - }); - - it('Set secret', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.secret('mySecret'); - assert.strictEqual(request.message.secret, 'mySecret'); - }); - - it('Set transaction hash', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.txHash( - 'E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7'); - assert.strictEqual(request.message.tx_hash, - 'E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7'); - }); - - it('Set transaction JSON', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - const txJson = { - hash: 'E08D6E9754025BA2534A78707605E0601F03ACE063687A0CA1BDDACFCD1698C7' - }; - request.txJson(txJson); - assert.deepEqual(request.message.tx_json, txJson); - }); - - it('Set transaction blob', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.txBlob('asdf'); - assert.strictEqual(request.message.tx_blob, 'asdf'); - }); - - it('Set ripple state', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.rippleState('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', 'USD'); - assert.deepEqual(request.message.ripple_state, { - currency: 'USD', - accounts: ['r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', - 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'] - }); - }); - - it('Set accounts', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - request.accounts([ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun', - 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - ]); - - assert.deepEqual(request.message.accounts, [ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun', - 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - ]); - }); - - it('Set accounts - string', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - request.accounts('rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun'); - - assert.deepEqual(request.message.accounts, [ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun' - ]); - }); - - it('Set accounts proposed', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - request.accountsProposed([ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun', - 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - ]); - - assert.deepEqual(request.message.accounts_proposed, [ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun', - 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - ]); - }); - - it('Add account', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - request.accounts([ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun' - ]); - - request.addAccount('rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'); - - assert.deepEqual(request.message.accounts, [ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun', - 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - ]); - }); - - it('Add account proposed', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - request.accountsProposed([ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun' - ]); - - request.addAccountProposed('rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'); - - assert.deepEqual(request.message.accounts_proposed, [ - 'rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun', - 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - ]); - }); - - it('Set books', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - const books = [ - { - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - } - } - ]; - - request.books(books); - - assert.deepEqual(request.message.books, [ - { - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'snapshot': true - } - ]); - }); - - it('Add book', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - request.addBook({ - 'taker_gets': { - 'currency': 'CNY', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - } - }); - - assert.deepEqual(request.message.books, [ - { - 'taker_gets': { - 'currency': 'CNY', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'snapshot': true - } - ]); - - const books = [ - { - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - } - } - ]; - - request.books(books); - - assert.deepEqual(request.message.books, [ - { - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'snapshot': true - } - ]); - }); - - it('Add book - missing side', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - request.message.books = undefined; - - const books = [ - { - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - } - } - ]; - - assert.throws(function() { - request.books(books); - }); - }); - - it('Add book - without snapshot', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - request.message.books = undefined; - - const book = { - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'both': true - }; - - request.addBook(book, true); - - assert.deepEqual(request.message.books, [{ - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'both': true, - 'snapshot': true - }]); - }); - - it('Add book - no snapshot', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - request.message.books = undefined; - - const book = { - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'both': true - }; - - request.addBook(book, false); - - assert.deepEqual(request.message.books, [{ - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'both': true - }]); - }); - - it('Add stream', function() { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'subscribe'); - - request.addStream('server', 'ledger'); - request.addStream('transactions', 'transactions_proposed'); - request.addStream('accounts', ['rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B']); - request.addStream('accounts_proposed', [ - 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59' - ]); - request.addStream('books', [{ - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - } - }]); - - assert.deepEqual(request.message, { - 'command': 'subscribe', - 'id': undefined, - 'streams': [ - 'server', - 'ledger', - 'transactions', - 'transactions_proposed', - 'accounts', - 'accounts_proposed', - 'books' - ], - 'accounts': [ - 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - ], - 'accounts_proposed': [ - 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59' - ], - 'books': [ - { - 'taker_gets': { - 'currency': 'EUR', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'taker_pays': { - 'currency': 'USD', - 'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B' - }, - 'snapshot': true - } - ] - }); - }); - - it('Emit "before" only once', function(done) { - const remote = new Remote(); - remote._connected = true; - - const request = new Request(remote, 'server_info'); - - let beforeCalled = 0; - - request.on('before', () => { - beforeCalled++; - }); - - request.request(function() {}); - assert.strictEqual(beforeCalled, 1); - done(); - }); - -}); diff --git a/test/server-test.js b/test/server-test.js deleted file mode 100644 index 2754318b..00000000 --- a/test/server-test.js +++ /dev/null @@ -1,1380 +0,0 @@ -'use strict'; - -/* eslint-disable no-new, max-len */ - -const _ = require('lodash'); -const assert = require('assert'); -const ws = require('ws'); -const Remote = require('ripple-lib').Remote; -const Server = require('ripple-lib').Server; -const Request = require('ripple-lib').Request; - -describe('Server', function() { - it('Server constructor - invalid options', function() { - assert.throws(function() { - new Server(new Remote()); - }); - }); - - it('Message listener', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - server._handleMessage = function(message) { - assert.strictEqual(typeof message, 'string'); - assert.deepEqual(JSON.parse(message).result, {}); - done(); - }; - - server.emit('message', JSON.stringify({ - result: {} - })); - }); - - it('Subscribe response listener', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - server._handleResponseSubscribe = function(message) { - assert.strictEqual(typeof message, 'string'); - assert.deepEqual(JSON.parse(message).result, {}); - done(); - }; - - server.emit('response_subscribe', JSON.stringify({ - result: {} - })); - }); - - it('Activity listener', function(done) { - // Activity listener should be enabled - const server = new Server(new Remote(), 'wss://localhost:5006'); - - server.emit('ledger_closed'); - - const interval = setInterval(function() {}, Infinity); - - assert.deepEqual(Object.getPrototypeOf(server._activityInterval), - Object.getPrototypeOf(interval)); - - clearInterval(interval); - - done(); - }); - - it('Reconnect activity listener', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - server.emit('ledger_closed'); - - const interval = setInterval(function() {}, Infinity); - - assert.deepEqual(Object.getPrototypeOf(server._activityInterval), - Object.getPrototypeOf(interval)); - - server.once('disconnect', function() { - // Interval should clear - assert.deepEqual(Object.getPrototypeOf(server._activityInterval), - Object.getPrototypeOf(interval)); - assert.strictEqual(server._activityInterval._onTimeout, null); - - server.once('ledger_closed', function() { - // Interval should be reset - assert.deepEqual(Object.getPrototypeOf(server._activityInterval), - Object.getPrototypeOf(interval)); - assert.strictEqual(typeof server._activityInterval._onTimeout, - 'function'); - done(); - }); - - server.emit('ledger_closed'); - }); - - server.emit('disconnect'); - }); - - it('Update server score ledger close listener', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - const ledger = { - type: 'ledgerClosed', - fee_base: 10, - fee_ref: 10, - ledger_hash: - 'D29E1F2A2617A88E9DAA14F468B169E6875092ECA0B3B1FA2BE1BC5524DE7CB2', - ledger_index: 7035609, - ledger_time: 455327690, - reserve_base: 20000000, - reserve_inc: 5000000, - txn_count: 1, - validated_ledgers: '32570-7035609' - }; - - server._updateScore = function(type, data) { - assert.strictEqual(type, 'ledgerclose'); - assert.deepEqual(data, ledger); - done(); - }; - - server._remote.emit('ledger_closed', ledger); - }); - - it('Update server score ping listener', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - const ping = { - time: 500 - }; - - server._updateScore = function(type, data) { - assert.strictEqual(type, 'response'); - assert.deepEqual(data, ping); - done(); - }; - - server.emit('response_ping', {}, ping); - }); - - it('Update server score load listener', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - const load = { - fee_base: 10, - fee_ref: 10 - }; - - server._updateScore = function(type, data) { - assert.strictEqual(type, 'loadchange'); - assert.deepEqual(data, load); - done(); - }; - - server.emit('load_changed', load); - }); - - it('Websocket constructor', function() { - assert.strictEqual(Server.websocketConstructor(), require('ws')); - }); - - it('Set state online', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - server._state = 'offline'; - - server.once('connect', function() { - assert(server._connected); - done(); - }); - - server._setState('online'); - }); - - it('Set state offline', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - server._state = 'online'; - - server.once('disconnect', function() { - assert(!server._connected); - done(); - }); - - server._setState('offline'); - }); - - it('Set state same state', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - - server._state = 'online'; - - server.once('state', function(state) { - assert(!server._connected); - assert.strictEqual(state, 'offline'); - done(); - }); - - server._setState('online'); - server._setState('offline'); - }); - - it('Check activity - inactive', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = true; - - server.reconnect = function() { - done(); - }; - - server._lastLedgerClose = Date.now() - 1000 * 30; - server._checkActivity(); - }); - - it('Check activity - unconnected', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = false; - - server.reconnect = function() { - assert(false, 'Should not reconnect'); - }; - - server._lastLedgerClose = Date.now() - 1000 * 30; - server._checkActivity(); - setImmediate(function() { - done(); - }); - }); - - it('Check activity - uninitialized', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = false; - - server.reconnect = function() { - assert(false, 'Should not reconnect'); - }; - - // server._lastLedgerClose = Date.now() - 1000 * 30; - server._checkActivity(); - setImmediate(function() { - done(); - }); - }); - - it('Check activity - sufficient ledger close', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = false; - - server.reconnect = function() { - assert(false, 'Should not reconnect'); - }; - - server._lastLedgerClose = Date.now() - 1000 * 20; - server._checkActivity(); - setImmediate(function() { - done(); - }); - }); - - it('Update score - response', function() { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = true; - - assert.deepEqual(server._scoreWeights, { - ledgerclose: 5, - response: 1 - }); - - assert.strictEqual(server._score, 0); - - server._updateScore('response', { - time: Date.now() - 1000 - }); - - // 1000ms second ping / 200ms * weight of 1 - assert.strictEqual(server._score, 5); - }); - - it('Update score - ledger', function() { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = true; - server._lastLedgerIndex = 1; - - assert.deepEqual(server._scoreWeights, { - ledgerclose: 5, - response: 1 - }); - - assert.strictEqual(server._score, 0); - - server._updateScore('ledgerclose', { - ledger_index: 5 - }); - - // Four ledgers behind the leading ledger * weight of 5 - assert.strictEqual(server._score, 20); - }); - - it('Update score - load', function() { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = true; - server._fee_cushion = 1; - - assert.deepEqual(server._scoreWeights, { - ledgerclose: 5, - response: 1 - }); - - assert.strictEqual(server._fee, 10); - - server.emit('message', { - type: 'serverStatus', - load_base: 256, - load_factor: 256 * 10, - server_status: 'full' - }); - - // server._updateScore('loadchange', { }); - - assert.strictEqual(server._fee, 100); - }); - - it('Update score - reaching reconnect threshold', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._lastLedgerIndex = 1; - server._connected = true; - - server.reconnect = function() { - done(); - }; - - assert.deepEqual(server._scoreWeights, { - ledgerclose: 5, - response: 1 - }); - - assert.strictEqual(server._score, 0); - - server._updateScore('ledgerclose', { - ledger_index: 250 - }); - - // Four ledgers behind the leading ledger * weight of 5 - assert.strictEqual(server._score, 1245); - }); - - it('Get remote address', function() { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = true; - server._ws = { - _socket: { - remoteAddress: '127.0.0.1' - } - }; - assert.strictEqual(server._remoteAddress(), '127.0.0.1'); - }); - - it('Disconnect', function(done) { - const server = new Server(new Remote(), 'wss://localhost:5006'); - server._connected = true; - - server._ws = { - close: function() { - assert(!server._shouldConnect); - assert.strictEqual(server._state, 'offline'); - done(); - } - }; - - server.disconnect(); - }); - - it('Connect', function(done) { - const wss = new ws.Server({ - port: 5748 - }); - - wss.once('connection', function(_ws) { - _ws.once('message', function(message) { - const m = JSON.parse(message); - - assert.deepEqual(m, { - command: 'subscribe', - id: 0, - streams: ['ledger', 'server'] - }); - - _ws.send(JSON.stringify({ - id: 0, - status: 'success', - type: 'response', - result: { - fee_base: 10, - fee_ref: 10, - ledger_hash: - '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - ledger_index: 7053695, - ledger_time: 455414390, - load_base: 256, - load_factor: 256, - random: - 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - reserve_base: 20000000, - reserve_inc: 5000000, - server_status: 'full', - validated_ledgers: '32570-7053695' - } - })); - }); - }); - - const server = new Server(new Remote(), 'ws://localhost:5748'); - - server.once('connect', function() { - server.once('disconnect', function() { - wss.close(); - done(); - }); - server.disconnect(); - }); - - server.connect(); - }); - - it('Connect -- with basic auth', function(done) { - const port = 5748; - function verifyClient(info, callback) { - assert.strictEqual(info.req.headers.authorization, 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='); - callback(true); - } - const wss = new ws.Server({port, verifyClient}); - - function handleSubscribeRequest(_ws, message) { - const m = JSON.parse(message); - - assert.deepEqual(m, { - command: 'subscribe', - id: 0, - streams: ['ledger', 'server'] - }); - - _ws.send(JSON.stringify({ - id: 0, - status: 'success', - type: 'response', - result: { - fee_base: 10, - fee_ref: 10, - ledger_hash: - '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - ledger_index: 7053695, - ledger_time: 455414390, - load_base: 256, - load_factor: 256, - random: - 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - reserve_base: 20000000, - reserve_inc: 5000000, - server_status: 'full', - validated_ledgers: '32570-7053695' - } - })); - } - - - wss.once('connection', function(_ws) { - _ws.once('message', _.partial(handleSubscribeRequest, _ws)); - }); - - const remote = new Remote({ - basic_auth: 'username:password' - }); - const server = new Server(remote, `ws://localhost:${port}`); - - server.once('connect', function() { - server.once('disconnect', function() { - wss.close(); - done(); - }); - server.disconnect(); - }); - - server.connect(); - }); - - it('Connect - already connected', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._connected = true; - - server.once('connect', function() { - assert(false, 'Should not connect'); - }); - - server.connect(); - - setImmediate(function() { - done(); - }); - }); - - it.skip('Connect - prior WebSocket connection exists', function(done) { - const wss = new ws.Server({ - port: 5748 - }); - - wss.once('connection', function(_ws) { - _ws.once('message', function(message) { - const m = JSON.parse(message); - - assert.deepEqual(m, { - command: 'subscribe', - id: 0, - streams: ['ledger', 'server'] - }); - - _ws.send(JSON.stringify({ - id: 0, - status: 'success', - type: 'response', - result: { - fee_base: 10, - fee_ref: 10, - ledger_hash: - '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - ledger_index: 7053695, - ledger_time: 455414390, - load_base: 256, - load_factor: 256, - random: - 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - reserve_base: 20000000, - reserve_inc: 5000000, - server_status: 'full', - validated_ledgers: '32570-7053695' - } - })); - }); - }); - - const server = new Server(new Remote(), 'ws://localhost:5748'); - - server.once('connect', function() { - server.once('disconnect', function() { - wss.close(); - done(); - }); - server.disconnect(); - }); - - server.connect(); - server.connect(); - }); - - it('Connect - no WebSocket constructor', function() { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._connected = false; - - const websocketConstructor = Server.websocketConstructor; - - Server.websocketConstructor = function() { - return undefined; - }; - - assert.throws(function() { - server.connect(); - }, Error); - - Server.websocketConstructor = websocketConstructor; - }); - - it('Connect - partial history disabled', function(done) { - const wss = new ws.Server({ - port: 5748 - }); - - wss.once('connection', function(_ws) { - _ws.once('message', function(message) { - const m = JSON.parse(message); - - assert.deepEqual(m, { - command: 'subscribe', - id: 0, - streams: ['ledger', 'server'] - }); - - _ws.send(JSON.stringify({ - id: 0, - status: 'success', - type: 'response', - result: { - fee_base: 10, - fee_ref: 10, - ledger_hash: - '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - ledger_index: 7053695, - ledger_time: 455414390, - load_base: 256, - load_factor: 256, - random: - 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - reserve_base: 20000000, - reserve_inc: 5000000, - server_status: 'syncing', - validated_ledgers: '3175520-3176615' - } - })); - }); - }); - - const server = new Server(new Remote({ - allow_partial_history: false - }), 'ws://localhost:5748'); - - server.reconnect = function() { - setImmediate(function() { - wss.close(); - done(); - }); - }; - - server.once('connect', function() { - assert(false, 'Should not connect'); - }); - - server.connect(); - }); - - it('Connect - syncing state', function(done) { - // Test that fee and load defaults are not overwritten by - // undefined properties on server subscribe response - const wss = new ws.Server({ - port: 5748 - }); - - wss.once('connection', function(_ws) { - _ws.once('message', function(message) { - const m = JSON.parse(message); - - assert.deepEqual(m, { - command: 'subscribe', - id: 0, - streams: ['ledger', 'server'] - }); - - _ws.send(JSON.stringify({ - id: 0, - status: 'success', - type: 'response', - result: { - load_base: 256, - load_factor: 256, - server_status: 'syncing' - } - })); - }); - }); - - const server = new Server(new Remote(), 'ws://localhost:5748'); - - server.once('connect', function() { - assert(server.isConnected()); - assert.strictEqual(server._load_base, 256); - assert.strictEqual(server._load_factor, 256); - assert.strictEqual(server._fee_base, 10); - assert.strictEqual(server._fee_ref, 10); - wss.close(); - done(); - }); - - server.connect(); - }); - - - it('Reconnect', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._connected = true; - server._shouldConnect = true; - server._ws = { }; - - let disconnected = false; - - server.disconnect = function() { - disconnected = true; - server.emit('disconnect'); - }; - - server.connect = function() { - assert(disconnected); - done(); - }; - - server.reconnect(); - }); - - it('Retry connect', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._connected = false; - server._shouldConnect = true; - - server.connect = function() { - done(); - }; - - server._retryConnect(); - - const timeout = setTimeout(function() {}, Infinity); - - assert.deepEqual(Object.getPrototypeOf(server._retryTimer), - Object.getPrototypeOf(timeout)); - - clearTimeout(timeout); - }); - - it('Handle close', function() { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._ws = { }; - server._handleClose(); - assert.strictEqual(server._ws.onopen, _.noop); - assert.strictEqual(server._ws.onclose, _.noop); - assert.strictEqual(server._ws.onmessage, _.noop); - assert.strictEqual(server._ws.onerror, _.noop); - assert.strictEqual(server._state, 'offline'); - }); - - it('Handle error', function(done) { - const wss = new ws.Server({ - port: 5748 - }); - - wss.once('connection', function(_ws) { - _ws.once('message', function(message) { - const m = JSON.parse(message); - - assert.deepEqual(m, { - command: 'subscribe', - id: 0, - streams: ['ledger', 'server'] - }); - - _ws.send(JSON.stringify({ - id: 0, - status: 'success', - type: 'response', - result: { - fee_base: 10, - fee_ref: 10, - ledger_hash: - '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - ledger_index: 7053695, - ledger_time: 455414390, - load_base: 256, - load_factor: 256, - random: - 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - reserve_base: 20000000, - reserve_inc: 5000000, - server_status: 'full', - validated_ledgers: '32570-7053695' - } - })); - }); - }); - - const server = new Server(new Remote(), 'ws://localhost:5748'); - - server.once('disconnect', function() { - done(); - }); - - server.once('connect', function() { - server._retryConnect = function() { - wss.close(); - }; - server._ws.emit('error', new Error()); - }); - - server.connect(); - }); - - it('Handle message - ledgerClosed', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - - const ledger = { - type: 'ledgerClosed', - ledger_index: 1 - }; - - server.once('ledger_closed', function() { - assert.strictEqual(server._lastLedgerIndex, ledger.ledger_index); - done(); - }); - - server.emit('message', ledger); - }); - - it('Handle message - serverStatus', function(done) { - const remote = new Remote(); - const server = new Server(remote, 'ws://localhost:5748'); - const events = 3; - let receivedEvents = 0; - - const status = { - type: 'serverStatus', - load_base: 256, - load_factor: 256 * 2 - }; - - server.once('load', function(message) { - assert.deepEqual(message, status); - if (++receivedEvents === events) { - done(); - } - }); - - server.once('load_changed', function(message) { - assert.deepEqual(message, status); - if (++receivedEvents === events) { - done(); - } - }); - - remote.once('load_changed', function(message) { - assert.deepEqual(message, status); - if (++receivedEvents === events) { - done(); - } - }); - - server.emit('message', status); - }); - - it('Handle message - serverStatus - no load', function(done) { - const remote = new Remote(); - const server = new Server(remote, 'ws://localhost:5748'); - - const status = { - type: 'serverStatus' - }; - - server.once('load', function(message) { - assert.deepEqual(message, status); - }); - - server.once('load_changed', function() { - assert(false, 'Non-load status should not trigger events'); - }); - - remote.once('load_changed', function() { - assert(false, 'Non-load status should not trigger events'); - }); - - server.emit('message', status); - - setImmediate(function() { - done(); - }); - }); - - it('Handle message - response - success', function(done) { - const remote = new Remote(); - const server = new Server(remote, 'ws://localhost:5748'); - const request = new Request(remote, 'server_info'); - const id = 1; - - assert(request instanceof process.EventEmitter); - - server._requests[id] = request; - - const response = { - id: id, - type: 'response', - status: 'success', - result: { - info: { - build_version: '0.25.2-rc1', - complete_ledgers: '32570-7623483', - hostid: 'MAC', - io_latency_ms: 1, - last_close: { - converge_time_s: 2.052, - proposers: 5 - }, - load_factor: 1, - peers: 50, - pubkey_node: 'n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW', - server_state: 'full', - validated_ledger: { - age: 5, - base_fee_xrp: 0.00001, - hash: - 'AB575193C623179078BE7CC42965FD4262EE8611D1CE7F839CEEBFFEF4B653B6', - reserve_base_xrp: 20, - reserve_inc_xrp: 5, - seq: 7623483 - }, - validation_quorum: 3 - } - } - }; - - let receivedEvents = 0; - const emitters = 3; - - request.once('success', function(message) { - assert.deepEqual(message, response.result); - if (++receivedEvents === emitters) { - done(); - } - }); - - server.once('response_server_info', function(message) { - assert.deepEqual(message, response.result); - if (++receivedEvents === emitters) { - done(); - } - }); - - remote.once('response_server_info', function(message) { - assert.deepEqual(message, response.result); - if (++receivedEvents === emitters) { - done(); - } - }); - - server.emit('message', response); - }); - - it('Handle message - response - error', function(done) { - const remote = new Remote(); - const server = new Server(remote, 'ws://localhost:5748'); - const request = new Request(remote, 'server_info'); - const id = 1; - - assert(request instanceof process.EventEmitter); - - server._requests[id] = request; - - const response = { - id: id, - type: 'response', - status: 'error', - error: { - test: 'property' - } - }; - - request.once('error', function(message) { - assert.deepEqual(message, { - error: 'remoteError', - error_message: 'Remote reported an error.', - remote: response - }); - done(); - }); - - server.emit('message', response); - }); - - it('Handle message - response - no request', function(done) { - const remote = new Remote(); - const server = new Server(remote, 'ws://localhost:5748'); - - const response = { - id: 1, - type: 'response', - status: 'success', - result: { } - }; - - Object.defineProperty(response, 'status', { - get: function() { - assert(false, 'Response status should not be checked'); - } - }); - - server.emit('message', response); - - setImmediate(function() { - done(); - }); - }); - - it('Handle message - path_find', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - - server._handlePathFind = function() { - done(); - }; - - server.emit('message', { - type: 'path_find' - }); - }); - - it('Handle message - invalid message', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - - server.once('unexpected', function() { - done(); - }); - - server.emit('message', { - butt: 'path_find' - }); - }); - - it('Send message', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - - const request = { - id: 1, - message: { - command: 'server_info' - } - }; - - server._ws = { - send: function(message) { - assert.deepEqual(JSON.parse(message), request); - done(); - } - }; - - server._sendMessage(request); - }); - - it('Request', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._connected = true; - - server._ws = { }; - - const request = { - message: { - command: 'server_info' - } - }; - - server._sendMessage = function() { - done(); - }; - - server._request(request); - }); - - it('Request - delayed connect', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._connected = false; - - server._ws = { }; - - const request = { - message: { - command: 'server_info' - } - }; - - server._request(request); - - setImmediate(function() { - server._sendMessage = function() { - done(); - }; - - server.emit('connect'); - }); - }); - - it('Request - no WebSocket', function(done) { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._connected = true; - - server._ws = undefined; - - const request = { - message: { - command: 'server_info' - } - }; - - server._sendMessage = function() { - assert(false, 'Should not send message if WebSocket does not exist'); - }; - - server._request(request); - - setImmediate(function() { - done(); - }); - }); - - it('Check connectivity', function() { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._connected = false; - server._ws = { - readyState: 1 - }; - - assert(!server._isConnected()); - - server._connected = true; - - assert(server._isConnected()); - }); - - it('Compute fee - fee units', function() { - const server = new Server(new Remote(), 'ws://localhost:5748'); - assert.strictEqual(server._computeFee(10), '12'); - }); - - it('Compute fee - bad arg', function() { - const server = new Server(new Remote(), 'ws://localhost:5748'); - assert.throws(function() { - server._computeFee('asdf'); - }); - }); - - it('Compute fee - increased load', function() { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._load_base = 256; - server._load_factor = 256 * 4; - assert.strictEqual(server._computeFee(10), '48'); - }); - - it('Compute reserve', function() { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._reserve_base = 20000000; - server._reserve_inc = 5000000; - assert.strictEqual(server._reserve().to_json(), '20000000'); - }); - - it('Compute reserve, positive OwnerCount', function() { - const server = new Server(new Remote(), 'ws://localhost:5748'); - server._reserve_base = 20000000; - server._reserve_inc = 5000000; - assert.strictEqual(server._reserve(4).to_json(), '40000000'); - }); - - it('Cache hostid', function(done) { - const wss = new ws.Server({ - port: 5748 - }); - - wss.once('connection', function(_ws) { - function sendServerInfo(message) { - _ws.send(JSON.stringify({ - id: message.id, - status: 'success', - type: 'response', - result: { - info: { - build_version: '0.25.2-rc1', - complete_ledgers: '32570-7623483', - hostid: 'MAC', - io_latency_ms: 1, - last_close: { - converge_time_s: 2.052, - proposers: 5 - }, - load_factor: 1, - peers: 50, - pubkey_node: - 'n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW', - server_state: 'full', - validated_ledger: { - age: 5, - base_fee_xrp: 0.00001, - hash: 'AB575193C623179078BE7CC42965FD4262EE8611D1CE7F839' - + 'CEEBFFEF4B653B6', - reserve_base_xrp: 20, - reserve_inc_xrp: 5, - seq: 7623483 - }, - validation_quorum: 3 - } - } - })); - } - - function sendSubscribe(message) { - _ws.send(JSON.stringify({ - id: message.id, - status: 'success', - type: 'response', - result: { - fee_base: 10, - fee_ref: 10, - ledger_hash: - '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - ledger_index: 7053695, - ledger_time: 455414390, - load_base: 256, - load_factor: 256, - random: - 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - reserve_base: 20000000, - reserve_inc: 5000000, - server_status: 'full', - validated_ledgers: '32570-7053695' - } - })); - } - - _ws.on('message', function(message) { - const m = JSON.parse(message); - - switch (m.command) { - case 'subscribe': - assert.strictEqual(m.command, 'subscribe'); - assert.deepEqual(m.streams, ['ledger', 'server']); - setImmediate(function() { - sendSubscribe(m); - }); - break; - case 'server_info': - assert.strictEqual(m.command, 'server_info'); - setImmediate(function() { - sendServerInfo(m); - }); - break; - } - }); - }); - - const server = new Server(new Remote(), 'ws://localhost:5748'); - - server.once('connect', function() { - server.once('response_server_info', function() { - assert.strictEqual(server.getServerID(), 'ws://localhost:5748 ' - + '(n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW)'); - server.once('disconnect', function() { - wss.close(); - done(); - }); - server.disconnect(); - }); - }); - - server.connect(); - }); - - it('Track ledger ranges', function(done) { - const wss = new ws.Server({ - port: 5748 - }); - - wss.once('connection', function(_ws) { - function sendSubscribe(message) { - _ws.send(JSON.stringify({ - id: message.id, - status: 'success', - type: 'response', - result: { - fee_base: 10, - fee_ref: 10, - ledger_hash: - '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - ledger_index: 7053695, - ledger_time: 455414390, - load_base: 256, - load_factor: 256, - random: - 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - reserve_base: 20000000, - reserve_inc: 5000000, - server_status: 'full', - validated_ledgers: '32570-7053695', - pubkey_node: 'n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW' - } - })); - } - - _ws.on('message', function(message) { - const m = JSON.parse(message); - - switch (m.command) { - case 'subscribe': - assert.strictEqual(m.command, 'subscribe'); - assert.deepEqual(m.streams, ['ledger', 'server']); - sendSubscribe(m); - break; - } - }); - }); - - const server = new Server(new Remote(), 'ws://localhost:5748'); - - server.once('connect', function() { - assert.strictEqual(server.hasLedger(32569), false); - assert.strictEqual(server.hasLedger(32570), true); - assert.strictEqual(server.hasLedger(7053695), true); - assert.strictEqual(server.hasLedger(7053696), false); - - server.emit('message', { - type: 'ledgerClosed', - fee_base: 10, - fee_ref: 10, - ledger_hash: - 'F29E1F2A2617A88E9DAA14F468B169E6875092ECA0B3B1FA2BE1BC5524DE7CB2', - ledger_index: 7053696, - ledger_time: 455327690, - reserve_base: 20000000, - reserve_inc: 5000000, - txn_count: 1 - }); - - assert.strictEqual(server.hasLedger(7053696), true); - assert.strictEqual(server.hasLedger( - 'F29E1F2A2617A88E9DAA14F468B169E6875092ECA0B3B1FA2BE1BC5524DE7CB2'), - true); - - server.once('disconnect', done); - wss.close(); - }); - - server.connect(); - }); - - it('Automatic reconnect', function(done) { - const port = 5748; - let connections = 0; - - function handleWsConnection(_ws) { - ++connections; - - _ws.on('message', (message) => { - const parsed = JSON.parse(message); - assert.strictEqual(parsed.command, 'subscribe'); - - _ws.send(JSON.stringify({ - id: parsed.id, - status: 'success', - type: 'response', - result: { - fee_base: 10, - fee_ref: 10, - ledger_hash: - '1838539EE12463C36F2C53B079D807C697E3D93A1936B717E565A4A912E11776', - ledger_index: 7053695, - ledger_time: 455414390, - load_base: 256, - load_factor: 256, - random: - 'E56C9154D9BE94D49C581179356C2E084E16D18D74E8B09093F2D61207625E6A', - reserve_base: 20000000, - reserve_inc: 5000000, - server_status: 'full', - validated_ledgers: '32570-7053695', - pubkey_node: 'n94pSqypSfddzAVj9qoezHyUoetsrMnwgNuBqRJ3WHvM8aMMf7rW' - } - })); - }); - } - - let wss = new ws.Server({port}); - - wss.once('connection', handleWsConnection); - - const remote = new Remote(); - const server = remote.addServer(`ws://localhost:${port}`); - - let receivedReconnectAttempt = false; - server.once('disconnect', () => { - assert.equal(remote.getServer(), null); - server.once('connect', () => { - assert(receivedReconnectAttempt); - assert.equal(connections, 2); - assert(remote.getServer() instanceof Server); - done(); - }); - server.once('connecting', () => { - receivedReconnectAttempt = true; - wss = new ws.Server({port}); - wss.once('connection', handleWsConnection); - }); - }); - server.once('connect', () => { - assert(remote.getServer() instanceof Server); - // Force server disconnect, ripple-lib should attempt - // to reconnect automatically - wss.close(); - }); - server.connect(); - }); -}); diff --git a/test/testconfig.js b/test/testconfig.js deleted file mode 100644 index 3ff37ed5..00000000 --- a/test/testconfig.js +++ /dev/null @@ -1,48 +0,0 @@ -// -// Configuration for unit tests: not to be locally customized. -// - -// Configuration for test accounts. -exports.accounts = { - // Users - "alice" : { - 'account' : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn", - 'secret' : "alice", - }, - "bob" : { - 'account' : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", - 'secret' : "bob", - }, - "carol" : { - 'account' : "rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW", - 'secret' : "carol", - }, - "dan" : { - 'account' : "rJ85Mok8YRNxSo7NnxKGrPuk29uAeZQqwZ", - 'secret' : "dan", - }, - - // Nexuses - "bitstamp" : { - 'account' : "r4jKmc2nQb5yEU6eycefiNKGHTU5NQJASx", - 'secret' : "bitstamp", - }, - "mtgox" : { - 'account' : "rGihwhaqU8g7ahwAvTq6iX5rvsfcbgZw6v", - 'secret' : "mtgox", - }, - - // Merchants - "amazon" : { - 'account' : "rhheXqX7bDnXePJeMHhubDDvw2uUTtenPd", - 'secret' : "amazon", - }, - - // Master account - "root" : { - 'account' : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", - 'secret' : "masterpassphrase", - }, -}; - -// vim:sw=2:sts=2:ts=8:et diff --git a/test/transaction-manager-test.js b/test/transaction-manager-test.js deleted file mode 100644 index b904889b..00000000 --- a/test/transaction-manager-test.js +++ /dev/null @@ -1,911 +0,0 @@ -/* eslint-disable max-len */ - -'use strict'; - -const ws = require('ws'); -const lodash = require('lodash'); -const assert = require('assert-diff'); -const Remote = require('ripple-lib').Remote; -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; - -const LEDGER = require('./fixtures/transactionmanager').LEDGER; -const ACCOUNT = require('./fixtures/transactionmanager').ACCOUNT; -const ACCOUNT2 = require('./fixtures/transactionmanager').ACCOUNT2; -const SUBSCRIBE_RESPONSE = require('./fixtures/transactionmanager') -.SUBSCRIBE_RESPONSE; -const ACCOUNT_INFO_RESPONSE = require('./fixtures/transactionmanager') -.ACCOUNT_INFO_RESPONSE; -const TX_STREAM_TRANSACTION = require('./fixtures/transactionmanager') -.TX_STREAM_TRANSACTION; -const ACCOUNT_TX_TRANSACTION = require('./fixtures/transactionmanager') -.ACCOUNT_TX_TRANSACTION; -const ACCOUNT_TX_RESPONSE = require('./fixtures/transactionmanager') -.ACCOUNT_TX_RESPONSE; -const ACCOUNT_TX_ERROR = require('./fixtures/transactionmanager') -.ACCOUNT_TX_ERROR; -const SUBMIT_RESPONSE = require('./fixtures/transactionmanager') -.SUBMIT_RESPONSE; -const SUBMIT_TEC_RESPONSE = require('./fixtures/transactionmanager') -.SUBMIT_TEC_RESPONSE; -const SUBMIT_TER_RESPONSE = require('./fixtures/transactionmanager') -.SUBMIT_TER_RESPONSE; -const SUBMIT_TEF_RESPONSE = require('./fixtures/transactionmanager') -.SUBMIT_TEF_RESPONSE; -const SUBMIT_TEL_RESPONSE = require('./fixtures/transactionmanager') -.SUBMIT_TEL_RESPONSE; -const SUBMIT_REMOTE_ERROR = require('./fixtures/transactionmanager') -.SUBMIT_REMOTE_ERROR; -const SUBMIT_TOO_BUSY_ERROR = require('./fixtures/transactionmanager') -.SUBMIT_TOO_BUSY_ERROR; - -describe('TransactionManager', function() { - let rippled; - let rippledConnection; - let remote; - let account; - let transactionManager; - - beforeEach(function(done) { - rippled = new ws.Server({port: 5763}); - - rippled.on('connection', function(c) { - const ledger = lodash.extend({}, LEDGER); - c.sendJSON = function(v) { - try { - c.send(JSON.stringify(v)); - } catch (e) /* eslint-disable no-empty */{ - // empty - } /* eslint-enable no-empty */ - }; - c.sendResponse = function(baseResponse, ext) { - assert.strictEqual(typeof baseResponse, 'object'); - assert.strictEqual(baseResponse.type, 'response'); - c.sendJSON(lodash.extend(baseResponse, ext)); - }; - c.closeLedger = function() { - c.sendJSON(lodash.extend(ledger, { - ledger_index: ++ledger.ledger_index - })); - }; - c.on('message', function(m) { - const parsed = JSON.parse(m); - rippled.emit('request_' + parsed.command, parsed, c); - }); - rippledConnection = c; - }); - - rippled.on('request_subscribe', function(message, c) { - if (lodash.isEqual(message.streams, ['ledger', 'server'])) { - c.sendResponse(SUBSCRIBE_RESPONSE, {id: message.id}); - } - }); - rippled.on('request_account_info', function(message, c) { - if (message.account === ACCOUNT.address) { - c.sendResponse(ACCOUNT_INFO_RESPONSE, {id: message.id}); - } - }); - - remote = new Remote({servers: ['ws://localhost:5763']}); - remote.setSecret(ACCOUNT.address, ACCOUNT.secret); - account = remote.account(ACCOUNT.address); - transactionManager = account._transactionManager; - - remote.connect(function() { - setTimeout(done, 10); - }); - }); - - afterEach(function(done) { - remote.disconnect(function() { - rippled.close(); - setImmediate(done); - }); - }); - - it('Normalize transaction', function() { - const t1 = TransactionManager.normalizeTransaction(TX_STREAM_TRANSACTION); - const t2 = TransactionManager.normalizeTransaction(ACCOUNT_TX_TRANSACTION); - - [t1, t2].forEach(function(t) { - assert(t.hasOwnProperty('metadata')); - assert(t.hasOwnProperty('tx_json')); - assert.strictEqual(t.validated, true); - assert.strictEqual(t.ledger_index, 1); - assert.strictEqual(t.engine_result, 'tesSUCCESS'); - assert.strictEqual(t.type, 'transaction'); - assert.strictEqual(t.tx_json.hash, - '01D66ACBD00B2A8F5D66FC8F67AC879CAECF49BC94FB97CF24F66B8406F4C040'); - }); - }); - - it('Handle received transaction', function(done) { - const transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction); - - transaction.once('success', function() { - done(); - }); - - transaction.addId(TX_STREAM_TRANSACTION.transaction.hash); - transactionManager.getPending().push(transaction); - rippledConnection.sendJSON(TX_STREAM_TRANSACTION); - }); - it('Handle received transaction -- failed', function(done) { - const transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction); - - transaction.once('error', function(err) { - assert.strictEqual(err.engine_result, 'tecINSUFF_FEE_P'); - done(); - }); - - transaction.addId(TX_STREAM_TRANSACTION.transaction.hash); - transactionManager.getPending().push(transaction); - rippledConnection.sendJSON(lodash.extend({ }, TX_STREAM_TRANSACTION, { - engine_result: 'tecINSUFF_FEE_P' - })); - }); - it('Handle received transaction -- not submitted', function(done) { - rippledConnection.sendJSON(TX_STREAM_TRANSACTION); - - remote.once('transaction', function() { - assert(transactionManager.getPending().getReceived( - TX_STREAM_TRANSACTION.transaction.hash)); - done(); - }); - }); - it('Handle received transaction -- Account mismatch', function(done) { - const tx = lodash.extend({ }, TX_STREAM_TRANSACTION); - lodash.extend(tx.transaction, { - Account: 'rMP2Y5EZrVZdFKsow11NoKTE5FjXuBQd3d' - }); - rippledConnection.sendJSON(tx); - - setImmediate(function() { - assert(!transactionManager.getPending().getReceived( - TX_STREAM_TRANSACTION.transaction.hash)); - done(); - }); - }); - it('Handle received transaction -- not validated', function(done) { - const tx = lodash.extend({ }, TX_STREAM_TRANSACTION, { - validated: false - }); - rippledConnection.sendJSON(tx); - - setImmediate(function() { - assert(!transactionManager.getPending().getReceived( - TX_STREAM_TRANSACTION.transaction.hash)); - done(); - }); - }); - it('Handle received transaction -- from account_tx', function(done) { - const transaction = Transaction.from_json(ACCOUNT_TX_TRANSACTION.tx); - transaction.once('success', function() { - done(); - }); - - transaction.addId(ACCOUNT_TX_TRANSACTION.tx.hash); - transactionManager.getPending().push(transaction); - transactionManager._transactionReceived(ACCOUNT_TX_TRANSACTION); - }); - - it('Adjust pending transaction fee', function(done) { - const transaction = new Transaction(remote); - transaction.tx_json = ACCOUNT_TX_TRANSACTION.tx; - - transaction.once('fee_adjusted', function(a, b) { - assert.strictEqual(a, '10'); - assert.strictEqual(b, '24'); - assert.strictEqual(transaction.tx_json.Fee, '24'); - done(); - }); - - transactionManager.getPending().push(transaction); - - rippledConnection.sendJSON({ - type: 'serverStatus', - load_base: 256, - load_factor: 256 * 2, - server_status: 'full' - }); - }); - - it('Adjust pending transaction fee -- max fee exceeded', function(done) { - transactionManager._maxFee = 10; - - const transaction = new Transaction(remote); - transaction.tx_json = ACCOUNT_TX_TRANSACTION.tx; - - transaction.once('fee_adjusted', function() { - assert(false, 'Fee should not be adjusted'); - }); - - transactionManager.getPending().push(transaction); - - rippledConnection.sendJSON({ - type: 'serverStatus', - load_base: 256, - load_factor: 256 * 2, - server_status: 'full' - }); - - setImmediate(done); - }); - - it('Adjust pending transaction fee -- no local fee', function(done) { - remote.local_fee = false; - - const transaction = new Transaction(remote); - transaction.tx_json = ACCOUNT_TX_TRANSACTION.tx; - - transaction.once('fee_adjusted', function() { - assert(false, 'Fee should not be adjusted'); - }); - - transactionManager.getPending().push(transaction); - - rippledConnection.sendJSON({ - type: 'serverStatus', - load_base: 256, - load_factor: 256 * 2, - server_status: 'full' - }); - - setImmediate(done); - }); - - it('Wait ledgers', function(done) { - transactionManager._waitLedgers(3, done); - - for (let i = 1; i <= 3; i++) { - rippledConnection.closeLedger(); - } - }); - - it('Wait ledgers -- no ledgers', function(done) { - transactionManager._waitLedgers(0, done); - }); - - it('Update pending status', function(done) { - const transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction); - transaction.submitIndex = 1; - transaction.tx_json.LastLedgerSequence = 10; - - let receivedMissing = false; - let receivedLost = false; - - transaction.once('missing', function() { - receivedMissing = true; - }); - transaction.once('lost', function() { - receivedLost = true; - }); - transaction.once('error', function(err) { - assert.strictEqual(err.engine_result, 'tejMaxLedger'); - assert(receivedMissing); - assert(receivedLost); - done(); - }); - - transaction.addId(TX_STREAM_TRANSACTION.transaction.hash); - transactionManager.getPending().push(transaction); - - for (let i = 1; i <= 10; i++) { - rippledConnection.closeLedger(); - } - }); - - it('Update pending status -- finalized before max ledger exceeded', - function(done) { - const transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction); - transaction.submitIndex = 1; - transaction.tx_json.LastLedgerSequence = 10; - transaction.finalized = true; - - let receivedMissing = false; - let receivedLost = false; - - transaction.once('missing', function() { - receivedMissing = true; - }); - transaction.once('lost', function() { - receivedLost = true; - }); - transaction.once('error', function() { - assert(false, 'Should not err'); - }); - - transaction.addId(TX_STREAM_TRANSACTION.transaction.hash); - transactionManager.getPending().push(transaction); - - for (let i = 1; i <= 10; i++) { - rippledConnection.closeLedger(); - } - - setImmediate(function() { - assert(!receivedMissing); - assert(!receivedLost); - done(); - }); - }); - - it('Handle reconnect', function(done) { - const transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction); - - const binaryTx = lodash.extend({}, ACCOUNT_TX_TRANSACTION, { - ledger_index: ACCOUNT_TX_TRANSACTION.tx.ledger_index, - tx_blob: binary.encode(ACCOUNT_TX_TRANSACTION.tx), - meta: binary.encode(ACCOUNT_TX_TRANSACTION.meta) - }); - - const prefix = (0x54584E00).toString(16); - const hash = utils.sha512half(new Buffer(prefix + binaryTx.tx_blob, 'hex')); - - transaction.addId(hash); - - transaction.once('success', function(res) { - assert.strictEqual(res.engine_result, 'tesSUCCESS'); - done(); - }); - - transactionManager.getPending().push(transaction); - - rippled.once('request_account_tx', function(m, req) { - const response = lodash.extend({}, ACCOUNT_TX_RESPONSE); - response.result.transactions = [binaryTx]; - req.sendResponse(response, {id: m.id}); - }); - - remote.disconnect(remote.connect); - }); - - it('Handle reconnect -- no matching transaction found', function(done) { - const transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction); - - const binaryTx = lodash.extend({}, ACCOUNT_TX_TRANSACTION, { - ledger_index: ACCOUNT_TX_TRANSACTION.tx.ledger_index, - tx_blob: binary.encode(ACCOUNT_TX_TRANSACTION.tx), - meta: binary.encode(ACCOUNT_TX_TRANSACTION.meta) - }); - - transactionManager._request = function() { - // Resubmitting - done(); - }; - - transactionManager.getPending().push(transaction); - - rippled.once('request_account_tx', function(m, req) { - const response = lodash.extend({}, ACCOUNT_TX_RESPONSE); - response.result.transactions = [binaryTx]; - req.sendResponse(response, {id: m.id}); - }); - - remote.disconnect(remote.connect); - }); - - it('Handle reconnect -- account_tx error', function(done) { - const transaction = Transaction.from_json(TX_STREAM_TRANSACTION.transaction); - transactionManager.getPending().push(transaction); - - transactionManager._resubmit = function() { - assert(false, 'Should not resubmit'); - }; - - rippled.once('request_account_tx', function(m, req) { - req.sendResponse(ACCOUNT_TX_ERROR, {id: m.id}); - setImmediate(done); - }); - - remote.disconnect(remote.connect); - }); - - it('Submit transaction', function(done) { - const transaction = remote.createTransaction('AccountSet', { - account: ACCOUNT.address - }); - - let receivedInitialSuccess = false; - let receivedProposed = false; - transaction.once('proposed', function(m) { - assert.strictEqual(m.engine_result, 'tesSUCCESS'); - receivedProposed = true; - }); - transaction.once('submitted', function(m) { - assert.strictEqual(m.engine_result, 'tesSUCCESS'); - receivedInitialSuccess = true; - }); - - rippled.once('request_submit', function(m, req) { - 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}); - setImmediate(function() { - const txEvent = lodash.extend({}, TX_STREAM_TRANSACTION); - txEvent.transaction = transaction.tx_json; - txEvent.transaction.hash = transaction.hash(); - rippledConnection.sendJSON(txEvent); - }); - }); - - transaction.submit(function(err, res) { - assert(!err, 'Transaction submission should succeed'); - assert(receivedInitialSuccess); - assert(receivedProposed); - assert.strictEqual(res.engine_result, 'tesSUCCESS'); - assert.strictEqual(transactionManager.getPending().length(), 0); - done(); - }); - }); - - it('Submit transaction -- tec error', function(done) { - const transaction = remote.createTransaction('AccountSet', { - account: ACCOUNT.address, - set_flag: 'asfDisableMaster' - }); - - let receivedSubmitted = false; - transaction.once('proposed', function() { - assert(false, 'Should not receive proposed event'); - }); - transaction.once('submitted', function(m) { - assert.strictEqual(m.engine_result, 'tecNO_REGULAR_KEY'); - receivedSubmitted = true; - }); - - rippled.once('request_submit', function(m, req) { - 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}); - setImmediate(function() { - const txEvent = lodash.extend({}, TX_STREAM_TRANSACTION, - SUBMIT_TEC_RESPONSE.result); - txEvent.transaction = transaction.tx_json; - txEvent.transaction.hash = transaction.hash(); - rippledConnection.sendJSON(txEvent); - }); - }); - - transaction.submit(function(err) { - assert(err, 'Transaction submission should not succeed'); - assert(receivedSubmitted); - assert.strictEqual(err.engine_result, 'tecNO_REGULAR_KEY'); - assert.strictEqual(transactionManager.getPending().length(), 0); - done(); - }); - }); - - it('Submit transaction -- ter error', function(done) { - const transaction = remote.createTransaction('Payment', { - account: ACCOUNT.address, - destination: ACCOUNT2.address, - amount: '1' - }); - transaction.tx_json.Sequence = ACCOUNT_INFO_RESPONSE.result - .account_data.Sequence + 1; - - let receivedSubmitted = false; - transaction.once('proposed', function() { - assert(false, 'Should not receive proposed event'); - }); - transaction.once('submitted', function(m) { - assert.strictEqual(m.engine_result, SUBMIT_TER_RESPONSE.result.engine_result); - receivedSubmitted = true; - }); - - rippled.on('request_submit', function(m, req) { - const deserialized = binary.decode(m.tx_blob); - - switch (deserialized.TransactionType) { - case 'Payment': - assert.deepEqual(deserialized, transaction.tx_json); - assert.strictEqual(transactionManager.getPending().length(), 1); - req.sendResponse(SUBMIT_TER_RESPONSE, {id: m.id}); - break; - case 'AccountSet': - assert.strictEqual(deserialized.Account, ACCOUNT.address); - assert.strictEqual(deserialized.Flags, 2147483648); - assert.strictEqual(deserialized.Sequence, - ACCOUNT_INFO_RESPONSE.result.account_data.Sequence); - req.sendResponse(SUBMIT_RESPONSE, {id: m.id}); - req.closeLedger(); - break; - } - }); - /* eslint-disable no-unused-vars */ - rippled.once('request_submit', function(m, req) { - /* eslint-enable no-unused-vars */ - req.sendJSON(lodash.extend({}, LEDGER, { - ledger_index: transaction.tx_json.LastLedgerSequence + 1 - })); - }); - - transaction.submit(function(err) { - assert(err, 'Transaction submission should not succeed'); - assert.strictEqual(err.engine_result, 'tejMaxLedger'); - assert(receivedSubmitted); - assert.strictEqual(transactionManager.getPending().length(), 0); - assert.strictEqual(transactionManager.getPending().length(), 0); - - const summary = transaction.summary(); - assert.strictEqual(summary.submissionAttempts, 1); - assert.strictEqual(summary.submitIndex, 2); - assert.strictEqual(summary.initialSubmitIndex, 2); - assert.strictEqual(summary.lastLedgerSequence, 5); - assert.strictEqual(summary.state, 'failed'); - assert.strictEqual(summary.finalized, true); - assert.deepEqual(summary.result, { - engine_result: SUBMIT_TER_RESPONSE.result.engine_result, - engine_result_message: SUBMIT_TER_RESPONSE.result.engine_result_message, - ledger_hash: undefined, - ledger_index: undefined, - transaction_hash: SUBMIT_TER_RESPONSE.result.tx_json.hash - }); - transactionManager.once('sequence_filled', done); - }); - }); - - it('Submit transaction -- tef error', function(done) { - const transaction = remote.createTransaction('AccountSet', { - account: ACCOUNT.address - }); - - transaction.tx_json.Sequence = ACCOUNT_INFO_RESPONSE.result - .account_data.Sequence - 1; - - let receivedSubmitted = false; - let receivedResubmitted = false; - transaction.once('proposed', function() { - assert(false, 'Should not receive proposed event'); - }); - transaction.once('submitted', function(m) { - assert.strictEqual(m.engine_result, 'tefPAST_SEQ'); - receivedSubmitted = true; - }); - - rippled.on('request_submit', function(m, req) { - assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json)); - assert.strictEqual(transactionManager.getPending().length(), 1); - req.sendResponse(SUBMIT_TEF_RESPONSE, {id: m.id}); - }); - - /* eslint-disable no-unused-vars */ - rippled.once('request_submit', function(m, req) { - /* eslint-enable no-unused-vars */ - transaction.once('resubmitted', function() { - receivedResubmitted = true; - req.sendJSON(lodash.extend({}, LEDGER, { - ledger_index: transaction.tx_json.LastLedgerSequence + 1 - })); - }); - - req.closeLedger(); - }); - - transaction.submit(function(err) { - assert(err, 'Transaction submission should not succeed'); - assert(receivedSubmitted); - assert(receivedResubmitted); - assert.strictEqual(err.engine_result, 'tejMaxLedger'); - assert.strictEqual(transactionManager.getPending().length(), 0); - - const summary = transaction.summary(); - assert.strictEqual(summary.submissionAttempts, 2); - assert.strictEqual(summary.submitIndex, 3); - assert.strictEqual(summary.initialSubmitIndex, 2); - assert.strictEqual(summary.lastLedgerSequence, 5); - assert.strictEqual(summary.state, 'failed'); - assert.strictEqual(summary.finalized, true); - assert.deepEqual(summary.result, { - engine_result: SUBMIT_TEF_RESPONSE.result.engine_result, - engine_result_message: SUBMIT_TEF_RESPONSE.result.engine_result_message, - ledger_hash: undefined, - ledger_index: undefined, - transaction_hash: SUBMIT_TEF_RESPONSE.result.tx_json.hash - }); - done(); - }); - }); - - it('Submit transaction -- tel error', function(done) { - const transaction = remote.createTransaction('AccountSet', { - account: ACCOUNT.address - }); - - let receivedSubmitted = false; - let receivedResubmitted = false; - transaction.once('proposed', function() { - assert(false, 'Should not receive proposed event'); - }); - transaction.once('submitted', function(m) { - assert.strictEqual(m.engine_result, 'telINSUF_FEE_P'); - receivedSubmitted = true; - }); - - rippled.on('request_submit', function(m, req) { - 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}); - }); - - /* eslint-disable no-unused-vars */ - rippled.once('request_submit', function(m, req) { - /* eslint-enable no-unused-vars */ - transaction.once('resubmitted', function() { - receivedResubmitted = true; - req.sendJSON(lodash.extend({}, LEDGER, { - ledger_index: transaction.tx_json.LastLedgerSequence + 1 - })); - }); - - req.closeLedger(); - }); - - transaction.submit(function(err) { - assert(err, 'Transaction submission should not succeed'); - assert(receivedSubmitted); - assert(receivedResubmitted); - assert.strictEqual(err.engine_result, 'tejMaxLedger'); - assert.strictEqual(transactionManager.getPending().length(), 0); - - const summary = transaction.summary(); - assert.strictEqual(summary.submissionAttempts, 2); - assert.strictEqual(summary.submitIndex, 3); - assert.strictEqual(summary.initialSubmitIndex, 2); - assert.strictEqual(summary.lastLedgerSequence, 5); - assert.strictEqual(summary.state, 'failed'); - assert.strictEqual(summary.finalized, true); - assert.deepEqual(summary.result, { - engine_result: SUBMIT_TEL_RESPONSE.result.engine_result, - engine_result_message: SUBMIT_TEL_RESPONSE.result.engine_result_message, - ledger_hash: undefined, - ledger_index: undefined, - transaction_hash: SUBMIT_TEL_RESPONSE.result.tx_json.hash - }); - done(); - }); - }); - - it('Submit transaction -- invalid secret', function(done) { - remote.setSecret(ACCOUNT.address, ACCOUNT.secret + 'z'); - - const transaction = remote.createTransaction('AccountSet', { - account: ACCOUNT.address - }); - - rippled.once('request_submit', function() { - assert(false, 'Should not request submit'); - }); - - transaction.submit(function(err) { - assert.strictEqual(err.engine_result, 'tejSecretInvalid'); - assert.strictEqual(transactionManager.getPending().length(), 0); - - const summary = transaction.summary(); - assert.deepEqual(summary.tx_json, transaction.tx_json); - assert.strictEqual(summary.submissionAttempts, 0); - assert.strictEqual(summary.submitIndex, undefined); - assert.strictEqual(summary.initialSubmitIndex, undefined); - assert.strictEqual(summary.lastLedgerSequence, remote.getLedgerSequenceSync() + 1 + Remote.DEFAULTS.last_ledger_offset); - assert.strictEqual(summary.state, 'failed'); - assert.strictEqual(summary.finalized, true); - assert.deepEqual(summary.result, { - engine_result: 'tejSecretInvalid', - engine_result_message: 'Invalid secret', - ledger_hash: undefined, - ledger_index: undefined, - transaction_hash: undefined - }); - done(); - }); - }); - - it('Submit transaction -- remote error', function(done) { - const transaction = remote.createTransaction('Payment', { - account: ACCOUNT.address, - destination: ACCOUNT2.address, - amount: '1' - }); - - // MemoType must contain only valid URL characters (RFC 3986). This - // transaction is invalid - // transaction.addMemo('my memotype','my_memo_data'); - transaction.tx_json.Memos = [{ - Memo: { - MemoType: '6D79206D656D6F74797065', - MemoData: '6D795F6D656D6F5F64617461' - } - }]; - - let receivedSubmitted = false; - transaction.once('proposed', function() { - assert(false, 'Should not receive proposed event'); - }); - transaction.once('submitted', function(m) { - assert.strictEqual(m.error, 'remoteError'); - receivedSubmitted = true; - }); - - rippled.on('request_submit', function(m, req) { - 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); - - /* eslint-disable max-len */ - - // rippled returns an exception here rather than an engine result - // https://github.com/ripple/rippled/blob/c61d0c663e410c3d3622f20092535710243b55af/src/ripple/rpc/handlers/Submit.cpp#L66-L75 - - /* eslint-enable max-len */ - - req.sendResponse(SUBMIT_REMOTE_ERROR, {id: m.id}); - }); - - transaction.submit(function(err) { - assert(err, 'Transaction submission should not succeed'); - assert(receivedSubmitted); - assert.strictEqual(err.error, 'remoteError'); - assert.strictEqual(err.remote.error, 'invalidTransaction'); - assert.strictEqual(transactionManager.getPending().length(), 0); - - const summary = transaction.summary(); - assert.deepEqual(summary.tx_json, transaction.tx_json); - assert.strictEqual(summary.submissionAttempts, 1); - assert.strictEqual(summary.submitIndex, 2); - assert.strictEqual(summary.initialSubmitIndex, 2); - assert.strictEqual(summary.lastLedgerSequence, 5); - assert.strictEqual(summary.state, 'failed'); - assert.strictEqual(summary.finalized, true); - assert.deepEqual(summary.result, { - engine_result: undefined, - engine_result_message: undefined, - ledger_hash: undefined, - ledger_index: undefined, - transaction_hash: undefined - }); - done(); - }); - }); - - it('Submit transaction -- disabled resubmission', function(done) { - const transaction = remote.createTransaction('AccountSet', { - account: ACCOUNT.address - }); - - transaction.setResubmittable(false); - - let receivedSubmitted = false; - let receivedResubmitted = false; - transaction.once('proposed', function() { - assert(false, 'Should not receive proposed event'); - }); - transaction.once('submitted', function(m) { - assert.strictEqual(m.engine_result, 'telINSUF_FEE_P'); - receivedSubmitted = true; - }); - - rippled.on('request_submit', function(m, req) { - 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}); - }); - - /* eslint-disable no-unused-vars */ - rippled.once('request_submit', function(m, req) { - /* eslint-enable no-unused-vars */ - transaction.once('resubmitted', function() { - receivedResubmitted = true; - }); - - req.closeLedger(); - - setImmediate(function() { - req.sendJSON(lodash.extend({}, LEDGER, { - ledger_index: transaction.tx_json.LastLedgerSequence + 1 - })); - }); - }); - - transaction.submit(function(err) { - assert(err, 'Transaction submission should not succeed'); - assert(receivedSubmitted); - assert(!receivedResubmitted); - assert.strictEqual(err.engine_result, 'tejMaxLedger'); - assert.strictEqual(transactionManager.getPending().length(), 0); - - const summary = transaction.summary(); - assert.strictEqual(summary.submissionAttempts, 1); - assert.strictEqual(summary.submitIndex, 2); - assert.strictEqual(summary.initialSubmitIndex, 2); - assert.strictEqual(summary.lastLedgerSequence, 5); - assert.strictEqual(summary.state, 'failed'); - assert.strictEqual(summary.finalized, true); - assert.deepEqual(summary.result, { - engine_result: SUBMIT_TEL_RESPONSE.result.engine_result, - engine_result_message: SUBMIT_TEL_RESPONSE.result.engine_result_message, - ledger_hash: undefined, - ledger_index: undefined, - transaction_hash: SUBMIT_TEL_RESPONSE.result.tx_json.hash - }); - done(); - }); - }); - - it('Submit transaction -- disabled resubmission -- too busy error', function(done) { - // Transactions should always be resubmitted in the event of a 'tooBusy' - // rippled response, even with transaction resubmission disabled - - const transaction = remote.createTransaction('AccountSet', { - account: ACCOUNT.address - }); - - transaction.setResubmittable(false); - - let receivedSubmitted = false; - let receivedResubmitted = false; - transaction.once('proposed', function() { - assert(false, 'Should not receive proposed event'); - }); - transaction.once('submitted', function() { - receivedSubmitted = true; - }); - - /* eslint-disable no-unused-vars */ - rippled.on('request_submit', function(m, req) { - 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_TOO_BUSY_ERROR, {id: m.id}); - }); - - /* eslint-disable no-unused-vars */ - rippled.once('request_submit', function(m, req) { - /* eslint-enable no-unused-vars */ - transaction.once('resubmitted', function() { - receivedResubmitted = true; - req.sendJSON(lodash.extend({}, LEDGER, { - ledger_index: transaction.tx_json.LastLedgerSequence + 1 - })); - }); - - req.closeLedger(); - }); - - transaction.submit(function(err) { - assert(err, 'Transaction submission should not succeed'); - assert(receivedSubmitted); - assert(receivedResubmitted); - assert.strictEqual(err.engine_result, 'tejMaxLedger'); - assert.strictEqual(transactionManager.getPending().length(), 0); - - const summary = transaction.summary(); - assert.strictEqual(summary.submissionAttempts, 2); - assert.strictEqual(summary.submitIndex, 3); - assert.strictEqual(summary.initialSubmitIndex, 2); - assert.strictEqual(summary.lastLedgerSequence, 5); - assert.strictEqual(summary.state, 'failed'); - assert.strictEqual(summary.finalized, true); - assert.deepEqual(summary.result, { - engine_result: undefined, - engine_result_message: undefined, - ledger_hash: undefined, - ledger_index: undefined, - transaction_hash: undefined - }); - done(); - }); - }); -}); diff --git a/test/transaction-queue-test.js b/test/transaction-queue-test.js deleted file mode 100644 index 391c1e94..00000000 --- a/test/transaction-queue-test.js +++ /dev/null @@ -1,102 +0,0 @@ -'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() { - const queue = new TransactionQueue(); - const tx = new Transaction(); - - queue.push(tx); - - assert.strictEqual(queue.length(), 1); - }); - - it('Remove transaction', function() { - const queue = new TransactionQueue(); - const tx = new Transaction(); - - queue.push(tx); - queue.remove(tx); - - assert.strictEqual(queue.length(), 0); - }); - - it('Remove transaction by ID', function() { - const queue = new TransactionQueue(); - const tx = new Transaction(); - - queue.push(tx); - - tx.submittedIDs = [ - '1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B', - '2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B' - ]; - - queue.remove( - '3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'); - - assert.strictEqual(queue.length(), 1); - - queue.remove( - '2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'); - - assert.strictEqual(queue.length(), 0); - }); - - it('Add sequence', function() { - const queue = new TransactionQueue(); - queue.addReceivedSequence(1); - assert(queue.hasSequence(1)); - }); - - it('Add ID', function() { - 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() { - const queue = new TransactionQueue(); - const tx = new Transaction(); - - queue.push(tx); - - tx.submittedIDs = [ - '1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B', - '2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B' - ]; - - assert.strictEqual(queue.getSubmission( - '1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx); - assert.strictEqual(queue.getSubmission( - '2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx); - assert.strictEqual(queue.getSubmission( - '3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), - undefined); - }); - - it('Iterate over queue', function() { - const queue = new TransactionQueue(); - let count = 10; - - for (let i = 0; i < count; i++) { - queue.push(new Transaction()); - } - - queue.forEach(function(tx) { - assert(tx instanceof Transaction); - --count; - }); - - assert.strictEqual(count, 0); - }); -}); - diff --git a/test/transaction-test.js b/test/transaction-test.js deleted file mode 100644 index fe83fd3c..00000000 --- a/test/transaction-test.js +++ /dev/null @@ -1,2332 +0,0 @@ -/* eslint-disable max-len, max-nested-callbacks */ - -'use strict'; - -const assert = require('assert-diff'); -const lodash = require('lodash'); -const addresses = require('./fixtures/addresses'); -const Transaction = require('ripple-lib').Transaction; -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', - engine_result_code: 0, - engine_result_message: 'The transaction was applied.', - ledger_hash: '2031E311FD28A6B37697BD6ECF8E6661521902E7A6A8EF069A2F3C628E76A322', - ledger_index: 7106144, - status: 'closed', - type: 'transaction', - validated: true, - metadata: { - AffectedNodes: [ ], - TransactionIndex: 0, - TransactionResult: 'tesSUCCESS' - }, - tx_json: { - Account: 'rHPotLj3CNKaP4bQANcecEuT8hai3VpxfB', - Amount: '1000000', - Destination: 'rYtn3D1VGQyf1MTqcwLDepUKm22YEGXGJA', - Fee: '10', - Flags: 0, - LastLedgerSequence: 7106151, - Sequence: 2973, - SigningPubKey: '0306E9F38DF11402953A5B030C1AE8A88C47E348170C3B8EC6C8D775E797168462', - TransactionType: 'Payment', - TxnSignature: '3045022100A58B0460BC5092CB4F96155C19125A4E079C870663F1D5E8BBC9BDEE06D51F530220408A3AA26988ABF18E16BE77B016F25018A2AA7C99FFE723FC8598471357DBCF', - date: 455660500, - hash: '61D60378AB70ACE630B20A81B50708A3DB5E7CEE35914292FF3761913DA61DEA' - } -}; - -// https://github.com/ripple/rippled/blob/c61d0c663e410c3d3622f20092535710243b55af/src/ripple/protocol/impl/STTx.cpp#L342-L370 -const allowed_memo_chars = ('0123456789-._~:/?#[]@!$&\'()*+,;=%ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz').split(''); - -// Disallowed ASCII characters -const disallowed_memo_chars = []; - -for (let i = 0; i <= 127; i++) { - const char = String.fromCharCode(i); - if (!lodash.contains(allowed_memo_chars, char)) { - disallowed_memo_chars.push(char); - } -} - -describe('Transaction', function() { - it('Success listener', function(done) { - const transaction = new Transaction(); - - transaction.once('final', function(message) { - assert.deepEqual(message, transactionResult); - assert(transaction.finalized); - assert.strictEqual(transaction.state, 'validated'); - done(); - }); - - transaction.emit('success', transactionResult); - }); - - it('Error listener', function(done) { - const transaction = new Transaction(); - - transaction.once('final', function(message) { - assert.deepEqual(message, transactionResult); - assert(transaction.finalized); - assert.strictEqual(transaction.state, 'failed'); - done(); - }); - - transaction.emit('error', transactionResult); - }); - - it('Submitted listener', function() { - const transaction = new Transaction(); - transaction.emit('submitted'); - assert.strictEqual(transaction.state, 'submitted'); - }); - - it('Proposed listener', function() { - const transaction = new Transaction(); - transaction.emit('proposed'); - assert.strictEqual(transaction.state, 'pending'); - }); - - it('Check response code is tel', function() { - const transaction = new Transaction(); - assert(!transaction.isTelLocal(-400)); - assert(transaction.isTelLocal(-399)); - assert(transaction.isTelLocal(-300)); - assert(!transaction.isTelLocal(-299)); - }); - - it('Check response code is tem', function() { - const transaction = new Transaction(); - assert(!transaction.isTemMalformed(-300)); - assert(transaction.isTemMalformed(-299)); - assert(transaction.isTemMalformed(-200)); - assert(!transaction.isTemMalformed(-199)); - }); - - it('Check response code is tef', function() { - const transaction = new Transaction(); - assert(!transaction.isTefFailure(-200)); - assert(transaction.isTefFailure(-199)); - assert(transaction.isTefFailure(-100)); - assert(!transaction.isTefFailure(-99)); - }); - - it('Check response code is ter', function() { - const transaction = new Transaction(); - assert(!transaction.isTerRetry(-100)); - assert(transaction.isTerRetry(-99)); - assert(transaction.isTerRetry(-1)); - assert(!transaction.isTerRetry(0)); - }); - - it('Check response code is tep', function() { - const transaction = new Transaction(); - assert(!transaction.isTepSuccess(-1)); - assert(transaction.isTepSuccess(0)); - assert(transaction.isTepSuccess(1e3)); - }); - - it('Check response code is tec', function() { - const transaction = new Transaction(); - assert(!transaction.isTecClaimed(99)); - assert(transaction.isTecClaimed(100)); - assert(transaction.isTecClaimed(1e3)); - }); - - it('Check response code is rejected', function() { - const transaction = new Transaction(); - assert(!transaction.isRejected(0)); - assert(!transaction.isRejected(-99)); - assert(transaction.isRejected(-100)); - assert(transaction.isRejected(-399)); - assert(!transaction.isRejected(-400)); - }); - - it('Set state', function(done) { - const transaction = new Transaction(); - - assert.strictEqual(transaction.state, 'unsubmitted'); - - let receivedEvents = 0; - const events = [ - 'submitted', - 'pending', - 'validated' - ]; - - transaction.on('state', function(state) { - receivedEvents++; - assert(events.indexOf(state) > -1, 'Invalid state: ' + state); - }); - - transaction.setState(events[0]); - transaction.setState(events[1]); - transaction.setState(events[1]); - transaction.setState(events[2]); - - assert.strictEqual(receivedEvents, 3); - assert.strictEqual(transaction.state, events[2]); - done(); - }); - - it('Finalize submission', function() { - const transaction = new Transaction(); - - const tx = transactionResult; - tx.ledger_hash = '2031E311FD28A6BZ76Z7BD6ECF8E6661521902E7A6A8EF069A2F3C628E76A322'; - tx.ledger_index = 7106150; - - transaction.result = transactionResult; - transaction.finalize(tx); - - assert.strictEqual(transaction.result.ledger_index, tx.ledger_index); - assert.strictEqual(transaction.result.ledger_hash, tx.ledger_hash); - }); - - it('Finalize unsubmitted', function() { - const transaction = new Transaction(); - transaction.finalize(transactionResult); - - assert.strictEqual(transaction.result.ledger_index, transactionResult.ledger_index); - assert.strictEqual(transaction.result.ledger_hash, transactionResult.ledger_hash); - }); - - it('Get account secret', function() { - const remote = new Remote(); - - remote.secrets = { - rpzT237Ctpaa58KieifoK8RyBmmRwEcfhK: 'shY1njzHAXp8Qt3bpxYW6RpoZtMKP', - rpdxPs9CR93eLAc5DTvAgv4S9XJ1CzKj1a: 'ssboTJezioTq8obyvDU9tVo95NGGQ' - }; - - const transaction = new Transaction(remote); - - assert.strictEqual(transaction._accountSecret('rpzT237Ctpaa58KieifoK8RyBmmRwEcfhK'), 'shY1njzHAXp8Qt3bpxYW6RpoZtMKP'); - assert.strictEqual(transaction._accountSecret('rpdxPs9CR93eLAc5DTvAgv4S9XJ1CzKj1a'), 'ssboTJezioTq8obyvDU9tVo95NGGQ'); - assert.strictEqual(transaction._accountSecret('rExistNot'), undefined); - }); - - it('Get fee units', function() { - const remote = new Remote(); - const transaction = new Transaction(remote); - assert.strictEqual(transaction.feeUnits(), 10); - }); - - it('Compute fee', function() { - const remote = new Remote(); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = true; - - const s2 = new Server(remote, 'wss://s-west.ripple.com:443'); - s2._connected = true; - s2._load_factor = 256 * 4; - - const s3 = new Server(remote, 'wss://s-west.ripple.com:443'); - s3._connected = true; - s3._load_factor = 256 * 8; - - const s4 = new Server(remote, 'wss://s-west.ripple.com:443'); - s4._connected = true; - s4._load_factor = 256 * 8; - - const s5 = new Server(remote, 'wss://s-west.ripple.com:443'); - s5._connected = true; - s5._load_factor = 256 * 7; - - remote._servers = [s2, s3, s1, s4]; - - assert.strictEqual(s1._computeFee(10), '12'); - assert.strictEqual(s2._computeFee(10), '48'); - assert.strictEqual(s3._computeFee(10), '96'); - assert.strictEqual(s4._computeFee(10), '96'); - assert.strictEqual(s5._computeFee(10), '84'); - - const transaction = new Transaction(remote); - - assert.strictEqual(transaction._computeFee(), '72'); - }); - - it('Compute fee, no remote', function() { - const transaction = new Transaction(); - assert.strictEqual(transaction._computeFee(10), undefined); - }); - - it('Compute fee - no connected server', function() { - const remote = new Remote(); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = false; - - const s2 = new Server(remote, 'wss://s-west.ripple.com:443'); - s2._connected = false; - s2._load_factor = 256 * 4; - - const s3 = new Server(remote, 'wss://s-west.ripple.com:443'); - s3._connected = false; - s3._load_factor = 256 * 8; - - remote._servers = [s1, s2, s3]; - - assert.strictEqual(s1._computeFee(10), '12'); - assert.strictEqual(s2._computeFee(10), '48'); - assert.strictEqual(s3._computeFee(10), '96'); - - const transaction = new Transaction(remote); - - assert.strictEqual(transaction._computeFee(), undefined); - }); - - it('Compute fee - one connected server', function() { - const remote = new Remote(); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = false; - - const s2 = new Server(remote, 'wss://s-west.ripple.com:443'); - s2._connected = false; - s2._load_factor = 256 * 4; - - const s3 = new Server(remote, 'wss://s-west.ripple.com:443'); - s3._connected = true; - s3._load_factor = 256 * 8; - - remote._servers = [s1, s2, s3]; - - assert.strictEqual(s1._computeFee(10), '12'); - assert.strictEqual(s2._computeFee(10), '48'); - assert.strictEqual(s3._computeFee(10), '96'); - - const transaction = new Transaction(remote); - - assert.strictEqual(transaction._computeFee(), '96'); - }); - - it('Does not compute a median fee with floating point', function() { - const remote = new Remote(); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = true; - - const s2 = new Server(remote, 'wss://s-west.ripple.com:443'); - s2._connected = true; - s2._load_factor = 256 * 4; - - const s3 = new Server(remote, 'wss://s-west.ripple.com:443'); - s3._connected = true; - - s3._load_factor = (256 * 7) + 1; - - const s4 = new Server(remote, 'wss://s-west.ripple.com:443'); - s4._connected = true; - s4._load_factor = 256 * 16; - - remote._servers = [s1, s2, s3, s4]; - - assert.strictEqual(s1._computeFee(10), '12'); - assert.strictEqual(s2._computeFee(10), '48'); - // 66.5 - assert.strictEqual(s3._computeFee(10), '85'); - assert.strictEqual(s4._computeFee(10), '192'); - - const transaction = new Transaction(remote); - transaction.tx_json.Sequence = 1; - const src = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'; - const dst = 'rGihwhaqU8g7ahwAvTq6iX5rvsfcbgZw6v'; - - transaction.payment(src, dst, '100'); - remote.setSecret(src, addresses.SECRET); - - assert(transaction.complete()); - const json = transaction.serialize(); - assert.notStrictEqual(json.Fee, '66500000', 'Fee == 66500000, i.e. 66.5 XRP!'); - }); - - - it('Compute fee - even server count', function() { - const remote = new Remote(); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = true; - - const s2 = new Server(remote, 'wss://s-west.ripple.com:443'); - s2._connected = true; - s2._load_factor = 256 * 4; - - const s3 = new Server(remote, 'wss://s-west.ripple.com:443'); - s3._connected = true; - s3._load_factor = 256 * 8; - - const s4 = new Server(remote, 'wss://s-west.ripple.com:443'); - s4._connected = true; - s4._load_factor = 256 * 16; - - remote._servers = [s1, s2, s3, s4]; - - assert.strictEqual(s1._computeFee(10), '12'); - assert.strictEqual(s2._computeFee(10), '48'); - // 72 - assert.strictEqual(s3._computeFee(10), '96'); - assert.strictEqual(s4._computeFee(10), '192'); - - const transaction = new Transaction(remote); - - assert.strictEqual(transaction._computeFee(), '72'); - }); - - it('Complete transaction', function(done) { - const remote = new Remote(); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = true; - - remote._servers = [s1]; - remote.trusted = true; - remote.local_signing = true; - - const transaction = new Transaction(remote); - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; - transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; - transaction.tx_json.Flags = 0; - - assert(transaction.complete()); - - done(); - }); - - it('Complete transaction, local signing, no remote', function(done) { - const transaction = new Transaction(); - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; - transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; - - assert(transaction.complete()); - - done(); - }); - - it('Complete transaction - untrusted', function(done) { - const remote = new Remote(); - const transaction = new Transaction(remote); - - transaction.setSecret('shK5kH88MZPaCpCNmzmzpLY58xKS9'); - remote.trusted = false; - remote.local_signing = false; - - transaction.once('error', function(err) { - assert.strictEqual(err.result, 'tejServerUntrusted'); - done(); - }); - - assert(!transaction.complete()); - }); - - it('Complete transaction - no secret', function(done) { - const remote = new Remote(); - const transaction = new Transaction(remote); - - remote.trusted = true; - remote.local_signing = true; - - transaction.once('error', function(err) { - assert.strictEqual(err.result, 'tejSecretUnknown'); - done(); - }); - - assert(!transaction.complete()); - }); - - it('Complete transaction - invalid secret', function(done) { - const remote = new Remote(); - const transaction = new Transaction(remote); - - remote.trusted = true; - remote.local_signing = true; - - transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoijX'; - transaction.once('error', function(err) { - assert.strictEqual(err.result, 'tejSecretInvalid'); - done(); - }); - - assert(!transaction.complete()); - }); - - it('Complete transaction - cached SigningPubKey', function(done) { - const remote = new Remote(); - const transaction = new Transaction(remote); - - remote.trusted = true; - remote.local_signing = true; - - transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; - transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoijX'; - - transaction.once('error', function(err) { - assert.notStrictEqual(err.result, 'tejSecretInvalid'); - done(); - }); - - assert(!transaction.complete()); - }); - - it('Complete transaction - compute fee', function(done) { - const remote = new Remote(); - const transaction = new Transaction(remote); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = true; - - remote._servers = [s1]; - remote.trusted = true; - remote.local_signing = true; - - transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; - transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; - - assert.strictEqual(transaction.tx_json.Fee, undefined); - - assert(transaction.complete()); - - assert.strictEqual(transaction.tx_json.Fee, '12'); - - done(); - }); - - it('Complete transaction - compute fee exceeds max fee', function(done) { - const remote = new Remote({max_fee: 10}); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = true; - s1._load_factor = 256 * 16; - - remote._servers = [s1]; - remote.trusted = true; - remote.local_signing = true; - - const transaction = new Transaction(remote); - transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; - transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; - - transaction.once('error', function(err) { - assert.strictEqual(err.result, 'tejMaxFeeExceeded'); - done(); - }); - - assert(!transaction.complete()); - }); - - it('Complete transaction - canonical flags', function(done) { - const remote = new Remote(); - - const s1 = new Server(remote, 'wss://s-west.ripple.com:443'); - s1._connected = true; - s1._load_factor = 256; - - remote._servers = [s1]; - remote.trusted = true; - remote.local_signing = true; - - const transaction = new Transaction(remote); - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; - transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; - transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; - transaction.tx_json.Flags = 0; - - assert(transaction.complete()); - assert.strictEqual(transaction.tx_json.Flags, 2147483648); - - done(); - }); - - describe('ed25519 signing', function() { - it('can accept an ed25519 seed for ._secret', function() { - const expectedPub = 'EDD3993CDC6647896C455F136648B7750' + - '723B011475547AF60691AA3D7438E021D'; - - const expectedSig = 'C3646313B08EED6AF4392261A31B961F' + - '10C66CB733DB7F6CD9EAB079857834C8' + - 'B0334270A2C037E63CDCCC1932E08328' + - '82B7B7066ECD2FAEDEB4A83DF8AE6303'; - - const tx_json = { - Account: 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN', - Amount: '1000', - Destination: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', - Fee: '10', - Flags: 2147483648, - Sequence: 1, - TransactionType: 'Payment' - }; - - const tx = Transaction.from_json(tx_json); - tx.setSecret('sEd7rBGm5kxzauRTAV2hbsNz7N45X91'); - tx.complete(); - tx.sign(); - - assert.strictEqual(tx.tx_json.SigningPubKey, expectedPub); - assert.strictEqual(tx.tx_json.TxnSignature, expectedSig); - }); - }); - - describe('signing', function() { - const tx_json = { - SigningPubKey: '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED', - Account: 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ', - Flags: 0, - Fee: '10', - Sequence: 1, - TransactionType: 'AccountSet' - }; - - const expectedSigningHash = - 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'; - - it('Get signing hash', function() { - const transaction = Transaction.from_json(tx_json); - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; - assert.strictEqual(transaction.signingHash(), expectedSigningHash); - }); - - it('Get signing data', function() { - const tx = Transaction.from_json(tx_json); - const data = tx.signingData(); - - assert.strictEqual(tx.signingHash(), expectedSigningHash); - - assert.strictEqual(data, - ('535458001200032200000000240000000168400000000000000' + - 'A7321021FED5FD081CE5C4356431267D04C6E2167E4112C897D' + - '5E10335D4E22B4DA49ED8114E0E6E281CA324AEE034B2BB8AC9' + - '7BA1ACA95A068')); - }); - }); - - it('Get hash - no 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.strictEqual(transaction.hash(), '1A860FC46D1DD9200560C64002418A4E8BBDE939957AC82D7B14D80A1C0E2EB5'); - - done(); - }); - - it('Get hash - 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.strictEqual(transaction.signingHash(), - 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); - - done(); - }); - - it('Get hash - complex transaction', function() { - const input_json = { - Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', - Amount: { - currency: 'LTC', - issuer: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', - value: '9.985' - }, - Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', - Fee: '15', - Flags: 0, - Paths: [ - [ - { - account: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', - currency: 'USD', - issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', - type: 49, - type_hex: '0000000000000031' - }, - { - currency: 'LTC', - issuer: 'rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX', - type: 48, - type_hex: '0000000000000030' - }, - { - account: 'rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX', - currency: 'LTC', - issuer: 'rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX', - type: 49, - type_hex: '0000000000000031' - } - ] - ], - SendMax: { - currency: 'USD', - issuer: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', - value: '30.30993068' - }, - Sequence: 415, - SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A', - TransactionType: 'Payment', - TxnSignature: '304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E' - }; - - const expected_hash = '87366146D381AD971B97DD41CFAC1AE4670B0E996AB574B0CE18CE6467811868'; - const transaction = Transaction.from_json(input_json); - - assert.deepEqual(transaction.hash(), expected_hash); - }); - - it('Get hash - complex transaction including Memo', function() { - const input_json = { - Account: 'rfe8yiZUymRPx35BEwGjhfkaLmgNsTytxT', - Amount: '1', - Destination: 'r9kiSEUEw6iSCNksDVKf9k3AyxjW3r1qPf', - Fee: '10', - Flags: 2147483648, - Memos: [ - { - Memo: { - MemoData: '61BAF845AF6D4FB1AC08A58D817261457034F1431FB9997B3460EB355DCA98D9382181A0E0125B4B0D6DAAF9A460D09C9EFDEFB2BE49E545036028A04DDFCFE8CBDD03DA844EEF9235B708574319A0F1186ADA054D2A4E970E73C67BE3232662726CD59C53CA2EF1DC0939C4793B1794932832D08B9B830AA917BB7CADA5B76C93DC5E0C928B93D8C336D6722E4757332A61DEB7E2A601E8D3766D5285A26A8DFAFFFDDED8BD49D471B9D885F3DF0CC031D522197BC248A5E2BEFAB12BC4A8D77BAB1555C8B38C26254C7BE8563D97EDD806EB7BE3872C750F28B41F693CB179849E6E2105B627F63D390FDDEFE863D3E7C28D6465AA158E7D96920E0EEF0BCB7993EC652C97F876F1666DBA9DA0A612FF9FE0A00AF03B2D5DADFC71984CBC93CA5EAE4C50BDDC839C1C6C3EEAAB44E8493BB7940C0C9ACA0BFEC2999DF5109A3EC40E62280E252712CC91476FB45E40EE314A26F3259027349E47FDD1C21A8DBDF58635943A13B7E2690B4CD153E2FA147A035E979BBDD814635BAB79683D7F62A7D7FF433F9DD35D0967F591D6D3776FC8ADF53E04EAB1DBC5863CBB85FC6F8E5C8B75037DEA9FEEB6A4D5FDF0AEE3F1BDD42EB1B976E98784A15C851E4F3B6234BAFFD11204CB2B76A3CBAA02E3B21051FAF012504DF33CAF9567A333DCE2BB5F454D4BA4B319DF43ECBC86DE214A712A4E214E874092DC84E05B', - MemoType: 'D723898B3DB828F061BCE8DA8F3068B31E527CBDB4D0D22F3D4F5F2C6A961A84EF1189E9CBE2741FEC5C7A46011316D6F9A7769C8E8157E5209FED2D3F950E2763BEF07254327B0EDE9C3CFEB248997EDF148BA36E9D1167C87D73FE9F047FF167DF37B0EF30ABF8E5FD0DCC7E7B964EA0E60E8B2C27E2C7C214BD8334CE830B66BBD724172F7ADCEF491B9B495A979819944DF8EB3A13E4F03B2A8D6FFF332E5C9980A540BEDF659DFDDA03EC78F4F0279F90C8BFD494C15708197C2BAE5CA661EC75FB6E6097E7C5435F374332B7A066FBBC14C629E8EE6042A64226B075B9309BF5FE227CEEEB9CEC7A6B79E724BFBA2BA706F28EC9F3702FB3AFB4C74F0411C7EFDE7927D1ED0670C4F426B8C40F09EB715713788902A4374D8CC7E5111BCA39A97D1F9BD3BD56E28E6167E4DC97A7DEF5428B809D03AE72CB5BA1D25DE3523BC182E3B8905666A972A949B20C30C4FBD1D0A2D9AD8E46EFDBE4E46F4E340FE39F4AD315F5D9EBF7ED9BDC6D577375B56E0CD9FAF0BFA02453F90290E0962D6362048F737BEE3E0E1C46ECE61CCAF4C317B4135B2C5B5D5C4EE728002B2116BB1AF21903AB3F2E4E1A4FE4C5D76507C71C50670281DAB334C37503FB851FC25EC85C757976450004EC642E217D7F4B2E4B6DD820B5E3968B79CB9D7706F28714003C63F4B89AD1B6208A56DF5AFF02E5327A8EAA532BD3ED1ED0' - } - } - ], - Sequence: 74519, - SigningPubKey: '02BA5A9F27C34830542D7AA460B82D68AB34B410470108EE2DFFD9D280B49DB161', - TransactionType: 'Payment', - TxnSignature: '3045022100EA99CD20B47AB1C7AEF348102B515DB2FA26F1C9E7DC8FCAE72A763CEC37F21102206190F16F509A088E6303ACB66E9A6C1B9886C20B23F55C3B0721F2B97DC0926E', - date: 455562850, - hash: '4907745B5254B1093E037BA3250B95CFAF35C11CC2CA5E538A68FCE39D16F402', - inLedger: 7085699, - ledger_index: 7085699, - meta: { - AffectedNodes: [ - { - ModifiedNode: { - FinalFields: { - Account: 'r9kiSEUEw6iSCNksDVKf9k3AyxjW3r1qPf', - Balance: '40900380802', - Flags: 0, - OwnerCount: 6, - Sequence: 98 - }, - LedgerEntryType: 'AccountRoot', - LedgerIndex: '0EB37649FDF753A78DACB689057E827296F47AB7D04A7CB273C971A207E17960', - PreviousFields: { - Balance: '40900380801' - }, - PreviousTxnID: '42C003842B5188E860B087DD509880C82263B31DE3DD6B5E4AC89AF541CF486E', - PreviousTxnLgrSeq: 5088491 - } - }, - { - ModifiedNode: { - FinalFields: { - Account: 'rfe8yiZUymRPx35BEwGjhfkaLmgNsTytxT', - Balance: '35339588', - Domain: '9888A596C489373CF5C40858A66C8922C54A2BE9521E39CFFD0EF7A9906DAF298CDA4ADA16111A020C8B940CBABC4D39406381E0DE791147FAD0A5729F3546BF47D515FCDC80A85A52701CF9120C64DD6D41D0CF3DF2B56AEF6DBB463BB69F153BEEAC31D5300B8A3AE558122E192D7211DC0E4F547AD96B2E2F30F46AF8B5D76A58A75D764BA6FDC8E748EEC7A29C2F2A71784B8141D1A9E66544FF7C07025827C3BBCD66FA121D5E50407A622C803B33FCFBA4B2A7454BF86C32628DE0259EC0014783871BD3ADAF2E9F4E0FA421A68AFE1EF3ADEDD9CB24E783D284666BA8ABC2428F77D5550BE76751AA500A90E648CF7524CFC8E8785CB1ACBFB5F0AA50', - EmailHash: 'A9527827303A62DA5DB83FE47ACC1B62', - Flags: 1048576, - OwnerCount: 0, - RegularKey: 'rDLNuE5hfxbRzuXaNn7iUQBftoKfYQQtFA', - Sequence: 74520, - WalletLocator: 'D091F672A5A6FCE5AD3CD35BDD7E6FA15D4205D22E5EB36CBFB961D6E355EF9A' - }, - LedgerEntryType: 'AccountRoot', - LedgerIndex: 'A85F8FD83E579C50AF595482824E2AC8C747E4673E83537E8CACCE595AA0C590', - PreviousFields: { - Balance: '35339599', - Sequence: 74519 - }, - PreviousTxnID: '3CB97C94E76F3A26C083DCA702E91446EB51D4DE62B25D8B7BA10F24FDA2F4E2', - PreviousTxnLgrSeq: 7085699 - } - } - ], - TransactionIndex: 13, - TransactionResult: 'tesSUCCESS', - delivered_amount: '1' - }, - validated: true - }; - - assert.deepEqual(Transaction.from_json(input_json).hash(), input_json.hash); - }); - - it('Serialize transaction', function() { - const input_json = { - Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', - Amount: { - currency: 'LTC', - issuer: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', - value: '9.985' - }, - Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', - Fee: '15', - Flags: 0, - Paths: [ - [ - { - account: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', - currency: 'USD', - issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', - type: 49, - type_hex: '0000000000000031' - }, - { - currency: 'LTC', - issuer: 'rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX', - type: 48, - type_hex: '0000000000000030' - }, - { - account: 'rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX', - currency: 'LTC', - issuer: 'rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX', - type: 49, - type_hex: '0000000000000031' - } - ] - ], - SendMax: { - currency: 'USD', - issuer: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS', - value: '30.30993068' - }, - Sequence: 415, - SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A', - TransactionType: 'Payment', - TxnSignature: '304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E' - }; - - const expected_hex = '1200002200000000240000019F61D4A3794DFA1510000000000000000000000000004C54430000000000EF7ED76B77750D79EC92A59389952E0E8054407668400000000000000F69D4CAC4AC112283000000000000000000000000005553440000000000EF7ED76B77750D79EC92A59389952E0E80544076732102854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A7448304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E8114EF7ED76B77750D79EC92A59389952E0E805440768314EF7ED76B77750D79EC92A59389952E0E80544076011231DD39C650A96EDA48334E70CC4A85B8B2E8502CD30000000000000000000000005553440000000000DD39C650A96EDA48334E70CC4A85B8B2E8502CD3300000000000000000000000004C5443000000000047DA9E2E00ECF224A52329793F1BB20FB1B5EA643147DA9E2E00ECF224A52329793F1BB20FB1B5EA640000000000000000000000004C5443000000000047DA9E2E00ECF224A52329793F1BB20FB1B5EA6400'; - - const transaction = Transaction.from_json(input_json); - - assert.deepEqual(transaction.serialize(), expected_hex); - }); - - it('Sign transaction', 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'; - - transaction.sign(); - - const signature = transaction.tx_json.TxnSignature; - - assert.strictEqual(transaction.previousSigningHash, 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); - assert(/^[A-Z0-9]+$/.test(signature)); - - // Unchanged transaction, signature should be the same - transaction.sign(); - - assert.strictEqual(transaction.previousSigningHash, 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); - assert(/^[A-Z0-9]+$/.test(signature)); - assert.strictEqual(transaction.tx_json.TxnSignature, signature); - - done(); - }); - - it('Add transaction ID', function(done) { - const transaction = new Transaction(); - - transaction.addId('D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); - transaction.addId('F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); - transaction.addId('F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); - - assert.deepEqual( - transaction.submittedIDs, - ['F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE', - 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'] - ); - - done(); - }); - - it('Find transaction IDs in cache', function(done) { - const transaction = new Transaction(); - - assert.deepEqual(transaction.findId({ - F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE: transaction - }), undefined); - - transaction.addId('F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); - - assert.deepEqual(transaction.findId({ - F1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE: transaction - }), transaction); - - assert.strictEqual(transaction.findId({ - Z1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE: transaction - }), undefined); - - done(); - }); - - it('Set build_path', function() { - const transaction = new Transaction(); - -// assert.throws(function() { -// transaction.buildPath(); -// }, /Error: TransactionType must be Payment to use build_path/); -// assert.throws(function() { -// transaction.setBuildPath(); -// }, /Error: TransactionType must be Payment to use build_path/); - - assert.strictEqual(transaction._build_path, false); - - transaction.setType('Payment'); - transaction.setBuildPath(); - assert.strictEqual(transaction._build_path, true); - transaction.setBuildPath(false); - assert.strictEqual(transaction._build_path, false); - }); - - it('Set DestinationTag', function() { - const transaction = new Transaction(); - -// assert.throws(function() { -// transaction.destinationTag(1); -// }, /Error: TransactionType must be Payment to use DestinationTag/); -// assert.throws(function() { -// transaction.setDestinationTag(1); -// }, /Error: TransactionType must be Payment to use DestinationTag/); - - assert.strictEqual(transaction.tx_json.DestinationTag, undefined); - - transaction.setType('Payment'); - - assert.throws(function() { - transaction.setDestinationTag('tag'); - }, /Error: DestinationTag must be a valid UInt32/); - - transaction.setDestinationTag(1); - assert.strictEqual(transaction.tx_json.DestinationTag, 1); - }); - - it('Set InvoiceID', function() { - const transaction = new Transaction(); - -// assert.throws(function() { -// transaction.invoiceID('DEADBEEF'); -// }, /Error: TransactionType must be Payment to use InvoiceID/); -// assert.strictEqual(transaction.tx_json.InvoiceID, undefined); - - transaction.setType('Payment'); - - assert.throws(function() { - transaction.setInvoiceID(1); - }, /Error: InvoiceID must be a valid Hash256/); - - assert.strictEqual(transaction.tx_json.InvoiceID, undefined); - - transaction.setType('Payment'); - - transaction.setInvoiceID('DEADBEEF'); - assert.strictEqual(transaction.tx_json.InvoiceID, 'DEADBEEF00000000000000000000000000000000000000000000000000000000'); - - transaction.setInvoiceID('FEADBEEF00000000000000000000000000000000000000000000000000000000'); - assert.strictEqual(transaction.tx_json.InvoiceID, 'FEADBEEF00000000000000000000000000000000000000000000000000000000'); - }); - - it('Set ClientID', function() { - const transaction = new Transaction(); - - transaction.clientID(1); - assert.strictEqual(transaction._clientID, undefined); - - transaction.clientID('DEADBEEF'); - assert.strictEqual(transaction._clientID, 'DEADBEEF'); - }); - - it('Set LastLedgerSequence', function() { - const transaction = new Transaction(); - - assert.throws(function() { - transaction.setLastLedgerSequence('a'); - }, /Error: LastLedgerSequence must be a valid UInt32/); - assert.throws(function() { - transaction.setLastLedgerSequence(NaN); - }, /Error: LastLedgerSequence must be a valid UInt32/); - - assert.strictEqual(transaction.tx_json.LastLedgerSequence, undefined); - assert(!transaction._setLastLedger); - - transaction.setLastLedgerSequence(12); - assert.strictEqual(transaction.tx_json.LastLedgerSequence, 12); - assert(transaction._setLastLedger); - }); - - it('Set Max Fee', function() { - const transaction = new Transaction(); - - transaction.maxFee('a'); - assert(!transaction._setMaxFee); - - transaction.maxFee(NaN); - assert(!transaction._setMaxFee); - - transaction.maxFee(1000); - assert.strictEqual(transaction._maxFee, 1000); - assert.strictEqual(transaction._setMaxFee, true); - }); - - it('Set Fixed Fee', function() { - const transaction = new Transaction(); - - transaction.setFixedFee('a'); - assert(!transaction._setFixedFee); - - transaction.setFixedFee(-1000); - assert(!transaction._setFixedFee); - - transaction.setFixedFee(NaN); - assert(!transaction._setFixedFee); - - transaction.setFixedFee(1000); - assert.strictEqual(transaction._setFixedFee, true); - assert.strictEqual(transaction.tx_json.Fee, '1000'); - }); - - it('Set resubmittable', function() { - const tx = new Transaction(); - - assert.strictEqual(tx.isResubmittable(), true); - - tx.setResubmittable(false); - assert.strictEqual(tx.isResubmittable(), false); - - tx.setResubmittable(true); - assert.strictEqual(tx.isResubmittable(), true); - }); - - it('Rewrite transaction path', function() { - const path = [ - { - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - extraneous_property: 1, - currency: 'USD' - }, - { - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - type_hex: '0000000000000001' - }, - { - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - test: 'XRP', - currency: 'USD' - } - ]; - - assert.deepEqual(Transaction._rewritePath(path), [ - { - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - currency: 'USD' - }, - { - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - type_hex: '0000000000000001' - }, - { - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - currency: 'USD' - } - ]); - }); - - it('Rewrite transaction path - invalid path', function() { - assert.throws(function() { - assert.strictEqual(Transaction._rewritePath(1), undefined); - }); - }); - - it('Add transaction path', function() { - const transaction = new Transaction(); - - const path = [ - { - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - extraneous_property: 1, - currency: 'USD' - } - ]; - - const path2 = [ - { - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - test: 'XRP', - currency: 'USD' - } - ]; - -// assert.throws(function() { -// transaction.pathAdd(path); -// }, /^Error: TransactionType must be Payment to use Paths$/); -// assert.throws(function() { -// transaction.addPath(path); -// }, /^Error: TransactionType must be Payment to use Paths$/); - - assert.strictEqual(transaction.tx_json.Paths, undefined); - - transaction.setType('Payment'); - - assert.throws(function() { - transaction.pathAdd(1); - }, /^Error: Path must be an array$/); - assert.throws(function() { - transaction.addPath(1); - }, /^Error: Path must be an array$/); - - transaction.addPath(path); - - assert.deepEqual(transaction.tx_json.Paths, [ - [{ - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - currency: 'USD' - }] - ]); - - transaction.addPath(path2); - - assert.deepEqual(transaction.tx_json.Paths, [ - [{ - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - currency: 'USD' - }], - [{ - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - currency: 'USD' - }] - ]); - }); - - it('Add transaction paths', function() { - const transaction = new Transaction(); - - const paths = [ - [{ - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - test: 1, - currency: 'USD' - }], - [{ - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - test: 2, - currency: 'USD' - }] - ]; - -// assert.throws(function() { -// transaction.paths(paths); -// }, /Error: TransactionType must be Payment to use Paths/); -// assert.throws(function() { -// transaction.setPaths(paths); -// }, /Error: TransactionType must be Payment to use Paths/); - - assert.strictEqual(transaction.tx_json.Paths, undefined); - - transaction.setType('Payment'); - - assert.throws(function() { - transaction.paths(1); - }, /Error: Paths must be an array/); - assert.throws(function() { - transaction.setPaths(1); - }, /Error: Paths must be an array/); - - transaction.setPaths(paths); - transaction.setPaths(paths); - - assert.deepEqual(transaction.tx_json.Paths, [ - [{ - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - currency: 'USD' - }], - [{ - account: 'rP51ycDJw5ZhgvdKiRjBYZKYjsyoCcHmnY', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - currency: 'USD' - }] - ]); - }); - - it('Does not add empty transaction paths', function() { - const transaction = new Transaction(); - - const paths = []; - - assert.strictEqual(transaction.tx_json.Paths, undefined); - - transaction.setType('Payment'); - - assert.throws(function() { - transaction.paths(1); - }, /Error: Paths must be an array/); - assert.throws(function() { - transaction.setPaths(1); - }, /Error: Paths must be an array/); - - transaction.setPaths(paths); - - assert.strictEqual(transaction.tx_json.Paths, undefined); - }); - - it('Set secret', function() { - const transaction = new Transaction(); - transaction.secret('shHXjwp9m3MDQNcUrTekXcdzFsCjM'); - assert.strictEqual(transaction._secret, 'shHXjwp9m3MDQNcUrTekXcdzFsCjM'); - }); - - it('Set SendMax', function() { - const transaction = new Transaction(); - -// assert.throws(function() { -// transaction.sendMax('1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'); -// }, /^Error: TransactionType must be Payment to use SendMax$/); -// assert.throws(function() { -// transaction.setSendMax('1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'); -// }, /^Error: TransactionType must be Payment to use SendMax$/); - - transaction.setType('Payment'); - transaction.setSendMax('1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'); - - assert.deepEqual(transaction.tx_json.SendMax, { - value: '1', - currency: 'USD', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }); - }); - - it('Set SourceTag', function() { - const transaction = new Transaction(); - - assert.throws(function() { - transaction.sourceTag('tag'); - }, /Error: SourceTag must be a valid UInt32/); - - assert.strictEqual(transaction.tx_json.SourceTag, undefined); - transaction.sourceTag(1); - assert.strictEqual(transaction.tx_json.SourceTag, 1); - transaction.setSourceTag(2); - assert.strictEqual(transaction.tx_json.SourceTag, 2); - }); - - it('Set TransferRate', function() { - const transaction = new Transaction(); - transaction.setType('AccountSet'); - - assert.throws(function() { - transaction.transferRate(1); - }, /^Error: TransferRate must be >= 1000000000$/); - assert.throws(function() { - transaction.setTransferRate(1); - }, /^Error: TransferRate must be >= 1000000000$/); - assert.throws(function() { - transaction.setTransferRate('a'); - }, /^Error: TransferRate must be a valid UInt32$/); - - assert.strictEqual(transaction.tx_json.TransferRate, undefined); - transaction.setTransferRate(1.5 * 1e9); - assert.strictEqual(transaction.tx_json.TransferRate, 1.5 * 1e9); - transaction.setTransferRate(0); - assert.strictEqual(transaction.tx_json.TransferRate, 0); - }); - - it('Set Flags', function(done) { - const transaction = new Transaction(); - transaction.tx_json.TransactionType = 'Payment'; - transaction.setFlags(); - assert.strictEqual(transaction.tx_json.Flags, 0); - - const transaction2 = new Transaction(); - transaction2.tx_json.TransactionType = 'Payment'; - transaction2.setFlags(Transaction.flags.Payment.PartialPayment); - assert.strictEqual(transaction2.tx_json.Flags, 131072); - - const transaction3 = new Transaction(); - transaction3.tx_json.TransactionType = 'Payment'; - transaction3.setFlags('NoRippleDirect'); - assert.strictEqual(transaction3.tx_json.Flags, 65536); - - const transaction4 = new Transaction(); - transaction4.tx_json.TransactionType = 'Payment'; - transaction4.setFlags('PartialPayment', 'NoRippleDirect'); - assert.strictEqual(transaction4.tx_json.Flags, 196608); - - const transaction5 = new Transaction(); - transaction5.setType('Payment'); - transaction5.setFlags(['LimitQuality', 'PartialPayment']); - assert.strictEqual(transaction5.tx_json.Flags, 393216); - - const transaction6 = new Transaction(); - transaction6.tx_json.TransactionType = 'Payment'; - transaction6.once('error', function(err) { - assert.strictEqual(err.result, 'tejInvalidFlag'); - done(); - }); - transaction6.setFlags('asdf'); - }); - - it('Add Memo', function() { - const transaction = new Transaction(); - transaction.tx_json.TransactionType = 'Payment'; - - const memoType = 'message'; - const memoFormat = 'json'; - const memoData = { - string: 'value', - bool: true, - integer: 1 - }; - - transaction.addMemo(memoType, memoFormat, memoData); - - const expected = [ - { - Memo: - { - MemoType: '6D657373616765', - MemoFormat: '6A736F6E', - MemoData: '7B22737472696E67223A2276616C7565222C22626F6F6C223A747275652C22696E7465676572223A317D' - } - } - ]; - - assert.deepEqual(transaction.tx_json.Memos, expected); - - allowed_memo_chars.forEach(function(c) { - const hexStr = new Buffer(c).toString('hex').toUpperCase(); - const tx = new Transaction(); - - tx.addMemo(c, c, c); - - assert.deepEqual(tx.tx_json.Memos, [{ - Memo: { - MemoType: hexStr, - MemoFormat: hexStr, - MemoData: hexStr - } - }]); - }); - }); - - it('Add Memo - by object', function() { - const transaction = new Transaction(); - transaction.setType('Payment'); - - const memo = { - memoType: 'type', - memoData: 'data' - }; - - transaction.addMemo(memo); - - const expected = [ - { - Memo: { - MemoType: '74797065', - MemoData: '64617461' - } - } - ]; - - assert.deepEqual(transaction.tx_json.Memos, expected); - }); - - it('Add Memos', function() { - const transaction = new Transaction(); - transaction.tx_json.TransactionType = 'Payment'; - - transaction.addMemo('testkey', undefined, 'testvalue'); - transaction.addMemo('testkey2', undefined, 'testvalue2'); - transaction.addMemo('testkey3', 'text/html'); - transaction.addMemo(undefined, undefined, 'testvalue4'); - transaction.addMemo('testkey4', 'text/html', ''); - - const expected = [ - { - Memo: { - MemoType: '746573746B6579', - MemoData: '7465737476616C7565' - } - }, - { - Memo: { - MemoType: '746573746B657932', - MemoData: '7465737476616C756532' - } - }, - { - Memo: { - MemoType: '746573746B657933', - MemoFormat: '746578742F68746D6C' - } - }, - { - Memo: { - MemoData: '7465737476616C756534' - } - }, - { - Memo: { - MemoType: '746573746B657934', - MemoFormat: '746578742F68746D6C', - MemoData: '3C68746D6C3E' - } - } - ]; - - assert.deepEqual(transaction.tx_json.Memos, expected); - }); - - it('Add Memo - invalid MemoType', function() { - const transaction = new Transaction(); - transaction.tx_json.TransactionType = 'Payment'; - - const error_regex = /^Error: MemoType must be a string containing only valid URL characters$/; - - assert.throws(function() { - transaction.addMemo(1); - }, error_regex); - assert.throws(function() { - transaction.addMemo('한국어'); - }, error_regex); - assert.throws(function() { - transaction.addMemo('my memo'); - }, error_regex); - - disallowed_memo_chars.forEach(function(c) { - assert.throws(function() { - transaction.addMemo(c); - }, error_regex); - }); - }); - - it('Add Memo - invalid MemoFormat', function() { - const transaction = new Transaction(); - transaction.tx_json.TransactionType = 'Payment'; - - const error_regex = /^Error: MemoFormat must be a string containing only valid URL characters$/; - - assert.throws(function() { - transaction.addMemo(undefined, 1); - }, error_regex); - assert.throws(function() { - transaction.addMemo(undefined, 'России'); - }, error_regex); - assert.throws(function() { - transaction.addMemo(undefined, 'my memo'); - }, error_regex); - - disallowed_memo_chars.forEach(function(c) { - assert.throws(function() { - transaction.addMemo(undefined, c); - }, error_regex); - }); - }); - - it('Add Memo - MemoData string', function() { - const transaction = new Transaction(); - transaction.tx_json.TransactionType = 'Payment'; - - transaction.addMemo({memoData: 'some_string'}); - - assert.deepEqual(transaction.tx_json.Memos, [ - { - Memo: { - MemoData: '736F6D655F737472696E67' - } - } - ]); - }); - - it('Add Memo - MemoData complex object', function() { - const transaction = new Transaction(); - transaction.tx_json.TransactionType = 'Payment'; - - const memo = { - memoFormat: 'json', - memoData: { - string: 'string', - int: 1, - array: [ - { - string: 'string' - } - ], - object: { - string: 'string' - } - } - }; - - transaction.addMemo(memo); - - assert.deepEqual(transaction.tx_json.Memos, [ - { - Memo: { - MemoFormat: '6A736F6E', - MemoData: '7B22737472696E67223A22737472696E67222C22696E74223A312C226172726179223A5B7B22737472696E67223A22737472696E67227D5D2C226F626A656374223A7B22737472696E67223A22737472696E67227D7D' - } - } - ]); - }); - - it('Set AccountTxnID', function() { - const transaction = new Transaction(); - assert(transaction instanceof Transaction); - - transaction.accountTxnID('75C5A92212AA82A89C3824F6F071FE49C95C45DE9113EB51763A217DBACB5B4F'); - - assert.deepEqual(transaction.tx_json, { - Flags: 0, - AccountTxnID: '75C5A92212AA82A89C3824F6F071FE49C95C45DE9113EB51763A217DBACB5B4F' - }); - }); - - it('Construct AccountSet transaction - with setFlag, clearFlag', function() { - const transaction = new Transaction().accountSet('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'asfRequireDest', 'asfRequireAuth'); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - SetFlag: 1, - ClearFlag: 2, - TransactionType: 'AccountSet', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }); - }); - - it('Construct AccountSet transaction - with AccountTxnID SetFlag', function() { - const transaction = new Transaction().accountSet('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'asfAccountTxnID'); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - SetFlag: 5, - TransactionType: 'AccountSet', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }); - }); - - it('Construct AccountSet transaction - params object', function() { - const transaction = new Transaction().accountSet({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - set: 'asfRequireDest', - clear: 'asfRequireAuth' - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - SetFlag: 1, - ClearFlag: 2, - TransactionType: 'AccountSet', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }); - }); - - it('Construct AccountSet transaction - invalid account', function() { - assert.throws(function() { - new Transaction().accountSet('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'); - }); - }); - - it('Construct OfferCancel transaction', function() { - const transaction = new Transaction().offerCancel('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 1); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'OfferCancel', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - OfferSequence: 1 - }); - }); - - it('Construct OfferCancel transaction - params object', function() { - const transaction = new Transaction().offerCancel({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - sequence: 1 - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'OfferCancel', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - OfferSequence: 1 - }); - }); - - it('Construct OfferCancel transaction - invalid account', function() { - assert.throws(function() { - new Transaction().offerCancel('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 1); - }); - }); - - it('Construct OfferCreate transaction', function() { - const bid = '1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; - const ask = '1/EUR/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; - - const transaction = new Transaction().offerCreate('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', bid, ask); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'OfferCreate', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - TakerPays: { - value: '1', - currency: 'USD', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }, - TakerGets: { - value: '1', - currency: 'EUR', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - } - }); - }); - - it('Construct OfferCreate transaction - with expiration, cancelSequence', function() { - const bid = '1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; - const ask = '1/EUR/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; - const expiration = new Date(); - expiration.setHours(expiration.getHours() + 1); - - const rippleExpiration = Math.round(expiration.getTime() / 1000) - 0x386D4380; - - const transaction = new Transaction().offerCreate('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', bid, ask, expiration, 1); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'OfferCreate', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - TakerPays: { - value: '1', - currency: 'USD', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }, - TakerGets: { - value: '1', - currency: 'EUR', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }, - Expiration: rippleExpiration, - OfferSequence: 1 - }); - }); - - it('Construct OfferCreate transaction - params object', function() { - const bid = '1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; - const ask = '1/EUR/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; - const expiration = new Date(); - expiration.setHours(expiration.getHours() + 1); - - const rippleExpiration = Math.round(expiration.getTime() / 1000) - 0x386D4380; - - const transaction = new Transaction().offerCreate({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - taker_gets: ask, - taker_pays: bid, - expiration: expiration, - cancel_sequence: 1 - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'OfferCreate', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - TakerPays: { - value: '1', - currency: 'USD', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }, - TakerGets: { - value: '1', - currency: 'EUR', - issuer: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm' - }, - Expiration: rippleExpiration, - OfferSequence: 1 - }); - }); - - it('Construct OfferCreate transaction - invalid account', function() { - const bid = '1/USD/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; - const ask = '1/EUR/rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm'; - assert.throws(function() { - new Transaction().offerCreate('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', bid, ask); - }); - }); - - it('Construct SetRegularKey transaction', function() { - const transaction = new Transaction().setRegularKey('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'SetRegularKey', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - RegularKey: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - }); - }); - - it('Construct SetRegularKey transaction - params object', function() { - const transaction = new Transaction().setRegularKey({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - regular_key: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'SetRegularKey', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - RegularKey: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - }); - }); - - it('Construct SetRegularKey transaction - invalid account', function() { - assert.throws(function() { - new Transaction().setRegularKey('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); - }); - }); - - it('Construct SetRegularKey transaction - invalid regularKey', function() { - assert.throws(function() { - new Transaction().setRegularKey('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', 'xr36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); - }); - }); - - it('Construct Payment transaction', function() { - const transaction = new Transaction().payment( - 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - '1' - ); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'Payment', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - Destination: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - Amount: '1' - }); - }); - - it('Construct Payment transaction - complex amount', function() { - const transaction = new Transaction().payment( - 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - ); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'Payment', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - Destination: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - Amount: { - value: '1', - currency: 'USD', - issuer: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - } - }); - }); - - it('Construct Payment transaction - params object', function() { - const transaction = new Transaction().payment({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - destination: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - amount: '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'Payment', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - Destination: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - Amount: { - value: '1', - currency: 'USD', - issuer: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - } - }); - }); - - it('Construct Payment transaction - invalid account', function() { - assert.throws(function() { - new Transaction().payment( - 'xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - ); - }); - }); - - it('Construct Payment transaction - invalid destination', function() { - assert.throws(function() { - new Transaction().payment( - 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - 'xr36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - ); - }); - }); - - it('Construct TrustSet transaction', function() { - const limit = '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'; - const transaction = new Transaction().trustSet('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', limit); - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'TrustSet', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - LimitAmount: { - value: '1', - currency: 'USD', - issuer: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - } - }); - }); - - it('Construct TrustSet transaction - with qualityIn, qualityOut', function() { - const limit = '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'; - const transaction = new Transaction().trustSet('rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', limit, 1.0, 1.0); - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'TrustSet', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - LimitAmount: { - value: '1', - currency: 'USD', - issuer: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - }, - QualityIn: 1.0, - QualityOut: 1.0 - }); - }); - - it('Construct TrustSet transaction - params object', function() { - const limit = '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'; - const transaction = new Transaction().trustSet({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - limit: limit, - quality_in: 1.0, - quality_out: 1.0 - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'TrustSet', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - LimitAmount: { - value: '1', - currency: 'USD', - issuer: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - }, - QualityIn: 1.0, - QualityOut: 1.0 - }); - }); - - it('Construct TrustSet transaction - invalid account', function() { - assert.throws(function() { - const limit = '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'; - new Transaction().trustSet('xrsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', limit, 1.0, 1.0); - }); - }); - - it('Construct SignerListSet transaction', function() { - const transaction = new Transaction().setSignerList({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - signerQuorum: 3, - signers: [ - { - account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW', - weight: 1 - }, - { - account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK', - weight: 2 - } - ] - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'SignerListSet', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - SignerQuorum: 3, - SignerEntries: [ - { - SignerEntry: { - Account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW', - SignerWeight: 1 - } - }, - { - SignerEntry: { - Account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK', - SignerWeight: 2 - } - } - ] - }); - }); - - it('Submit transaction', function(done) { - const remote = new Remote(); - remote._ledger_current_index = 1; - - const transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); - - assert.strictEqual(transaction.callback, undefined); - assert.strictEqual(transaction._errorHandler, undefined); - assert.strictEqual(transaction._successHandler, undefined); - assert.strictEqual(transaction.listeners('error').length, 1); - - const account = remote.addAccount('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); - - account._transactionManager._nextSequence = 1; - - account._transactionManager._request = function(tx) { - tx.emit('success', { }); - }; - - transaction.complete = function() { - return this; - }; - - let receivedSuccess = false; - - transaction.once('success', function() { - receivedSuccess = true; - }); - - function submitCallback(err) { - setImmediate(function() { - assert.ifError(err); - assert(receivedSuccess); - done(); - }); - } - - transaction.submit(submitCallback); - - assert(transaction instanceof Transaction); - assert.strictEqual(transaction.callback, submitCallback); - assert.strictEqual(typeof transaction._errorHandler, 'function'); - assert.strictEqual(typeof transaction._successHandler, 'function'); - }); - - it('Submit transaction - submission error', function(done) { - const remote = new Remote(); - - remote._ledger_current_index = 1; - - const transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); - - const account = remote.addAccount('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe'); - - account._transactionManager._nextSequence = 1; - - account._transactionManager._request = function(tx) { - tx.emit('error', new Error('Test error')); - }; - - transaction.complete = function() { - return this; - }; - - let receivedError = false; - - transaction.once('error', function() { - receivedError = true; - }); - - function submitCallback(err) { - setImmediate(function() { - assert(err); - assert.strictEqual(err.constructor.name, 'RippleError'); - assert(receivedError); - done(); - }); - } - - transaction.submit(submitCallback); - }); - - it('Submit transaction - submission error, no remote', function(done) { - const transaction = new Transaction(); - - transaction.once('error', function(error) { - assert(error); - assert.strictEqual(error.message, 'No remote found'); - done(); - }); - - transaction.submit(); - }); - - it('Submit transaction - invalid account', function(done) { - const remote = new Remote(); - assert.throws(function() { - new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWeZ'); - }); - done(); - }); - - it('Abort submission on presubmit', function(done) { - const remote = new Remote(); - remote.setSecret('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79', 'snPwFATthTkKnGjEW73q3TL4yci1Q'); - - const server = new Server(remote, 'wss://s1.ripple.com:443'); - server._computeFee = function() { - return '12'; - }; - server._connected = true; - - remote._servers.push(server); - remote._connected = true; - remote._ledger_current_index = 1; - - const transaction = new Transaction(remote).accountSet('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79'); - const account = remote.account('rJaT8TafQfYJqDm8aC5n3Yx5yWEL2Ery79'); - - account._transactionManager._nextSequence = 1; - - transaction.once('presubmit', function() { - transaction.abort(); - }); - - transaction.submit(function(err) { - setImmediate(function() { - assert(err); - assert.strictEqual(err.result, 'tejAbort'); - done(); - }); - }); - }); - - it('Get min ledger', function() { - const queue = new TransactionQueue(); - - // Randomized submit indexes - const indexes = [ - 28093, - 456944, - 347213, - 165662, - 729760, - 808990, - 927393, - 925550, - 872298, - 543305 - ]; - - indexes.forEach(function(index) { - const tx = new Transaction(); - tx.initialSubmitIndex = index; - queue.push(tx); - }); - - // Pending queue sorted by submit index - const sorted = queue._queue.slice().sort(function(a, b) { - return a.initialSubmitIndex - b.initialSubmitIndex; - }); - - sorted.forEach(function(tx) { - assert.strictEqual(queue.getMinLedger(), tx.initialSubmitIndex); - queue.remove(tx); - }); - }); - - it('Construct SuspendedPaymentCreate transaction', function() { - const transaction = new Transaction().suspendedPaymentCreate({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - destination: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - amount: '1/USD/r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'SuspendedPaymentCreate', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - Destination: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe', - Amount: { - value: '1', - currency: 'USD', - issuer: 'r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe' - } - }); - }); - - it('Set Digest', function() { - const transaction = new Transaction(); - assert.strictEqual(transaction.tx_json.Digest, undefined); - transaction.setType('SuspendedPaymentCreate'); - assert.throws(function() { - transaction.setDigest('foo'); - }, /Error: Digest must be a valid Hash256/); - - const hash = '8F434346648F6B96DF89DDA901C5176B10A6D83961DD3C1AC88B59B2DC327AA4'; - transaction.setDigest(hash); - assert.strictEqual(transaction.tx_json.Digest, hash); - }); - - it('Set CancelAfter', function() { - const transaction = new Transaction(); - assert.strictEqual(transaction.tx_json.CancelAfter, undefined); - transaction.setType('SuspendedPaymentCreate'); - assert.throws(function() { - transaction.setAllowCancelAfter('foo'); - }, /Error: CancelAfter must be a valid UInt32/); - - transaction.setAllowCancelAfter(1441043377523); - assert.strictEqual(transaction.tx_json.CancelAfter, 494358578); - }); - - it('Set FinishAfter', function() { - const transaction = new Transaction(); - assert.strictEqual(transaction.tx_json.FinishAfter, undefined); - transaction.setType('SuspendedPaymentCreate'); - assert.throws(function() { - transaction.setAllowExecuteAfter('foo'); - }, /Error: FinishAfter must be a valid UInt32/); - - transaction.setAllowExecuteAfter(1441043377523); - assert.strictEqual(transaction.tx_json.FinishAfter, 494358578); - }); - - it('Construct SuspendedPaymentFinish transaction', function() { - const transaction = new Transaction().suspendedPaymentFinish({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - owner: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - paymentSequence: 1234 - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'SuspendedPaymentFinish', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - Owner: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - OfferSequence: 1234 - }); - }); - - it('Set Method', function() { - const transaction = new Transaction(); - assert.strictEqual(transaction.tx_json.Method, undefined); - transaction.setType('SuspendedPaymentFinish'); - assert.throws(function() { - transaction.setMethod('foo'); - }, /Error: Method must be a valid UInt8/); - - transaction.setMethod(1); - assert.strictEqual(transaction.tx_json.Method, 1); - }); - - it('Set Proof', function() { - const transaction = new Transaction(); - assert.strictEqual(transaction.tx_json.Proof, undefined); - transaction.setType('SuspendedPaymentFinish'); - transaction.setProof('foo'); - assert.strictEqual(transaction.tx_json.Proof, '666F6F'); - }); - - it('Construct SuspendedPaymentCancel transaction', function() { - const transaction = new Transaction().suspendedPaymentCancel({ - account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - owner: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - paymentSequence: 1234 - }); - - assert(transaction instanceof Transaction); - assert.deepEqual(transaction.tx_json, { - Flags: 0, - TransactionType: 'SuspendedPaymentCancel', - Account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - Owner: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm', - OfferSequence: 1234 - }); - }); - - it('Add multisigner', function() { - const transaction = new Transaction(); - const s1 = { - Account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK', - TxnSignature: '304402203020865BDC995431325C371E6A3CE89BFC40597D9CFAF77DBB16E9D159824EA402203645A6462A6DCEC7B5D0811882DC54CEA66258A227A2762BE6EFCD9EB62C27BF', - SigningPubKey: '02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE' - }; - const s2 = { - Account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW', - TxnSignature: '30450221009C84E455DC199A7DB4B800D68C92269D60972E8850AFC0D50B1AE6B08BBB02EA02206FA93A560BE96844DF7D96D07F6400EF9534A32FBA352DD10E855DA8923A3AF8', - SigningPubKey: '028949021029D5CC87E78BCF053AFEC0CAFD15108EC119EAAFEC466F5C095407BF' - }; - - transaction.addMultiSigner(s1); - transaction.addMultiSigner(s2); - - assert.deepEqual(transaction.getMultiSigners(), [ - {Signer: s2}, {Signer: s1}]); - }); - - it('Get multisign data', function() { - const transaction = Transaction.from_json({ - Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn', - Sequence: 1, - Fee: '100', - TransactionType: 'AccountSet', - Flags: 0 - }); - - transaction.setSigningPubKey(''); - - const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK'; - const d1 = transaction.multiSigningData(a1); - - const txHex = binary.encode( - lodash.merge(transaction.tx_json, {SigningPubKey: ''})); - const abytes = decodeAddress(a1); - const prefix = 0x534D5400; - const prefixHex = prefix.toString(16); - - assert.deepEqual(new Buffer(d1, 'hex'), - Buffer.concat([new Buffer(prefixHex, 'hex'), new Buffer(txHex, 'hex'), - new Buffer(abytes)])); - }); - - it('Multisign', function() { - const transaction = Transaction.from_json({ - Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn', - Sequence: 1, - Fee: '100', - TransactionType: 'AccountSet', - Flags: 0, - LastLedgerSequence: 1 - }); - - const multiSigningJson = transaction.getMultiSigningJson(); - const t1 = Transaction.from_json(multiSigningJson); - - const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK'; - const a2 = 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW'; - - const s1 = t1.multiSign(a1, addresses.SECRET); - assert.deepEqual(s1, { - Account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK', - TxnSignature: '30440220613DF9410B4844C7FAB637FD707F5185A2107DD10D0C2F59155844CD1910AB99022004A2AE607C15DD0959FDB3AAEE6A0337AA5337515230CE6EC11E32B74EEE896E', - SigningPubKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' - - }); - - const s2 = t1.multiSign(a2, addresses.SECRET); - assert.deepEqual(s2, { - Account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW', - TxnSignature: '3044022011762BC175E166EF540ABB162F0E8B48250E7C95DE5E8464E3F648EAF9A94A50022022439146DC3C6BB943C719F89696E7EBED14888A653F4618F62F8DA5CE202A45', - SigningPubKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' - }); - - transaction.addMultiSigner(s1); - transaction.addMultiSigner(s2); - - assert.deepEqual(transaction.getMultiSigners(), [ - {Signer: s2}, - {Signer: s1} - ]); - - transaction.remote = new Remote(); - assert(transaction.complete()); - assert.strictEqual(transaction.tx_json.SigningPubKey, ''); - }); - - it('Multisign -- missing LastLedgerSequence', function() { - const transaction = Transaction.from_json({ - Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn', - Sequence: 1, - Fee: '100', - TransactionType: 'AccountSet', - Flags: 0 - }); - - assert.throws(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); - }); -}); diff --git a/test/utils-test.js b/test/utils-test.js deleted file mode 100644 index 3e811399..00000000 --- a/test/utils-test.js +++ /dev/null @@ -1,24 +0,0 @@ -var assert = require('assert'); -var utils = require('ripple-lib').utils; - -describe('Utils', function() { - describe('hexToString and stringToHex', function() { - it('Even: 123456', function () { - assert.strictEqual('123456', utils.stringToHex(utils.hexToString('123456'))); - }); - it('Odd: 12345', function () { - assert.strictEqual('012345', utils.stringToHex(utils.hexToString('12345'))); - }); - it('Under 10: 0', function () { - assert.strictEqual('00', utils.stringToHex(utils.hexToString('0'))); - }); - it('Under 10: 1', function () { - assert.strictEqual('01', utils.stringToHex(utils.hexToString('1'))); - }); - it('Empty', function () { - assert.strictEqual('', utils.stringToHex(utils.hexToString(''))); - }); - }); -}); - -// vim:sw=2:sts=2:ts=8:et