Compare commits

...

33 Commits
1.2.4 ... 1.3.4

Author SHA1 Message Date
Elliot Lee
eb2a497dee Release 1.3.4 2019-10-18 14:41:58 -07:00
Elliot Lee
988381d584 Merge pull request #1048 from FredKSchott/tsc-update
Update TypeScript
2019-10-16 23:04:20 -07:00
Elliot Lee
0b163eae23 XRP Ledger Hashes: create README.md 2019-10-15 16:34:48 -07:00
Fred K. Schott
fadfd4e06c update typescript 2019-10-13 16:19:45 -07:00
Sohail Salehi
eb521faa8d Doc update for sign() method (#1010)
* Add multi-signing example to the sign() method

Explains why the `signAs` option is compulsory for multi-signing scenarios.
2019-10-08 11:59:12 -07:00
Tyler Storm
14e6bf5ef9 Integrate ripple-hashes (#1039)
* Update TxParser and Mocha

* Convert ripple-hashes to TypeScript and add into ripple-lib along with unit tests

* Switch casing to camelCase

* Document intToVuc

* Convert Slots to numbers, add exception if number is outside 0-15

* Remove Shamap v2 support

* Improve naming
2019-10-01 23:35:11 -07:00
Elliot Lee
b6bddd3b0e [MINOR] Remove unnecessary semicolon 2019-09-26 03:18:54 -07:00
Elliot Lee
d5ed9b6cf5 Improve error message when signing fails
When there are multiple representations of the same value
(for example, trailing zeros) the verification will fail.

This points users to the error.data object for details about
the failure, and adds a diff so that the cause of the
discrepancy can be seen at a glance.
2019-09-24 09:58:24 -07:00
Tyler Storm
7a9912d4e0 Update TxParser and Mocha 2019-09-24 09:57:56 -07:00
Mo Morsi
4d2ddceb4e Change "wipple" to "xrp1ntel" (#1036)
The Wipple Analytics Engine is now accessed through the xrp1ntel website
2019-09-12 13:15:26 -07:00
Elliot Lee
ae2aed675a v1.3.3 2019-09-10 19:30:11 -07:00
Elliot Lee
1d7cb41218 Add rippleTimeToISO8601.md.ejs 2019-09-04 00:31:02 -07:00
Elliot Lee
76db002eb5 Expand node version compatibility
Previously the library could not be installed with node 12:

https://github.com/ripple/ripple-binary-codec/pull/33
2019-09-04 00:17:53 -07:00
Elliot Lee
ebb64ba177 v1.3.2 2019-09-03 16:43:31 -07:00
Elliot Lee
1a685e2b68 Fix #1032 (#1033)
* Update ripple-address-codec@latest

* Export and document rippleTimeToISO8601
2019-09-03 16:38:56 -07:00
Rome Reginelli
9c561885a1 Docs: update recommended Node ver. (#1031) 2019-08-29 20:34:25 -07:00
Elliot Lee
612e98b198 Release 1.3.1 2019-08-26 13:58:09 -07:00
Elliot Lee
0a41d5ccf1 Upgrade to gulp 4; remove http server (#1030)
* Update gulp version to ^4.0.2 and Gulpfile.js

* Remove http server
2019-08-26 13:50:36 -07:00
Elliot Lee
ba8fe1f32c Update HISTORY - rippled 1.3.1 2019-08-16 21:28:32 -07:00
Elliot Lee
0f840876a5 Update HISTORY 2019-08-16 21:11:02 -07:00
Elliot Lee
ac3900b6f4 Release 1.2.5 and 1.3.0 2019-08-16 21:09:08 -07:00
Elliot Lee
5e138b9937 Sign method - verify accurate encoding (#1026)
* Decode signed transactions and check field integrity

* Add tests (including signing a tx without Flags)

* Update tests to output more descriptive errors on failure

* Update ripple-binary-codec to 0.2.2
2019-08-16 16:22:25 -07:00
Elliot Lee
82d50cd903 Document setCanonicalFlag and export from src/transaction/utils 2019-08-09 10:16:33 -07:00
Elliot Lee
c5b1d4daac Update lodash to ^4.14.136, fix docs typo, update HISTORY.md 2019-08-07 12:20:56 -07:00
Namrata
b4a30d49d8 Support removing a signer list (#1021)
* Make weights an optional field
* Fix #971
2019-07-30 16:48:30 -07:00
Elliot Lee
229360d1b9 README - Update link 2019-07-18 21:50:58 -07:00
Rome Reginelli
d627d362af Merge pull request #1023 from mDuo13/doc_cleanup
Phrasing cleanup in schema descriptions
2019-07-16 15:11:01 -07:00
mDuo13
7ca7a07942 Phrasing cleanup in schema descriptions
- Change 'Ripple address'→'XRP Ledger address'
- Change 'is exclusive with'→'cannot be used with'
2019-07-15 14:26:43 -07:00
dependabot[bot]
09f81fa3cd Bump lodash from 4.17.11 to 4.17.13
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.13.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.13)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-13 13:26:48 -07:00
The XRP Scan Project
b03bf9c7f1 Updated APPLICATIONS.md and added xrpscan
Added xrpscan.com under Data and Visualization section.
2019-06-24 02:03:21 -07:00
Elliot Lee
4d696369fe APIOptions: extend ConnectionOptions (#1018)
Fix #1017
2019-06-21 01:45:47 -07:00
Elliot Lee
bcb80ea5f5 Fix getServerInfo (#1012)
* Return array for serverInfo.load.jobTypes

* Include start/end in invalid range assertion error
2019-06-14 14:57:36 -07:00
Elliot Lee
1be2ee5875 Docs: fix #574 (#1011)
destination.address
2019-06-14 14:57:14 -07:00
75 changed files with 2143 additions and 1233 deletions

View File

@@ -12,7 +12,7 @@ Warning: Use at your own risk.
## Data and visualizations
- **[Wipple - XRP Intelligence](https://wipple.devnull.network/)**
- **[xrp1ntel - XRP Intelligence](https://xrp1ntel.com/)**
Monitor the XRP Network in real time and explore historical statistics.
@@ -36,6 +36,10 @@ Warning: Use at your own risk.
List of XRPL validators, nodes, and testnet validators.
- **[XRP Scan - XRP Ledger explorer](https://http://xrpscan.com)**
XRP Ledger explorer, metrics and analytics.
## Send and request payments
- **[XRP Tip Bot](https://www.xrptipbot.com/)**

View File

@@ -6,15 +6,12 @@ const fs = require('fs');
const path = require('path');
const assert = require('assert');
const gulp = require('gulp');
const rename = require('gulp-rename');
const webpack = require('webpack');
const bump = require('gulp-bump');
const argv = require('yargs').argv;
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
var pkg = require('./package.json');
const pkg = require('./package.json');
var uglifyOptions = {
const uglifyOptions = {
mangle: {
reserved: ['_', 'RippleError', 'RippledError', 'UnexpectedError',
'LedgerVersionError', 'ConnectionError', 'NotConnectedError',
@@ -36,7 +33,7 @@ function getWebpackConfig(extension, overrides) {
output: {
library: 'ripple',
path: path.join(__dirname, 'build/'),
filename: ['ripple-', extension].join(pkg.version)
filename: `ripple-${pkg.version}${extension}`
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/^ws$/, './wswrapper'),
@@ -69,7 +66,7 @@ function getWebpackConfig(extension, overrides) {
return _.assign({}, defaults, overrides);
}
function webpackConfigForWebTest(testFileName, path) {
function webpackConfigForWebTest(testFileName) {
var match = testFileName.match(/\/?([^\/]*)-test.js$/);
if (!match) {
assert(false, 'wrong filename:' + testFileName);
@@ -83,28 +80,13 @@ function webpackConfigForWebTest(testFileName, path) {
entry: testFileName,
output: {
library: match[1].replace(/-/g, '_'),
path: './test-compiled-for-web/' + (path ? path : ''),
path: path.join(__dirname, 'test-compiled-for-web/'),
filename: match[1] + '-test.js'
}
};
return getWebpackConfig('.js', configOverrides);
}
gulp.task('build-tests', function(callback) {
var times = 0;
function done() {
if (++times >= 5) {
callback();
}
}
webpack(webpackConfigForWebTest('./test/rangeset-test.js'), done);
webpack(webpackConfigForWebTest('./test/connection-test.js'), done);
webpack(webpackConfigForWebTest('./test/api-test.js'), done);
webpack(webpackConfigForWebTest('./test/broadcast-api-test.js'), done);
webpack(webpackConfigForWebTest('./test/integration/integration-test.js',
'integration/'), done);
});
function createLink(from, to) {
if (fs.existsSync(to)) {
fs.unlinkSync(to);
@@ -120,11 +102,22 @@ function createBuildLink(callback) {
};
}
gulp.task('build', function(callback) {
webpack(getWebpackConfig('.js'), createBuildLink(callback));
});
function watch(callback) {
gulp.watch('src/*', gulp.series(buildDebug));
callback();
}
gulp.task('build-min', function(callback) {
function build(callback) {
webpack(getWebpackConfig('.js'), createBuildLink(callback));
}
function buildDebug(callback) {
const webpackConfig = getWebpackConfig('-debug.js', {devtool: 'eval'});
webpackConfig.plugins.unshift(new webpack.LoaderOptionsPlugin({debug: true}));
webpack(webpackConfig, callback);
}
function buildMin(callback) {
const webpackConfig = getWebpackConfig('-min.js');
webpackConfig.plugins.push(new UglifyJsPlugin({uglifyOptions}));
webpack(webpackConfig, function() {
@@ -132,86 +125,27 @@ gulp.task('build-min', function(callback) {
'./build/ripple-latest-min.js');
callback();
});
});
gulp.task('build-debug', function(callback) {
const webpackConfig = getWebpackConfig('-debug.js', {devtool: 'eval'});
webpackConfig.plugins.unshift(new webpack.LoaderOptionsPlugin({debug: true}));
webpack(webpackConfig, callback);
});
/**
* Generate a WebPack external for a given unavailable module which replaces
* that module's constructor with an error-thrower
*/
function buildUseError(cons) {
return ('var {<CONS>:function(){throw new Error('
+ '"Class is unavailable in this build: <CONS>")}}')
.replace(new RegExp('<CONS>', 'g'), cons);
}
gulp.task('build-core', function(callback) {
var configOverrides = {
cache: false,
entry: './src/remote.ts',
externals: [{
'./transaction': buildUseError('Transaction'),
'./orderbook': buildUseError('OrderBook'),
'./account': buildUseError('Account'),
'./serializedobject': buildUseError('SerializedObject')
}],
plugins: [
new UglifyJsPlugin()
]
};
webpack(getWebpackConfig('-core.js', configOverrides), callback);
});
gulp.task('bower-build', ['build'], function() {
return gulp.src(['./build/ripple-', '.js'].join(pkg.version))
.pipe(rename('ripple.js'))
.pipe(gulp.dest('./dist/bower'));
});
gulp.task('bower-build-min', ['build-min'], function() {
return gulp.src(['./build/ripple-', '-min.js'].join(pkg.version))
.pipe(rename('ripple-min.js'))
.pipe(gulp.dest('./dist/bower'));
});
gulp.task('bower-build-debug', ['build-debug'], function() {
return gulp.src(['./build/ripple-', '-debug.js'].join(pkg.version))
.pipe(rename('ripple-debug.js'))
.pipe(gulp.dest('./dist/bower'));
});
gulp.task('bower-version', function() {
gulp.src('./dist/bower/bower.json')
.pipe(bump({version: pkg.version}))
.pipe(gulp.dest('./dist/bower'));
});
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug',
'bower-version']);
gulp.task('watch', function() {
gulp.watch('src/*', ['build-debug']);
});
gulp.task('version-bump', function() {
if (!argv.type) {
throw new Error('No type found, pass it in using the --type argument');
function buildTests(callback) {
var times = 0;
function done() {
if (++times >= 5) {
callback();
}
}
webpack(webpackConfigForWebTest('./test/rangeset-test.js'), done);
webpack(webpackConfigForWebTest('./test/connection-test.js'), done);
webpack(webpackConfigForWebTest('./test/api-test.js'), done);
webpack(webpackConfigForWebTest('./test/broadcast-api-test.js'), done);
webpack(webpackConfigForWebTest('./test/integration/integration-test.js',
'integration/'), done);
}
gulp.src('./package.json')
.pipe(bump({type: argv.type}))
.pipe(gulp.dest('./'));
});
exports.watch = watch;
exports.build = build;
exports.buildDebug = buildDebug;
exports.buildMin = buildMin;
exports.buildTests = buildTests;
gulp.task('version-beta', function() {
gulp.src('./package.json')
.pipe(bump({version: pkg.version + '-beta'}))
.pipe(gulp.dest('./'));
});
gulp.task('default', ['build', 'build-debug', 'build-min']);
exports.default = gulp.parallel(build, buildDebug, buildMin);

View File

@@ -1,5 +1,115 @@
# ripple-lib Release History
## 1.3.4 (2019-10-18)
* Update ripple-lib-transactionparser
* Improve error message when signing fails (e.g. due to trailing zeros)
* Integrate ripple-hashes (in TypeScript with improved naming and docs)
* Add multi-signing example to sign() method docs
* Update TypeScript
## 1.3.3 (2019-09-10)
* Expand node version compatibility to support Node.js 12 ([ripple-binary-codec#32](https://github.com/ripple/ripple-binary-codec/issues/32))
## 1.3.2 (2019-09-03)
* Export and document `rippleTimeToISO8601()` method
* Add type for isValidAddress (fixes error TS7016, #1032)
* Docs: update recommended Node.js version (#1031)
When using this release with [rippled](https://github.com/ripple/rippled), we recommend using [rippled version 1.3.1](https://github.com/ripple/rippled/releases/tag/1.3.1) or later.
## 1.3.1 (2019-08-26)
* Upgrade to gulp 4 (#1030)
* Remove http server (unused)
* Update dependencies
There are no changes in the browser version with this release. The npm package for Node.js should be slightly smaller.
When using this release with [rippled](https://github.com/ripple/rippled), we recommend using [rippled version 1.3.1](https://github.com/ripple/rippled/releases/tag/1.3.1).
## 1.3.0 (2019-08-16)
### Bug fixes:
* Breaking change: Fix `getServerInfo` (#1012)
Before:
```
{
// ...
"load": {
"jobTypes": {
"0": {"jobType": "untrustedValidation", "perSecond": 10},
"1": {"jobType": "ledgerData", "peakTime": 2, "perSecond": 2},
"2": {"inProgress": 1, "jobType": "clientCommand", "perSecond": 1},
"3": {"jobType": "transaction", "perSecond": 1},
// ...
},
"threads": 6
},
// ...
}
```
After:
```
{
// ...
"load": {
"jobTypes": [
{
"jobType": "untrustedValidation",
"perSecond": 8
},
{
"avgTime": 2,
"jobType": "ledgerData",
"peakTime": 72,
"perSecond": 2
},
{
"inProgress": 1,
"jobType": "clientCommand",
"perSecond": 1
},
{
"jobType": "transaction",
"perSecond": 1
},
// ...
],
"threads": 6
},
// ...
}
```
* Sign method - verify accurate encoding (#1026)
* In previous versions, the following could be encoded incorrectly:
* Amounts of XRP with more than 6 decimal places
* Amounts of XRP drops with any decimal places
* In versions 1.2.5 and 1.3.0, amounts that are invalid in this way will throw an error
* Expand `APIOptions` by extending `ConnectionOptions` (#1018, fixes #1017)
* Fix docs for destination.address (#1011)
### New features:
* Support removing a signer list (#1021)
* Export `setCanonicalFlag` method from `src/transaction/utils`
### Improvements:
* Clean up phrasing in schema descriptions (#1023)
* Improve docs
* Update dependencies
Since this release fixes a bug that could cause transactions to be serialized incorrectly during the signing process, it has also been simultaneously released as version 1.2.5 (patch release).
When using this release with [rippled](https://github.com/ripple/rippled), we recommend using [rippled version 1.3.1](https://github.com/ripple/rippled/releases/tag/1.3.1).
## 1.2.4 (2019-06-06)
* Update README.md

View File

@@ -14,7 +14,7 @@ A JavaScript API for interacting with the XRP Ledger
## Getting Started
See also: [RippleAPI Beginners Guide](https://ripple.com/build/rippleapi-beginners-guide/)
See also: [RippleAPI Beginners Guide](https://xrpl.org/get-started-with-rippleapi-for-javascript.html)
### Requirements

View File

@@ -91,6 +91,7 @@
- [xrpToDrops](#xrptodrops)
- [dropsToXrp](#dropstoxrp)
- [iso8601ToRippleTime](#iso8601torippletime)
- [rippleTimeToISO8601](#rippletimetoiso8601)
- [txFlags](#txflags)
- [schemaValidator](#schemavalidator)
- [schemaValidate](#schemavalidate)
@@ -141,7 +142,7 @@ api.connect().then(() => {
}).catch(console.error);
```
RippleAPI is designed to work in [Node.js](https://nodejs.org) version **6.11.3**. RippleAPI may work on older Node.js versions if you use [Babel](https://babeljs.io/) for [ECMAScript 6](https://babeljs.io/docs/learn-es2015/) support.
RippleAPI is designed to work in [Node.js](https://nodejs.org) version 6 or higher. Ripple recommends Node.js v10 LTS.
The code samples in this documentation are written with ECMAScript 6 (ES6) features, but `RippleAPI` also works with ECMAScript 5 (ES5). Regardless of whether you use ES5 or ES6, the methods that return Promises return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
@@ -273,7 +274,7 @@ A *balance* is an amount than can have a negative value.
Name | Type | Description
---- | ---- | -----------
currency | [currency](#currency) | The three-character code or hexadecimal string used to denote currencies, or "drops" for the smallest unit of XRP.
counterparty | [address](#address) | *Optional* The Ripple address of the account that owes or is owed the funds (omitted if `currency` is "XRP" or "drops")
counterparty | [address](#address) | *Optional* The XRP Ledger address of the account that owes or is owed the funds (omitted if `currency` is "XRP" or "drops")
value | [value](#value) | *Optional* The quantity of the currency, denoted as a string to retain floating point precision
# Transaction Overview
@@ -375,15 +376,14 @@ Name | Type | Description
---- | ---- | -----------
source | object | The source of the funds to be sent.
*source.* address | [address](#address) | The address to send from.
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with source.maxAmount)
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field is exclusive with source.amount)
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field cannot be used with source.amount)
destination | object | The destination of the funds to be sent.
*destination.* address | [address](#address) | The address to receive at.
*destination.* address | [address](#address) | An address representing the destination of the transaction.
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*destination.* address | [address](#address) | The address to send to.
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field is exclusive with destination.amount)
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field cannot be used with destination.amount)
allowPartialPayment | boolean | *Optional* If true, this payment should proceed even if the whole amount cannot be delivered due to a lack of liquidity or a lack of funds in the source account.
invoiceID | string | *Optional* A 256-bit hash that can be used to identify a particular payment.
limitQuality | boolean | *Optional* Only take paths where all the conversions have an input:output ratio that is equal or better than the ratio of destination.amount:source.maxAmount.
@@ -536,7 +536,7 @@ requireAuthorization | boolean | *Optional* If set, this account must individual
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
*signers.* threshold | integer | A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
*signers.* weights | array | Weights of signatures for each signer.
*signers.* weights | array | *Optional* Weights of signatures for each signer.
*signers.* weights[] | object | An association of an address and a weight.
*signers.weights[].* address | [address](#address) | A Ripple account address
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
@@ -1639,8 +1639,8 @@ options | object | *Optional* Options to filter the resulting transactions.
*options.* limit | integer | *Optional* If specified, return at most this many transactions.
*options.* maxLedgerVersion | integer | *Optional* Return only transactions in this ledger version or lower.
*options.* maxLedgerVersion | string | *Optional* Return only transactions in this ledger version or lower.
*options.* minLedgerVersion | integer | *Optional* Return only transactions in this ledger verion or higher.
*options.* minLedgerVersion | string | *Optional* Return only transactions in this ledger verion or higher.
*options.* minLedgerVersion | integer | *Optional* Return only transactions in this ledger version or higher.
*options.* minLedgerVersion | string | *Optional* Return only transactions in this ledger version or higher.
*options.* start | string | *Optional* If specified, this transaction will be the first transaction in the result. You cannot use `start` with `minLedgerVersion` or `maxLedgerVersion`. When `start` is specified, these ledger versions will be determined internally.
*options.* types | array\<[transactionType](#transaction-types)\> | *Optional* Only return transactions of the specified [Transaction Types](#transaction-types).
@@ -2021,7 +2021,7 @@ Name | Type | Description
---- | ---- | -----------
currency | [currency](#currency) | The three-character code or hexadecimal string used to denote currencies
value | [signedValue](#value) | The balance on the trustline
counterparty | [address](#address) | *Optional* The Ripple address of the account that owes or is owed the funds.
counterparty | [address](#address) | *Optional* The XRP Ledger address of the account that owes or is owed the funds.
### Example
@@ -2172,7 +2172,7 @@ Returns aggregate balances by currency plus a breakdown of assets and obligation
Name | Type | Description
---- | ---- | -----------
address | [address](#address) | The Ripple address of the account to get the balance sheet of.
address | [address](#address) | The XRP Ledger address of the account to get the balance sheet of.
options | object | *Optional* Options to determine how the balances will be calculated.
*options.* excludeAddresses | array\<[address](#address)\> | *Optional* Addresses to exclude from the balance totals.
*options.* ledgerVersion | integer | *Optional* Get the balance sheet as of this historical ledger version.
@@ -2270,14 +2270,14 @@ Name | Type | Description
---- | ---- | -----------
pathfind | object | Specification of a pathfind request.
*pathfind.* source | object | Properties of the source of funds.
*pathfind.source.* address | [address](#address) | The Ripple address of the account where funds will come from.
*pathfind.source.* address | [address](#address) | The XRP Ledger address of the account where funds will come from.
*pathfind.source.* amount | [laxAmount](#amount) | *Optional* The amount of funds to send.
*pathfind.source.* currencies | array | *Optional* An array of currencies (with optional counterparty) that may be used in the payment paths.
*pathfind.source.* currencies[] | object | A currency with optional counterparty.
*pathfind.source.currencies[].* currency | [currency](#currency) | The three-character code or hexadecimal string used to denote currencies
*pathfind.source.currencies[].* counterparty | [address](#address) | *Optional* The counterparty for the currency; if omitted any counterparty may be used.
*pathfind.* destination | object | Properties of the destination of funds.
*pathfind.destination.* address | [address](#address) | The address to send to.
*pathfind.destination.* address | [address](#address) | An address representing the destination of the transaction.
*pathfind.destination.* amount | [laxLaxAmount](#amount) | The amount to be received by the receiver (`value` may be ommitted if a source amount is specified).
### Return Value
@@ -2288,15 +2288,14 @@ Name | Type | Description
---- | ---- | -----------
source | object | Properties of the source of the payment.
*source.* address | [address](#address) | The address to send from.
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with source.maxAmount)
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field is exclusive with source.amount)
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field cannot be used with source.amount)
destination | object | Properties of the destination of the payment.
*destination.* address | [address](#address) | The address to receive at.
*destination.* address | [address](#address) | An address representing the destination of the transaction.
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*destination.* address | [address](#address) | The address to send to.
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field is exclusive with destination.amount)
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field cannot be used with destination.amount)
paths | string | The paths of trustlines and orders to use in executing the payment.
### Example
@@ -2390,7 +2389,7 @@ Returns open orders for the specified account. Open orders are orders that have
Name | Type | Description
---- | ---- | -----------
address | [address](#address) | The Ripple address of the account to get open orders for.
address | [address](#address) | The XRP Ledger address of the account to get open orders for.
options | object | *Optional* Options that determine what orders will be returned.
*options.* ledgerVersion | integer | *Optional* Return orders as of this historical ledger version.
*options.* ledgerVersion | string | *Optional* Return orders as of this historical ledger version.
@@ -3905,7 +3904,7 @@ requireAuthorization | boolean | *Optional* If set, this account must individual
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
*signers.* threshold | integer | A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
*signers.* weights | array | Weights of signatures for each signer.
*signers.* weights | array | *Optional* Weights of signatures for each signer.
*signers.* weights[] | object | An association of an address and a weight.
*signers.weights[].* address | [address](#address) | A Ripple account address
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
@@ -5397,12 +5396,14 @@ This method can sign any of [the transaction types supported by ripple-binary-co
Name | Type | Description
---- | ---- | -----------
txJSON | string | Transaction represented as a JSON string in rippled format.
keypair | object | *Optional* The private and public key of the account that is initiating the transaction. (This field is exclusive with secret).
keypair | object | *Optional* The private and public key of the account that is initiating the transaction. (This field cannot be used with secret).
*keypair.* privateKey | privateKey | The uppercase hexadecimal representation of the secp256k1 or Ed25519 private key.
*keypair.* publicKey | publicKey | The uppercase hexadecimal representation of the secp256k1 or Ed25519 public key.
options | object | *Optional* Options that control the type of signature that will be generated.
*options.* signAs | [address](#address) | *Optional* The account that the signature should count for in multisigning.
secret | secret string | *Optional* The secret of the account that is initiating the transaction. (This field is exclusive with keypair).
secret | secret string | *Optional* The secret of the account that is initiating the transaction. (This field cannot be used with keypair).
Please note that when this method is used for multisigning, the `options` parameter is not *Optional* anymore. It will be compulsory. See the multisigning example in this section for more details.
### Return Value
@@ -5430,6 +5431,93 @@ return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
}
```
### Example (multisigning)
```javascript
const RippleAPI = require('ripple-lib').RippleAPI;
// jon's address will have a multi-signing setup with a quorum of 2
const jon = {
account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
secret: 'sh4Va7b1wQof8knHFV2sxwX12fSgK'
};
const aya = {
account: 'rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN',
secret: 'snaMuMrXeVc2Vd4NYvHofeGNjgYoe'
};
const bran = {
account: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
secret: 'shQtQ8Um5MS218yvEU3Ehy1eZQKqH'
};
// Setup the signers list with a quorum of 2
const multiSignSetupTransaction = {
"Flags": 0,
"TransactionType": "SignerListSet",
"Account": "rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj",
"Fee": "120",
"SignerQuorum": 2,
"SignerEntries": [
{
"SignerEntry": {
"Account": "rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN",
"SignerWeight": 2
}
},
{
"SignerEntry": {
"Account": "rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH",
"SignerWeight": 1
}
},
]
};
// a transaction which requires multi signing
const multiSignPaymentTransaction = {
TransactionType: 'Payment',
Account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
Destination: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
Amount: '88000000'
};
const api = new RippleAPI({
server: 'wss://s.altnet.rippletest.net:51233'
});
api.connect().then(() => {
// adding the multi signing feature to jon's account
api.prepareTransaction(multiSignSetupTransaction).then((prepared) => {
console.log(prepared);
jonSign = api.sign(prepared.txJSON, jon.secret).signedTransaction;
api.submit(jonSign).then( response => {
console.log(response.resultCode, response.resultMessage);
// multi sign a transaction
api.prepareTransaction(multiSignPaymentTransaction).then(prepared => {
console.log(prepared);
// Aya and Bran sign it too but with 'signAs' set to their own account
let ayaSign = api.sign(prepared.txJSON, aya.secret, {'signAs': aya.account}).signedTransaction;
let branSign = api.sign(prepared.txJSON, bran.secret, {'signAs': bran.account}).signedTransaction;
// signatures are combined and submitted
let combinedTx = api.combine([ayaSign, branSign]);
api.submit(combinedTx.signedTransaction).then(response => {
console.log(response.tx_json.hash);
return api.disconnect();
}).catch(console.error);
}).catch(console.error);
}).catch(console.error)
}).catch(console.error);
}).catch(console.error);
```
Assuming the multisigning account was setup properly, the above example will respond with `resultCode: 'tesSUCCESS'` and the hash for the transaction.
If any of `{signAs: some_address}` options were missing the code will return a validation error as follow:
```
[ValidationError(txJSON is not the same for all signedTransactions)]
```
## combine
@@ -5862,6 +5950,34 @@ api.iso8601ToRippleTime('2017-02-17T15:04:57Z');
540659097
```
## rippleTimeToISO8601
`rippleTimeToISO8601(rippleTime: number): string`
This method takes the number of seconds since the "Ripple Epoch" of January 1, 2000 (00:00 UTC) and returns a string representation of a date.
The Ripple Epoch is 946684800 seconds after the Unix Epoch.
This method is useful for interpreting timestamps returned by the rippled APIs. The rippled APIs represent time as an unsigned integer of the number of seconds since the Ripple Epoch.
### Parameters
`rippleTime`: A number of seconds since the Ripple Epoch.
### Return Value
A string representing a date and time, created by calling a `Date` object's `toISOString()` method.
### Example
```javascript
api.rippleTimeToISO8601(540659097);
```
```json
'2017-02-17T15:04:57.000Z'
```
## txFlags
`txFlags.TRANSACTION_TYPE.FLAG`

View File

@@ -26,7 +26,7 @@ api.connect().then(() => {
}).catch(console.error);
```
RippleAPI is designed to work in [Node.js](https://nodejs.org) version **6.11.3**. RippleAPI may work on older Node.js versions if you use [Babel](https://babeljs.io/) for [ECMAScript 6](https://babeljs.io/docs/learn-es2015/) support.
RippleAPI is designed to work in [Node.js](https://nodejs.org) version 6 or higher. Ripple recommends Node.js v10 LTS.
The code samples in this documentation are written with ECMAScript 6 (ES6) features, but `RippleAPI` also works with ECMAScript 5 (ES5). Regardless of whether you use ES5 or ES6, the methods that return Promises return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

View File

@@ -62,6 +62,7 @@
<% include computeLedgerHash.md.ejs %>
<% include xrpToDropsAndDropsToXrp.md.ejs %>
<% include iso8601ToRippleTime.md.ejs %>
<% include rippleTimeToISO8601.md.ejs %>
<% include txFlags.md.ejs %>
<% include schemaValidator.md.ejs %>
<% include events.md.ejs %>

View File

@@ -0,0 +1,27 @@
## rippleTimeToISO8601
`rippleTimeToISO8601(rippleTime: number): string`
This method takes the number of seconds since the "Ripple Epoch" of January 1, 2000 (00:00 UTC) and returns a string representation of a date.
The Ripple Epoch is 946684800 seconds after the Unix Epoch.
This method is useful for interpreting timestamps returned by the rippled APIs. The rippled APIs represent time as an unsigned integer of the number of seconds since the Ripple Epoch.
### Parameters
`rippleTime`: A number of seconds since the Ripple Epoch.
### Return Value
A string representing a date and time, created by calling a `Date` object's `toISOString()` method.
### Example
```javascript
api.rippleTimeToISO8601(540659097);
```
```json
'2017-02-17T15:04:57.000Z'
```

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "1.2.4",
"version": "1.3.4",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -17,17 +17,17 @@
"test": "test"
},
"dependencies": {
"@types/lodash": "^4.14.85",
"@types/lodash": "^4.14.136",
"@types/ws": "^3.2.0",
"bignumber.js": "^4.1.0",
"https-proxy-agent": "2.2.1",
"jsonschema": "1.2.2",
"lodash": "^4.17.4",
"ripple-address-codec": "^2.0.1",
"ripple-binary-codec": "0.2.1",
"ripple-hashes": "0.3.2",
"lodash.isequal": "^4.5.0",
"ripple-address-codec": "^3.0.4",
"ripple-binary-codec": "^0.2.4",
"ripple-keypairs": "^0.10.1",
"ripple-lib-transactionparser": "0.7.1",
"ripple-lib-transactionparser": "0.8.0",
"ws": "^3.3.1"
},
"devDependencies": {
@@ -36,13 +36,10 @@
"doctoc": "^0.15.0",
"ejs": "^2.3.4",
"eventemitter2": "^0.4.14",
"gulp": "^3.8.10",
"gulp-bump": "^0.1.13",
"gulp-rename": "^1.2.0",
"jayson": "^1.2.2",
"gulp": "^4.0.2",
"json-loader": "^0.5.2",
"json-schema-to-markdown-table": "^0.4.0",
"mocha": "6.1.3",
"mocha": "6.2.0",
"mocha-junit-reporter": "^1.9.1",
"null-loader": "^0.1.1",
"nyc": "^14.1.1",
@@ -51,10 +48,9 @@
"ts-node": "8.0.3",
"tslint": "^5.8.0",
"tslint-eslint-rules": "^4.1.1",
"typescript": "3.4.2",
"typescript": "^3.6.4",
"uglifyjs-webpack-plugin": "^1.1.4",
"webpack": "3.12.0",
"yargs": "13.2.2"
"webpack": "3.12.0"
},
"scripts": {
"build": "gulp",
@@ -67,8 +63,7 @@
"test": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha --exit",
"lint": "tslint -p ./",
"perf": "./scripts/perf_test.sh",
"start": "node scripts/http.js",
"sauce": "node scripts/sauce-runner.js"
"start": "node scripts/http.js"
},
"repository": {
"type": "git",

View File

@@ -45,7 +45,6 @@ unittest() {
integrationtest() {
mocha test/integration/integration-test.js
mocha test/integration/http-integration-test.js
# run integration tests in PhantomJS
#gulp build-tests build-min

View File

@@ -1,16 +0,0 @@
'use strict';
const createHTTPServer = require('../dist/npm/http').createHTTPServer;
const port = 5990;
const serverUrl = 'wss://s1.ripple.com';
function main() {
const server = createHTTPServer({server: serverUrl}, port);
server.start().then(() => {
console.log('Server started on port ' + String(port));
});
}
main();

View File

@@ -5,6 +5,7 @@ import {
validate,
xrpToDrops,
dropsToXrp,
rippleTimeToISO8601,
iso8601ToRippleTime,
txFlags
} from './common'
@@ -72,8 +73,9 @@ import * as schemaValidator from './common/schema-validator'
import {getServerInfo, getFee} from './common/serverinfo'
import {clamp, renameCounterpartyToIssuer} from './ledger/utils'
import {TransactionJSON, Instructions, Prepare} from './transaction/types'
import {ConnectionOptions} from './common/connection'
export type APIOptions = {
export interface APIOptions extends ConnectionOptions {
server?: string,
feeCushion?: number,
maxFeeXRP?: string,
@@ -336,6 +338,7 @@ class RippleAPI extends EventEmitter {
xrpToDrops = xrpToDrops
dropsToXrp = dropsToXrp
rippleTimeToISO8601 = rippleTimeToISO8601
iso8601ToRippleTime = iso8601ToRippleTime
txFlags = txFlags

View File

@@ -1,7 +1,7 @@
import * as _ from 'lodash'
import {EventEmitter} from 'events'
import {parse as parseUrl} from 'url'
import * as WebSocket from 'ws'
import WebSocket from 'ws'
import RangeSet from './rangeset'
import {RippledError, DisconnectedError, NotConnectedError,
TimeoutError, ResponseFormatError, ConnectionError,

View File

@@ -0,0 +1,49 @@
# XRP Ledger Hashes
Methods to hash XRP Ledger objects
## Methods
### computeBinaryTransactionHash = (txBlobHex: string): string
Compute the hash of a binary transaction blob.
### computeTransactionHash = (txJSON: any): string
Compute the hash of a transaction in txJSON format.
### computeBinaryTransactionSigningHash = (txBlobHex: string): string
### computeTransactionSigningHash = (txJSON: any): string
### computeAccountHash = (address: string): string
Compute the hash of an account, given the account's classic address (starting with `r`).
### computeSignerListHash = (address: string): string
Compute the hash of an account's SignerList.
### computeOrderHash = (address: string, sequence: number): string
Compute the hash of an order, given the owner's classic address (starting with `r`) and the account sequence number of the `OfferCreate` order transaction.
### computeTrustlineHash = (address1: string, address2: string, currency: string): string
Compute the hash of a trustline, given the two parties' classic addresses (starting with `r`) and the currency code.
### computeTransactionTreeHash = (transactions: any[]): string
### computeStateTreeHash = (entries: any[]): string
### computeLedgerHash = (ledgerHeader): string
Compute the hash of a ledger.
### computeEscrowHash = (address, sequence): string
Compute the hash of an escrow, given the owner's classic address (starting with `r`) and the account sequence number of the `EscrowCreate` escrow transaction.
### computePaymentChannelHash = (address, dstAddress, sequence): string
Compute the hash of a payment channel, given the owner's classic address (starting with `r`), the classic address of the destination, and the account sequence number of the `PaymentChannelCreate` payment channel transaction.

View File

@@ -0,0 +1,40 @@
/**
* Prefix for hashing functions.
*
* These prefixes are inserted before the source material used to
* generate various hashes. This is done to put each hash in its own
* "space." This way, two different types of objects with the
* same binary data will produce different hashes.
*
* Each prefix is a 4-byte value with the last byte set to zero
* and the first three bytes formed from the ASCII equivalent of
* some arbitrary string. For example "TXN".
*/
enum HashPrefix {
// transaction plus signature to give transaction ID
TRANSACTION_ID = 0x54584E00, // 'TXN'
// transaction plus metadata
TRANSACTION_NODE = 0x534E4400, // 'TND'
// inner node in tree
INNER_NODE = 0x4D494E00, // 'MIN'
// leaf node in tree
LEAF_NODE = 0x4D4C4E00, // 'MLN'
// inner transaction to sign
TRANSACTION_SIGN = 0x53545800, // 'STX'
// inner transaction to sign (TESTNET)
TRANSACTION_SIGN_TESTNET = 0x73747800, // 'stx'
// inner transaction to multisign
TRANSACTION_MULTISIGN = 0x534D5400, // 'SMT'
// ledger
LEDGER = 0x4C575200 // 'LWR'
}
export default HashPrefix

155
src/common/hashes/index.ts Normal file
View File

@@ -0,0 +1,155 @@
import BigNumber from 'bignumber.js'
import {decodeAddress} from 'ripple-address-codec'
import sha512Half from './sha512Half'
import HashPrefix from './hash-prefix'
import {SHAMap, NodeType} from './shamap'
import {encode} from 'ripple-binary-codec'
import ledgerspaces from './ledgerspaces'
const padLeftZero = (string: string, length: number): string => {
return Array(length - string.length + 1).join('0') + string
}
const intToHex = (integer: number, byteLength: number): string => {
return padLeftZero(Number(integer).toString(16), byteLength * 2)
}
const bytesToHex = (bytes: number[]): string => {
return Buffer.from(bytes).toString('hex')
}
const bigintToHex = (integerString: string | number | BigNumber, byteLength: number): string => {
const hex = (new BigNumber(integerString)).toString(16)
return padLeftZero(hex, byteLength * 2)
}
const ledgerSpaceHex = (name: string): string => {
return intToHex(ledgerspaces[name].charCodeAt(0), 2)
}
const addressToHex = (address: string): string => {
return (Buffer.from(decodeAddress(address))).toString('hex')
}
const currencyToHex = (currency: string): string => {
if (currency.length === 3) {
let bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
bytes[12] = currency.charCodeAt(0) & 0xff
bytes[13] = currency.charCodeAt(1) & 0xff
bytes[14] = currency.charCodeAt(2) & 0xff
return bytesToHex(bytes)
}
return currency
}
const addLengthPrefix = (hex: string): string => {
const length = hex.length / 2
if (length <= 192) {
return bytesToHex([length]) + hex
} else if (length <= 12480) {
const x = length - 193
return bytesToHex([193 + (x >>> 8), x & 0xff]) + hex
} else if (length <= 918744) {
const x = length - 12481
return bytesToHex([241 + (x >>> 16), x >>> 8 & 0xff, x & 0xff]) + hex
}
throw new Error('Variable integer overflow.')
}
export const computeBinaryTransactionHash = (txBlobHex: string): string => {
const prefix = HashPrefix.TRANSACTION_ID.toString(16).toUpperCase()
return sha512Half(prefix + txBlobHex)
}
export const computeTransactionHash = (txJSON: any): string => {
return computeBinaryTransactionHash(encode(txJSON))
}
export const computeBinaryTransactionSigningHash = (txBlobHex: string): string => {
const prefix = HashPrefix.TRANSACTION_SIGN.toString(16).toUpperCase()
return sha512Half(prefix + txBlobHex)
}
export const computeTransactionSigningHash = (txJSON: any): string => {
return computeBinaryTransactionSigningHash(encode(txJSON))
}
export const computeAccountHash = (address: string): string => {
return sha512Half(ledgerSpaceHex('account') + addressToHex(address))
}
export const computeSignerListHash = (address: string): string => {
return sha512Half(ledgerSpaceHex('signerList') +
addressToHex(address) +
'00000000' /* uint32(0) signer list index */)
}
export const computeOrderHash = (address: string, sequence: number): string => {
const prefix = '00' + intToHex(ledgerspaces.offer.charCodeAt(0), 1)
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
}
export const computeTrustlineHash = (address1: string, address2: string, currency: string): string => {
const address1Hex = addressToHex(address1)
const address2Hex = addressToHex(address2)
const swap = (new BigNumber(address1Hex, 16)).greaterThan(
new BigNumber(address2Hex, 16))
const lowAddressHex = swap ? address2Hex : address1Hex
const highAddressHex = swap ? address1Hex : address2Hex
const prefix = ledgerSpaceHex('rippleState')
return sha512Half(prefix + lowAddressHex + highAddressHex +
currencyToHex(currency))
}
export const computeTransactionTreeHash = (transactions: any[]): string => {
const shamap = new SHAMap()
transactions.forEach((txJSON) => {
const txBlobHex = encode(txJSON)
const metaHex = encode(txJSON.metaData)
const txHash = computeBinaryTransactionHash(txBlobHex)
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex)
shamap.addItem(txHash, data, NodeType.TRANSACTION_METADATA)
})
return shamap.hash
}
export const computeStateTreeHash = (entries: any[]): string => {
const shamap = new SHAMap()
entries.forEach((ledgerEntry) => {
const data = encode(ledgerEntry)
shamap.addItem(ledgerEntry.index, data, NodeType.ACCOUNT_STATE)
})
return shamap.hash
}
// see rippled Ledger::updateHash()
export const computeLedgerHash = (ledgerHeader): string => {
const prefix = HashPrefix.LEDGER.toString(16).toUpperCase()
return sha512Half(prefix +
intToHex(ledgerHeader.ledger_index, 4) +
bigintToHex(ledgerHeader.total_coins, 8) +
ledgerHeader.parent_hash +
ledgerHeader.transaction_hash +
ledgerHeader.account_hash +
intToHex(ledgerHeader.parent_close_time, 4) +
intToHex(ledgerHeader.close_time, 4) +
intToHex(ledgerHeader.close_time_resolution, 1) +
intToHex(ledgerHeader.close_flags, 1)
)
}
export const computeEscrowHash = (address, sequence): string => {
return sha512Half(ledgerSpaceHex('escrow') + addressToHex(address) +
intToHex(sequence, 4))
}
export const computePaymentChannelHash = (address, dstAddress, sequence): string => {
return sha512Half(ledgerSpaceHex('paychan') + addressToHex(address) +
addressToHex(dstAddress) + intToHex(sequence, 4))
}

View File

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

View File

@@ -0,0 +1,7 @@
import {createHash} from 'crypto'
const sha512Half = (hex: string): string => {
return createHash('sha512').update(Buffer.from(hex, 'hex')).digest('hex').toUpperCase().slice(0, 64)
}
export default sha512Half

173
src/common/hashes/shamap.ts Normal file
View File

@@ -0,0 +1,173 @@
import hashPrefix from './hash-prefix'
import sha512Half from './sha512Half'
const HEX_ZERO = '0000000000000000000000000000000000000000000000000000000000000000'
export enum NodeType {
INNER = 1,
TRANSACTION_NO_METADATA = 2,
TRANSACTION_METADATA = 3,
ACCOUNT_STATE = 4
}
export abstract class Node {
/**
* Abstract constructor representing a node in a SHAMap tree.
* Can be either InnerNode or Leaf.
* @constructor
*/
public constructor() {}
public addItem(_tag: string, _node: Node): void {
throw new Error('Called unimplemented virtual method SHAMapTreeNode#addItem.')
}
public get hash(): string|void {
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
}
}
export class InnerNode extends Node {
public leaves: { [slot: number]: Node }
public type: NodeType
public depth: number
public empty: boolean
/**
* Define an Inner (non-leaf) node in a SHAMap tree.
* @param {number} depth i.e. how many parent inner nodes
* @constructor
*/
public constructor(depth: number = 0) {
super()
this.leaves = {}
this.type = NodeType.INNER
this.depth = depth
this.empty = true
}
/**
* @param {string} tag equates to a ledger entry `index`
* @param {Node} node to add
* @return {void}
*/
public addItem(tag: string, node: Node): void {
const existingNode = this.getNode(parseInt(tag[this.depth],16))
if (existingNode) {
// A node already exists in this slot
if (existingNode instanceof InnerNode) {
// There is an inner node, so we need to go deeper
existingNode.addItem(tag, node)
} else if (existingNode instanceof Leaf) {
if (existingNode.tag === tag) {
// Collision
throw new Error(
'Tried to add a node to a SHAMap that was already in there.')
} else {
// Turn it into an inner node
const newInnerNode = new InnerNode(this.depth + 1)
// Parent new and existing node
newInnerNode.addItem(existingNode.tag, existingNode)
newInnerNode.addItem(tag, node)
// And place the newly created inner node in the slot
this.setNode(parseInt(tag[this.depth], 16), newInnerNode)
}
}
} else {
// Neat, we have a nice open spot for the new node
this.setNode(parseInt(tag[this.depth], 16), node)
}
}
/**
* Overwrite the node that is currently in a given slot.
* @param {number} slot a number 0-15
* @param {Node} node to place
* @return {void}
*/
public setNode(slot: number, node: Node): void {
if (slot < 0 || slot > 15) {
throw new Error ('Invalid slot: slot must be between 0-15.')
}
this.leaves[slot] = node
this.empty = false
}
/**
* Get the node that is currently in a given slot.
* @param {number} slot a number 0-15
* @return {Node}
*/
public getNode(slot: number): Node {
if (slot < 0 || slot > 15) {
throw new Error ('Invalid slot: slot must be between 0-15.')
}
return this.leaves[slot]
}
public get hash(): string {
if (this.empty) return HEX_ZERO
let hex = ''
for (let i = 0; i < 16; i++) {
hex += this.leaves[i] ? this.leaves[i].hash : HEX_ZERO
}
const prefix = hashPrefix.INNER_NODE.toString(16)
return sha512Half(prefix + hex)
}
}
export class Leaf extends Node {
public tag: string
public type: NodeType
public data: string
/**
* Leaf node in a SHAMap tree.
* @param {string} tag equates to a ledger entry `index`
* @param {string} data hex of account state, transaction etc
* @param {number} type one of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc
* @constructor
*/
public constructor(tag: string, data: string, type: NodeType) {
super()
this.tag = tag
this.type = type
this.data = data
}
public get hash(): string|void {
switch (this.type) {
case NodeType.ACCOUNT_STATE:
const leafPrefix = hashPrefix.LEAF_NODE.toString(16)
return sha512Half(leafPrefix + this.data + this.tag)
case NodeType.TRANSACTION_NO_METADATA:
const txIDPrefix = hashPrefix.TRANSACTION_ID.toString(16)
return sha512Half(txIDPrefix + this.data)
case NodeType.TRANSACTION_METADATA:
const txNodePrefix = hashPrefix.TRANSACTION_NODE.toString(16)
return sha512Half(txNodePrefix + this.data + this.tag)
default:
throw new Error('Tried to hash a SHAMap node of unknown type.')
}
}
}
export class SHAMap {
public root: InnerNode
/**
* SHAMap tree.
* @constructor
*/
public constructor() {
this.root = new InnerNode(0)
}
public addItem(tag: string, data: string, type: NodeType): void {
this.root.addItem(tag, new Leaf(tag, data, type))
}
public get hash(): string {
return this.root.hash
}
}

View File

@@ -35,7 +35,7 @@ class RangeSet {
}
addRange(start: number, end: number) {
assert(start <= end, 'invalid range')
assert.ok(start <= end, `invalid range ${start} <= ${end}`)
this.ranges = mergeIntervals(this.ranges.concat([[start, end]]))
}

View File

@@ -121,7 +121,7 @@ function loadSchemas() {
]
const titles = schemas.map(schema => schema.title)
const duplicates = _.keys(_.pickBy(_.countBy(titles), count => count > 1))
assert(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates)
assert.ok(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates)
const validator = new Validator()
// Register custom format validators that ignore undefined instances
// since jsonschema will still call the format validator on a missing

View File

@@ -6,7 +6,7 @@
"properties": {
"address": {
"$ref": "address",
"description": "The Ripple address of the account to get the balance sheet of."
"description": "The XRP Ledger address of the account to get the balance sheet of."
},
"options": {
"properties": {

View File

@@ -6,7 +6,7 @@
"properties": {
"address": {
"$ref": "address",
"description": "The Ripple address of the account to get open orders for."
"description": "The XRP Ledger address of the account to get open orders for."
},
"options": {
"description": "Options that determine what orders will be returned.",

View File

@@ -12,7 +12,7 @@
"properties": {
"address": {
"$ref": "address",
"description": "The Ripple address of the account where funds will come from."
"description": "The XRP Ledger address of the account where funds will come from."
},
"amount": {
"$ref": "laxAmount",
@@ -49,7 +49,7 @@
"properties": {
"address": {
"$ref": "address",
"description": "The address to send to."
"description": "An address representing the destination of the transaction."
},
"amount": {
"$ref": "laxLaxAmount",

View File

@@ -22,7 +22,7 @@
},
"minLedgerVersion": {
"$ref": "ledgerVersion",
"description": "Return only transactions in this ledger verion or higher."
"description": "Return only transactions in this ledger version or higher."
},
"maxLedgerVersion": {
"$ref": "ledgerVersion",

View File

@@ -10,7 +10,7 @@
"secret": {
"type": "string",
"format": "secret",
"description": "The secret of the account that is initiating the transaction. (This field is exclusive with keypair)."
"description": "The secret of the account that is initiating the transaction. (This field cannot be used with keypair)."
},
"keypair": {
"type": "object",
@@ -24,7 +24,7 @@
"description": "The uppercase hexadecimal representation of the secp256k1 or Ed25519 public key."
}
},
"description": "The private and public key of the account that is initiating the transaction. (This field is exclusive with secret).",
"description": "The private and public key of the account that is initiating the transaction. (This field cannot be used with secret).",
"required": ["privateKey", "publicKey"],
"additionalProperties": false
},

View File

@@ -13,7 +13,7 @@
"$ref": "currency"
},
"counterparty": {
"description": "The Ripple address of the account that owes or is owed the funds (omitted if `currency` is \"XRP\" or \"drops\")",
"description": "The XRP Ledger address of the account that owes or is owed the funds (omitted if `currency` is \"XRP\" or \"drops\")",
"$ref": "address"
}
},

View File

@@ -14,7 +14,7 @@
"$ref": "currency"
},
"counterparty": {
"description": "The Ripple address of the account that owes or is owed the funds.",
"description": "The XRP Ledger address of the account that owes or is owed the funds.",
"$ref": "address"
}
},

View File

@@ -6,7 +6,7 @@
"properties": {
"address": {
"$ref": "address",
"description": "The address to receive at."
"description": "An address representing the destination of the transaction."
},
"tag": {"$ref": "tag"}
},

View File

@@ -5,7 +5,7 @@
"properties": {
"address": {
"$ref": "address",
"description": "The address to receive at."
"description": "An address representing the destination of the transaction."
},
"amount": {
"$ref": "laxAmount",

View File

@@ -9,7 +9,7 @@
},
"maxAmount": {
"$ref": "laxAmount",
"description": "The maximum amount to send. (This field is exclusive with source.amount)"
"description": "The maximum amount to send. (This field cannot be used with source.amount)"
},
"tag": {"$ref": "tag"}
},

View File

@@ -5,11 +5,11 @@
"properties": {
"address": {
"$ref": "address",
"description": "The address to send to."
"description": "An address representing the destination of the transaction."
},
"minAmount": {
"$ref": "laxAmount",
"description": "The minimum amount to be delivered. (This field is exclusive with destination.amount)"
"description": "The minimum amount to be delivered. (This field cannot be used with destination.amount)"
},
"tag": {"$ref": "tag"}
},

View File

@@ -94,7 +94,7 @@
"maxItems": 8
}
},
"required": ["threshold", "weights"],
"required": ["threshold"],
"additionalProperties": false
},
"transferRate": {

View File

@@ -9,7 +9,7 @@
},
"amount": {
"$ref": "laxAmount",
"description": "An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with source.maxAmount)"
"description": "An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)"
},
"tag": {"$ref": "tag"}
},

View File

@@ -103,6 +103,7 @@ function toRippledAmount(amount: Amount): RippledAmount {
function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
if (typeof obj === 'object') {
const accumulator = Array.isArray(obj) ? [] : {}
let newKey
return _.reduce(obj, (result, value, key) => {
newKey = key
@@ -113,7 +114,7 @@ function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
}
result[newKey] = convertKeysFromSnakeCaseToCamelCase(value)
return result
}, {})
}, accumulator)
}
return obj
}

View File

@@ -1,84 +0,0 @@
/* eslint-disable new-cap */
import * as assert from 'assert'
import * as _ from 'lodash'
import * as jayson from 'jayson'
import {RippleAPI} from './api'
/* istanbul ignore next */
function createHTTPServer(options, httpPort) {
const rippleAPI = new RippleAPI(options)
const methodNames = _.filter(_.keys(RippleAPI.prototype), k => {
return typeof RippleAPI.prototype[k] === 'function'
&& k !== 'connect'
&& k !== 'disconnect'
&& k !== 'constructor'
&& k !== 'RippleAPI'
})
function applyPromiseWithCallback(fnName, callback, args_) {
try {
let args = args_
if (!_.isArray(args_)) {
const fnParameters = jayson.Utils.getParameterNames(rippleAPI[fnName])
args = fnParameters.map(name => args_[name])
const defaultArgs = _.omit(args_, fnParameters)
assert(_.size(defaultArgs) <= 1,
'Function must have no more than one default argument')
if (_.size(defaultArgs) > 0) {
args.push(defaultArgs[_.keys(defaultArgs)[0]])
}
}
Promise.resolve(rippleAPI[fnName](...args))
.then(res => callback(null, res))
.catch(err => {
callback({code: 99, message: err.message, data: {name: err.name}})
})
} catch (err) {
callback({code: 99, message: err.message, data: {name: err.name}})
}
}
const methods = {}
_.forEach(methodNames, fn => {
methods[fn] = jayson.Method((args, cb) => {
applyPromiseWithCallback(fn, cb, args)
}, {collect: true})
})
const server = jayson.server(methods)
let httpServer = null
return {
server: server,
start: function() {
if (httpServer !== null) {
return Promise.reject('Already started')
}
return new Promise(resolve => {
rippleAPI.connect().then(() => {
httpServer = server.http()
httpServer.listen(httpPort, resolve)
})
})
},
stop: function() {
if (httpServer === null) {
return Promise.reject('Not started')
}
return new Promise(resolve => {
rippleAPI.disconnect()
httpServer.close(() => {
httpServer = null
resolve()
})
})
}
}
}
export {
createHTTPServer
}

View File

@@ -1,7 +1,7 @@
import * as assert from 'assert'
function parseOrderCancellation(tx: any): object {
assert(tx.TransactionType === 'OfferCancel')
assert.ok(tx.TransactionType === 'OfferCancel')
return {
orderSequence: tx.OfferSequence
}

View File

@@ -8,7 +8,7 @@ export type FormattedCheckCancel = {
}
function parseCheckCancel(tx: any): FormattedCheckCancel {
assert(tx.TransactionType === 'CheckCancel')
assert.ok(tx.TransactionType === 'CheckCancel')
return removeUndefined({
checkID: tx.CheckID

View File

@@ -23,7 +23,7 @@ export type FormattedCheckCash = {
}
function parseCheckCash(tx: any): FormattedCheckCash {
assert(tx.TransactionType === 'CheckCash')
assert.ok(tx.TransactionType === 'CheckCash')
return removeUndefined({
checkID: tx.CheckID,

View File

@@ -24,7 +24,7 @@ export type FormattedCheckCreate = {
}
function parseCheckCreate(tx: any): FormattedCheckCreate {
assert(tx.TransactionType === 'CheckCreate')
assert.ok(tx.TransactionType === 'CheckCreate')
return removeUndefined({
destination: tx.Destination,

View File

@@ -10,7 +10,7 @@ export type FormattedDepositPreauth = {
}
function parseDepositPreauth(tx: any): FormattedDepositPreauth {
assert(tx.TransactionType === 'DepositPreauth')
assert.ok(tx.TransactionType === 'DepositPreauth')
return removeUndefined({
authorize: tx.Authorize,

View File

@@ -3,7 +3,7 @@ import {parseMemos} from './utils'
import {removeUndefined} from '../../common'
function parseEscrowCancellation(tx: any): object {
assert(tx.TransactionType === 'EscrowCancel')
assert.ok(tx.TransactionType === 'EscrowCancel')
return removeUndefined({
memos: parseMemos(tx),

View File

@@ -4,7 +4,7 @@ import {parseTimestamp, parseMemos} from './utils'
import {removeUndefined} from '../../common'
function parseEscrowCreation(tx: any): object {
assert(tx.TransactionType === 'EscrowCreate')
assert.ok(tx.TransactionType === 'EscrowCreate')
return removeUndefined({
amount: parseAmount(tx.Amount).value,

View File

@@ -3,7 +3,7 @@ import {parseMemos} from './utils'
import {removeUndefined} from '../../common'
function parseEscrowExecution(tx: any): object {
assert(tx.TransactionType === 'EscrowFinish')
assert.ok(tx.TransactionType === 'EscrowFinish')
return removeUndefined({
memos: parseMemos(tx),

View File

@@ -10,7 +10,7 @@ import {
const flags = txFlags.OfferCreate
function parseOrder(tx: OfferCreateTransaction): FormattedOrderSpecification {
assert(tx.TransactionType === 'OfferCreate')
assert.ok(tx.TransactionType === 'OfferCreate')
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(tx.TakerGets)

View File

@@ -4,7 +4,7 @@ import parseAmount from './amount'
const claimFlags = txFlags.PaymentChannelClaim
function parsePaymentChannelClaim(tx: any): object {
assert(tx.TransactionType === 'PaymentChannelClaim')
assert.ok(tx.TransactionType === 'PaymentChannelClaim')
return removeUndefined({
channel: tx.Channel,

View File

@@ -4,7 +4,7 @@ import {removeUndefined} from '../../common'
import parseAmount from './amount'
function parsePaymentChannelCreate(tx: any): object {
assert(tx.TransactionType === 'PaymentChannelCreate')
assert.ok(tx.TransactionType === 'PaymentChannelCreate')
return removeUndefined({
amount: parseAmount(tx.Amount).value,

View File

@@ -4,7 +4,7 @@ import {removeUndefined} from '../../common'
import parseAmount from './amount'
function parsePaymentChannelFund(tx: any): object {
assert(tx.TransactionType === 'PaymentChannelFund')
assert.ok(tx.TransactionType === 'PaymentChannelFund')
return removeUndefined({
channel: tx.Channel,

View File

@@ -19,7 +19,7 @@ function removeGenericCounterparty(amount, address) {
// Payment specification
function parsePayment(tx: any): object {
assert(tx.TransactionType === 'Payment')
assert.ok(tx.TransactionType === 'Payment')
const source = {
address: tx.Account,

View File

@@ -7,7 +7,7 @@ import parseFields from './fields'
function getAccountRootModifiedNode(tx: any) {
const modifiedNodes = tx.meta.AffectedNodes.filter(node =>
node.ModifiedNode.LedgerEntryType === 'AccountRoot')
assert(modifiedNodes.length === 1)
assert.ok(modifiedNodes.length === 1)
return modifiedNodes[0].ModifiedNode
}
@@ -51,7 +51,7 @@ function parseFlags(tx: any): any {
function parseSettings(tx: any) {
const txType = tx.TransactionType
assert(txType === 'AccountSet' || txType === 'SetRegularKey' ||
assert.ok(txType === 'AccountSet' || txType === 'SetRegularKey' ||
txType === 'SignerListSet')
return _.assign({}, parseFlags(tx), parseFields(tx))

View File

@@ -14,7 +14,7 @@ function parseFlag(flagsValue, trueValue, falseValue) {
}
function parseTrustline(tx: any): object {
assert(tx.TransactionType === 'TrustSet')
assert.ok(tx.TransactionType === 'TrustSet')
return removeUndefined({
limit: tx.LimitAmount.value,

View File

@@ -1,5 +1,5 @@
import * as _ from 'lodash'
import transactionParser = require('ripple-lib-transactionparser')
import transactionParser from 'ripple-lib-transactionparser'
import BigNumber from 'bignumber.js'
import * as common from '../../common'
import parseAmount from './amount'

View File

@@ -1,6 +1,6 @@
import * as _ from 'lodash'
import binary = require('ripple-binary-codec')
const {computeTransactionHash} = require('ripple-hashes')
import binary from 'ripple-binary-codec';
import {computeTransactionHash} from '../common/hashes'
import * as utils from './utils'
import parseTransaction from './parse/transaction'
import getTransaction from './transaction'

View File

@@ -4,7 +4,7 @@ import * as common from '../common'
import {Connection} from '../common'
import {FormattedTransactionType} from '../transaction/types'
import {Issue} from '../common/types/objects'
import {RippleAPI} from '..'
import {RippleAPI} from '..'
export type RecursiveData = {
marker: string,
@@ -14,7 +14,7 @@ export type RecursiveData = {
export type Getter = (marker?: string, limit?: number) => Promise<RecursiveData>
function clamp(value: number, min: number, max: number): number {
assert(min <= max, 'Illegal clamp bounds')
assert.ok(min <= max, 'Illegal clamp bounds')
return Math.min(Math.max(value, min), max)
}

View File

@@ -1,4 +1,4 @@
import keypairs = require('ripple-keypairs')
import keypairs from 'ripple-keypairs'
import * as common from '../common'
const {errors, validate} = common

View File

@@ -1,5 +1,5 @@
import * as _ from 'lodash'
import hashes = require('ripple-hashes')
import {computeLedgerHash, computeTransactionTreeHash, computeStateTreeHash} from '../common/hashes'
import * as common from '../common'
function convertLedgerHeader(header): any {
@@ -22,11 +22,11 @@ function convertLedgerHeader(header): any {
function hashLedgerHeader(ledgerHeader) {
const header = convertLedgerHeader(ledgerHeader)
return hashes.computeLedgerHash(header)
return computeLedgerHash(header)
}
function computeTransactionHash(ledger, version,
options: ComputeLedgerHashOptions) {
function computeTransactionHash(ledger,
options: ComputeLedgerHeaderHashOptions) {
let transactions: any[]
if (ledger.rawTransactions) {
transactions = JSON.parse(ledger.rawTransactions)
@@ -56,7 +56,7 @@ function computeTransactionHash(ledger, version,
tx.meta ? {metaData: tx.meta} : {})
return renameMeta
})
const transactionHash = hashes.computeTransactionTreeHash(txs, version)
const transactionHash = computeTransactionTreeHash(txs)
if (ledger.transactionHash !== undefined
&& ledger.transactionHash !== transactionHash) {
throw new common.errors.ValidationError('transactionHash in header'
@@ -68,8 +68,8 @@ function computeTransactionHash(ledger, version,
return transactionHash
}
function computeStateHash(ledger, version,
options: ComputeLedgerHashOptions) {
function computeStateHash(ledger,
options: ComputeLedgerHeaderHashOptions) {
if (ledger.rawState === undefined) {
if (options.computeTreeHashes) {
throw new common.errors.ValidationError('rawState'
@@ -78,7 +78,7 @@ function computeStateHash(ledger, version,
return ledger.stateHash
}
const state = JSON.parse(ledger.rawState)
const stateHash = hashes.computeStateTreeHash(state, version)
const stateHash = computeStateTreeHash(state)
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
throw new common.errors.ValidationError('stateHash in header'
+ ' does not match computed hash of state')
@@ -86,20 +86,17 @@ function computeStateHash(ledger, version,
return stateHash
}
const sLCF_SHAMapV2 = 0x02
export type ComputeLedgerHashOptions = {
export type ComputeLedgerHeaderHashOptions = {
computeTreeHashes?: boolean
}
function computeLedgerHash(ledger: any,
options: ComputeLedgerHashOptions = {}): string {
const version = ((ledger.closeFlags & sLCF_SHAMapV2) === 0) ? 1 : 2
function computeLedgerHeaderHash(ledger: any,
options: ComputeLedgerHeaderHashOptions = {}): string {
const subhashes = {
transactionHash: computeTransactionHash(ledger, version, options),
stateHash: computeStateHash(ledger, version, options)
transactionHash: computeTransactionHash(ledger, options),
stateHash: computeStateHash(ledger, options)
}
return hashLedgerHeader(_.assign({}, ledger, subhashes))
}
export default computeLedgerHash
export default computeLedgerHeaderHash

View File

@@ -1,6 +1,6 @@
import * as common from '../common'
import keypairs = require('ripple-keypairs')
import binary = require('ripple-binary-codec')
import keypairs from 'ripple-keypairs'
import binary from 'ripple-binary-codec'
const {validate, xrpToDrops} = common
function signPaymentChannelClaim(channel: string, amount: string,

View File

@@ -1,5 +1,5 @@
import keypairs = require('ripple-keypairs')
import binary = require('ripple-binary-codec')
import keypairs from 'ripple-keypairs'
import binary from 'ripple-binary-codec'
import {validate, xrpToDrops} from '../common'
function verifyPaymentChannelClaim(channel: string, amount: string,

View File

@@ -1,10 +1,10 @@
import * as _ from 'lodash'
import binary = require('ripple-binary-codec')
import binary from 'ripple-binary-codec'
import * as utils from './utils'
import BigNumber from 'bignumber.js'
import {decodeAddress} from 'ripple-address-codec'
import {validate} from '../common'
import {computeBinaryTransactionHash} from 'ripple-hashes'
import {computeBinaryTransactionHash} from '../common/hashes'
function addressToBigNumber(address) {
const hex = (Buffer.from(decodeAddress(address))).toString('hex')

View File

@@ -10,7 +10,7 @@ import {RippleAPI} from '..'
function setTransactionFlags(txJSON: utils.TransactionJSON, values: FormattedSettings) {
const keys = Object.keys(values)
assert(keys.length === 1, 'ERROR: can only set one setting per transaction')
assert.ok(keys.length === 1, 'ERROR: can only set one setting per transaction')
const flagName = keys[0]
const value = values[flagName]
const index = AccountFlagIndices[flagName]
@@ -89,12 +89,17 @@ function createSettingsTransactionWithoutMemos(
}
if (settings.signers !== undefined) {
return {
const setSignerList = {
TransactionType: 'SignerListSet',
Account: account,
SignerQuorum: settings.signers.threshold,
SignerEntries: settings.signers.weights.map(formatSignerEntry)
SignerEntries: [],
SignerQuorum: settings.signers.threshold
};
if (settings.signers.weights !== undefined) {
setSignerList.SignerEntries = settings.signers.weights.map(formatSignerEntry);
}
return setSignerList;
}
const txJSON: SettingsTransaction = {

View File

@@ -1,17 +1,18 @@
import isEqual from 'lodash.isequal'
import * as utils from './utils'
import keypairs = require('ripple-keypairs')
import binary = require('ripple-binary-codec')
import {computeBinaryTransactionHash} from 'ripple-hashes'
import keypairs from 'ripple-keypairs'
import binaryCodec from 'ripple-binary-codec'
import {computeBinaryTransactionHash} from '../common/hashes'
import {SignOptions, KeyPair} from './types'
import {BigNumber} from 'bignumber.js'
import {xrpToDrops} from '../common'
import {RippleAPI} from '..'
const validate = utils.common.validate
const validate = utils.common.validate
function computeSignature(tx: object, privateKey: string, signAs?: string) {
const signingData = signAs
? binary.encodeForMultisigning(tx, signAs)
: binary.encodeForSigning(tx)
? binaryCodec.encodeForMultisigning(tx, signAs)
: binaryCodec.encodeForSigning(tx)
return keypairs.sign(signingData, privateKey)
}
@@ -32,7 +33,155 @@ function signWithKeypair(
)
}
const fee = new BigNumber(tx.Fee)
checkFee(api, tx.Fee)
const txToSignAndEncode = Object.assign({}, tx)
txToSignAndEncode.SigningPubKey = options.signAs ? '' : keypair.publicKey
if (options.signAs) {
const signer = {
Account: options.signAs,
SigningPubKey: keypair.publicKey,
TxnSignature: computeSignature(txToSignAndEncode, keypair.privateKey, options.signAs)
}
txToSignAndEncode.Signers = [{Signer: signer}]
} else {
txToSignAndEncode.TxnSignature = computeSignature(txToSignAndEncode, keypair.privateKey)
}
const serialized = binaryCodec.encode(txToSignAndEncode)
checkTxSerialization(serialized, tx)
return {
signedTransaction: serialized,
id: computeBinaryTransactionHash(serialized)
}
}
/**
* Compares two objects and creates a diff.
*
* @param a An object to compare.
* @param b The other object to compare with.
*
* @returns An object containing the differences between the two objects.
*/
function objectDiff(a: object, b: object): object {
const diffs = {}
// Compare two items and push non-matches to object
const compare = function (i1: any, i2: any, k: string): void {
const type1 = Object.prototype.toString.call(i1)
const type2 = Object.prototype.toString.call(i2)
if (type2 === '[object Undefined]') {
diffs[k] = null // Indicate that the item has been removed
return
}
if (type1 !== type2) {
diffs[k] = i2 // Indicate that the item has changed types
return
}
if (type1 === '[object Object]') {
const objDiff = objectDiff(i1, i2)
if (Object.keys(objDiff).length > 0) {
diffs[k] = objDiff
}
return
}
if (type1 === '[object Array]') {
if (!isEqual(i1, i2)) {
diffs[k] = i2 // If arrays do not match, add second item to diffs
}
return
}
if (type1 === '[object Function]') {
if (i1.toString() !== i2.toString()) {
diffs[k] = i2 // If functions differ, add second one to diffs
}
return
}
if (i1 !== i2) {
diffs[k] = i2
}
}
// Check items in first object
for (const key in a) {
if (a.hasOwnProperty(key)) {
compare(a[key], b[key], key)
}
}
// Get items that are in the second object but not the first
for (const key in b) {
if (b.hasOwnProperty(key)) {
if (!a[key] && a[key] !== b[key]) {
diffs[key] = b[key]
}
}
}
return diffs
}
/**
* Decode a serialized transaction, remove the fields that are added during the signing process,
* and verify that it matches the transaction prior to signing.
*
* @param {string} serialized A signed and serialized transaction.
* @param {utils.TransactionJSON} tx The transaction prior to signing.
*
* @returns {void} This method does not return a value, but throws an error if the check fails.
*/
function checkTxSerialization(serialized: string, tx: utils.TransactionJSON): void {
// Decode the serialized transaction:
const decoded = binaryCodec.decode(serialized)
// ...And ensure it is equal to the original tx, except:
// - It must have a TxnSignature or Signers (multisign).
if (!decoded.TxnSignature && !decoded.Signers) {
throw new utils.common.errors.ValidationError(
'Serialized transaction must have a TxnSignature or Signers property'
)
}
// - We know that the original tx did not have TxnSignature, so we should delete it:
delete decoded.TxnSignature
// - We know that the original tx did not have Signers, so if it exists, we should delete it:
delete decoded.Signers
// - If SigningPubKey was not in the original tx, then we should delete it.
// But if it was in the original tx, then we should ensure that it has not been changed.
if (!tx.SigningPubKey) {
delete decoded.SigningPubKey
}
if (!isEqual(decoded, tx)) {
const error = new utils.common.errors.ValidationError(
'Serialized transaction does not match original txJSON. See `error.data`'
)
error.data = {
decoded,
tx,
diff: objectDiff(tx, decoded)
}
throw error
}
}
/**
* Check that a given transaction fee does not exceed maxFeeXRP (in drops).
*
* See https://xrpl.org/rippleapi-reference.html#parameters
*
* @param {RippleAPI} api A RippleAPI instance.
* @param {string} txFee The transaction fee in drops, encoded as a string.
*
* @returns {void} This method does not return a value, but throws an error if the check fails.
*/
function checkFee(api: RippleAPI, txFee: string): void {
const fee = new BigNumber(txFee)
const maxFeeDrops = xrpToDrops(api._maxFeeXRP)
if (fee.greaterThan(maxFeeDrops)) {
throw new utils.common.errors.ValidationError(
@@ -40,25 +189,6 @@ function signWithKeypair(
'To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor.'
)
}
tx.SigningPubKey = options.signAs ? '' : keypair.publicKey
if (options.signAs) {
const signer = {
Account: options.signAs,
SigningPubKey: keypair.publicKey,
TxnSignature: computeSignature(tx, keypair.privateKey, options.signAs)
}
tx.Signers = [{Signer: signer}]
} else {
tx.TxnSignature = computeSignature(tx, keypair.privateKey)
}
const serialized = binary.encode(tx)
return {
signedTransaction: serialized,
id: computeBinaryTransactionHash(serialized)
}
}
function sign(

View File

@@ -34,7 +34,17 @@ function formatPrepareResponse(txJSON: any): Prepare {
}
}
function setCanonicalFlag(txJSON) {
/**
* Set the `tfFullyCanonicalSig` flag on a transaction.
*
* See https://xrpl.org/transaction-malleability.html
*
* @param {TransactionJSON} txJSON The transaction object to modify.
* This method will modify object's `Flags` property, or add it if it does not exist.
*
* @returns {void} This method mutates the original txJSON and does not return a value.
*/
function setCanonicalFlag(txJSON: TransactionJSON): void {
txJSON.Flags |= txFlags.Universal.FullyCanonicalSig
// JavaScript converts operands to 32-bit signed ints before doing bitwise
@@ -58,6 +68,11 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
'" exists in instance when not allowed'))
}
// To remove the signer list, SignerEntries field should be omitted.
if (txJSON['SignerQuorum'] === 0) {
delete txJSON.SignerEntries
}
const account = txJSON.Account
setCanonicalFlag(txJSON)
@@ -187,5 +202,6 @@ export {
convertStringToHex,
convertMemo,
prepareTransaction,
common
common,
setCanonicalFlag
}

View File

@@ -1767,19 +1767,15 @@ describe('RippleAPI', function () {
};
});
it('prepareSettings - signers no weights', function (done) {
it('prepareSettings - signers no weights', function () {
const settings = requests.prepareSettings.signers.noWeights;
try {
this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(prepared => {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.settings.signers requires property "weights"');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
const localInstructions = _.defaults({
signersCount: 1
}, instructionsWithMaxLedgerVersionOffset);
return this.api.prepareSettings(
address, settings, localInstructions).then(
_.partial(checkResult, responses.prepareSettings.noWeights,
'prepare'));
});
it('prepareSettings - fee for multisign', function () {
@@ -1792,6 +1788,17 @@ describe('RippleAPI', function () {
'prepare'));
});
it('prepareSettings - no signer list', function () {
const settings = requests.prepareSettings.noSignerEntries;
const localInstructions = _.defaults({
signersCount: 1
}, instructionsWithMaxLedgerVersionOffset);
return this.api.prepareSettings(
address, settings, localInstructions).then(
_.partial(checkResult, responses.prepareSettings.noSignerList,
'prepare'));
});
it('prepareSettings - invalid', function (done) {
// domain must be a string
const settings = Object.assign({},
@@ -2601,6 +2608,146 @@ describe('RippleAPI', function () {
assert.deepEqual(signature, responses.sign.signAs);
});
it('sign - succeeds - prepared payment', async function () {
const payment = await this.api.preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', {
source: {
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
maxAmount: {
value: '1',
currency: 'drops'
}
},
destination: {
address: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
amount: {
value: '1',
currency: 'drops'
}
}
});
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const result = this.api.sign(payment.txJSON, secret);
const expectedResult = {
signedTransaction:
'12000022800000002400000017201B008694F261400000000000000168400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100A9C91D4CFAE45686146EE0B56D4C53A2E7C2D672FB834D43E0BE2D2E9106519A022075DDA2F92DE552B0C45D83D4E6D35889B3FBF51BFBBD9B25EBF70DE3C96D0D6681145E7B112523F68D2F5E879DB4EAC51C6698A693048314FDB08D07AAA0EB711793A3027304D688E10C3648',
id:
'88D6B913C66279EA31ADC25C5806C48B2D4E5680261666790A736E1961217700'
};
assert.deepEqual(result, expectedResult);
schemaValidator.schemaValidate('sign', result);
});
it('sign - succeeds - no flags', async function () {
const txJSON = '{"TransactionType":"Payment","Account":"r45Rev1EXGxy2hAUmJPCne97KUE7qyrD3j","Destination":"rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r","Amount":"20000000","Sequence":1,"Fee":"12"}';
const secret = 'shotKgaEotpcYsshSE39vmSnBDRim';
const result = this.api.sign(txJSON, secret);
const expectedResult = {
signedTransaction:
'1200002400000001614000000001312D0068400000000000000C7321022B05847086686F9D0499B13136B94AD4323EE1B67D4C429ECC987AB35ACFA34574473045022100C104B7B97C31FACA4597E7D6FCF13BD85BD11375963A62A0AC45B0061236E39802207784F157F6A98DFC85B051CDDF61CC3084C4F5750B82674801C8E9950280D1998114EE3046A5DDF8422C40DDB93F1D522BB4FE6419158314FDB08D07AAA0EB711793A3027304D688E10C3648',
id:
'0596925967F541BF332FF6756645B2576A9858414B5B363DC3D34915BE8A70D6'
};
const decoded = binary.decode(result.signedTransaction);
assert(decoded.Flags === undefined, `Flags = ${decoded.Flags}, should be undefined`);
assert.deepEqual(result, expectedResult);
schemaValidator.schemaValidate('sign', result);
});
it('sign - throws when encoded tx does not match decoded tx - prepared payment', async function () {
const payment = await this.api.preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', {
source: {
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
maxAmount: {
value: '1.1234567',
currency: 'drops'
}
},
destination: {
address: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
amount: {
value: '1.1234567',
currency: 'drops'
}
}
});
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
assert.throws(
() => {
this.api.sign(payment.txJSON, secret);
},
/^Error: 1\.1234567 is an illegal amount/
);
});
it('sign - throws when encoded tx does not match decoded tx - prepared order', async function () {
const order = {
direction: 'sell',
quantity: {
currency: 'USD',
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '3.140000'
},
totalPrice: {
currency: 'XRP',
value: '31415'
}
};
const prepared = await this.api.prepareOrder('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', order, {
sequence: 123
});
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
try {
this.api.sign(prepared.txJSON, secret);
return Promise.reject(new Error('api.sign should have thrown'));
} catch (error) {
assert.equal(error.name, 'ValidationError');
assert.equal(error.message, 'Serialized transaction does not match original txJSON. See `error.data`');
assert.deepEqual(error.data.diff, {
TakerGets: {
value: '3.14'
}
});
}
});
it('sign - throws when encoded tx does not match decoded tx - AccountSet', function () {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const request = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"1.2\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
"instructions": {
"fee": "0.0000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
assert.throws(
() => {
this.api.sign(request.txJSON, secret);
},
/Error: 1\.2 is an illegal amount/
);
});
it('sign - throws when encoded tx does not match decoded tx - higher fee', function () {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const request = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"1123456.7\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
"instructions": {
"fee": "1.1234567",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
assert.throws(
() => {
this.api.sign(request.txJSON, secret);
},
/Error: 1123456\.7 is an illegal amount/
);
});
it('sign - throws when Fee exceeds maxFeeXRP (in drops)', function () {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const request = {

View File

@@ -22,6 +22,7 @@ module.exports = {
},
prepareSettings: {
domain: require('./prepare-settings'),
noSignerEntries: require('./prepare-settings-no-signer-entries'),
signers: {
normal: require('./prepare-settings-signers'),
noThreshold: require('./prepare-settings-signers-no-threshold'),

View File

@@ -0,0 +1,5 @@
{
"signers": {
"threshold": 0
}
}

View File

@@ -122,7 +122,9 @@ module.exports = {
noInstructions: require('./prepare-settings-no-instructions.json'),
signed: require('./prepare-settings-signed.json'),
noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json'),
signers: require('./prepare-settings-signers.json')
signers: require('./prepare-settings-signers.json'),
noSignerList: require('./prepare-settings-no-signer-list.json'),
noWeights: require('./prepare-settings-no-weight.json')
},
prepareCheckCreate: {
normal: require('./prepare-check-create'),

View File

@@ -0,0 +1,8 @@
{
"txJSON": "{\"TransactionType\":\"SignerListSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerQuorum\":0,\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"24\",\"Sequence\":23}",
"instructions": {
"fee": "0.000024",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}

View File

@@ -0,0 +1,8 @@
{
"txJSON": "{\"TransactionType\":\"SignerListSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerQuorum\":2,\"SignerEntries\":[],\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"24\",\"Sequence\":23}",
"instructions": {
"fee": "0.000024",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

127
test/hashes-test.js Normal file
View File

@@ -0,0 +1,127 @@
/* eslint-disable max-len, valid-jsdoc */
'use strict';
var assert = require('assert');
var fs = require('fs');
var hashes = require('../src/common/hashes');
/**
* @param ledgerIndex {Number}
* Expects a corresponding ledger dump in $repo/test/fixtures/rippled folder
*/
function createLedgerTest(ledgerIndex) {
describe(String(ledgerIndex), function() {
var path = __dirname + '/fixtures/rippled/ledger-full-' + ledgerIndex + '.json';
var ledgerRaw = fs.readFileSync(path);
var ledgerJSON = JSON.parse(ledgerRaw);
var hasAccounts = Array.isArray(ledgerJSON.accountState)
&& ledgerJSON.accountState.length > 0;
if (hasAccounts) {
it('has account_hash of ' + ledgerJSON.account_hash, function() {
assert.equal(ledgerJSON.account_hash,
hashes.computeStateTreeHash(ledgerJSON.accountState, 1));
});
}
it('has transaction_hash of ' + ledgerJSON.transaction_hash, function() {
assert.equal(ledgerJSON.transaction_hash,
hashes.computeTransactionTreeHash(ledgerJSON.transactions, 1));
});
});
}
describe('Ledger', function() {
// This is the first recorded ledger with a non empty transaction set
createLedgerTest(38129);
// Because, why not.
createLedgerTest(40000);
// 1311 AffectedNodes, no accounts
createLedgerTest(7501326);
describe('calcAccountRootEntryHash', function() {
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
var expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
var actualEntryHash = hashes.computeAccountHash(account);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
describe('calcRippleStateEntryHash', function() {
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function() {
var account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
var account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
var currency = 'USD';
var expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
var actualEntryHash1 = hashes.computeTrustlineHash(
account1, account2, currency);
var actualEntryHash2 = hashes.computeTrustlineHash(
account2, account1, currency);
assert.equal(actualEntryHash1, expectedEntryHash);
assert.equal(actualEntryHash2, expectedEntryHash);
});
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function() {
var account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
var account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
var currency = 'UAM';
var expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
var actualEntryHash1 = hashes.computeTrustlineHash(
account1, account2, currency);
var actualEntryHash2 = hashes.computeTrustlineHash(
account2, account1, currency);
assert.equal(actualEntryHash1, expectedEntryHash);
assert.equal(actualEntryHash2, expectedEntryHash);
});
});
describe('calcOfferEntryHash', function() {
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function() {
var account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
var sequence = 137;
var expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
var actualEntryHash = hashes.computeOrderHash(account, sequence);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
describe('computeSignerListHash', function() {
it('will calculate the SignerList index for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw', function() {
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
var expectedEntryHash = '778365D5180F5DF3016817D1F318527AD7410D83F8636CF48C43E8AF72AB49BF';
var actualEntryHash = hashes.computeSignerListHash(account);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
describe('calcEscrowEntryHash', function() {
it('will calculate the Escrow entry hash for rDx69ebzbowuqztksVDmZXjizTd12BVr4x, sequence 84', function() {
var account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x';
var sequence = 84;
var expectedEntryHash = '61E8E8ED53FA2CEBE192B23897071E9A75217BF5A410E9CB5B45AAB7AECA567A';
var actualEntryHash = hashes.computeEscrowHash(account, sequence);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
describe('calcPaymentChannelEntryHash', function() {
it('will calculate the PaymentChannel entry hash for rDx69ebzbowuqztksVDmZXjizTd12BVr4x and rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso, sequence 82', function() {
var account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x';
var dstAccount = 'rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso'
var sequence = 82;
var expectedEntryHash = 'E35708503B3C3143FB522D749AAFCC296E8060F0FB371A9A56FAE0B1ED127366';
var actualEntryHash = hashes.computePaymentChannelHash(account, dstAccount, sequence);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
});

View File

@@ -1,202 +0,0 @@
/* eslint-disable max-nested-callbacks */
'use strict';
const assert = require('assert-diff');
const _ = require('lodash');
const jayson = require('jayson');
const RippleAPI = require('../../src').RippleAPI;
const createHTTPServer = require('../../src/http').createHTTPServer;
const {payTo, ledgerAccept} = require('./utils');
const apiFixtures = require('../fixtures');
const apiRequests = apiFixtures.requests;
const apiResponses = apiFixtures.responses;
const TIMEOUT = 20000; // how long before each test case times out
const serverUri = 'ws://127.0.0.1:6006';
const apiOptions = {
server: serverUri
};
const httpPort = 3000;
function createClient() {
return jayson.client.http({port: httpPort, hostname: 'localhost'});
}
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
function makePositionalParams(params) {
return params.map(value => value[_.keys(value)[0]]);
}
function makeNamedParams(params) {
return _.reduce(params, _.assign, {});
}
function random() {
return _.fill(Array(16), 0);
}
describe('http server integration tests', function() {
this.timeout(TIMEOUT);
let server = null;
let client = null;
let paymentId = null;
let newWallet = null;
function createTestInternal(testName, methodName, params, testFunc, id) {
it(testName, function() {
return new Promise((resolve, reject) => {
client.request(methodName, params, id,
(err, result) => err ? reject(err) : resolve(testFunc(result)));
});
});
}
function createTest(name, params, testFunc, id) {
createTestInternal(name + ' - positional params', name,
makePositionalParams(params), testFunc, id);
createTestInternal(name + ' - named params', name,
makeNamedParams(params), testFunc, id);
}
before(() => {
this.api = new RippleAPI({server: serverUri});
console.log('CONNECTING...');
return this.api.connect().then(() => {
console.log('CONNECTED...');
})
.then(() => ledgerAccept(this.api))
.then(() => newWallet = this.api.generateAddress())
.then(() => ledgerAccept(this.api))
.then(() => payTo(this.api, newWallet.address))
.then(paymentId_ => {
paymentId = paymentId_;
});
});
beforeEach(function() {
server = createHTTPServer(apiOptions, httpPort);
return server.start().then(() => {
this.client = createClient();
client = this.client;
});
});
afterEach(function() {
return server.stop();
});
createTest(
'getLedgerVersion',
[],
result => assert(_.isNumber(result.result))
);
createTest(
'getServerInfo',
[],
result => assert(_.isNumber(result.result.validatedLedger.ledgerVersion))
);
it('getTransaction', function() {
const params = [{id: paymentId}];
return new Promise((resolve, reject) => {
client.request('getTransaction', makePositionalParams(params),
(err, result) => {
if (err) {
reject(err);
}
assert.strictEqual(result.result.id, paymentId);
const outcome = result.result.outcome;
assert.strictEqual(outcome.result, 'tesSUCCESS');
assert.strictEqual(outcome.balanceChanges
.rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh[0].value, '-4003218.000012');
resolve(result);
});
});
});
it('getTransactions', function() {
const params = [{address: newWallet.address}, {
options: {
binary: true,
limit: 1
}
}];
return new Promise((resolve, reject) => {
client.request('getTransactions', makeNamedParams(params),
(err, result) => {
if (err) {
reject(err);
}
assert.strictEqual(result.result.length, 1);
assert.strictEqual(result.result[0].id, paymentId);
resolve(result);
});
});
});
createTest(
'prepareSettings',
[
{address},
{settings: apiRequests.prepareSettings.domain},
{instructions: {
maxFee: '0.000012',
sequence: 23,
maxLedgerVersion: 8820051
}}
],
result => {
const got = JSON.parse(result.result.txJSON);
const expected = JSON.parse(apiResponses.prepareSettings.flags.txJSON);
assert.deepEqual(got, expected);
}
);
createTest(
'sign',
[{txJSON: apiRequests.sign.normal.txJSON},
{secret: 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'}],
result => assert.deepEqual(result.result, apiResponses.sign.normal)
);
createTest(
'sign',
[{txJSON: apiRequests.sign.normal.txJSON},
{keypair: {
privateKey: '00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A',
publicKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' }}],
result => assert.deepEqual(result.result, apiResponses.sign.normal)
);
createTest(
'generateAddress',
[{options: {entropy: random()}}],
result => assert.deepEqual(result.result, apiResponses.generateAddress)
);
createTest(
'computeLedgerHash',
[{ledger: _.assign({}, apiResponses.getLedger.full,
{parentCloseTime: apiResponses.getLedger.full.closeTime})
}],
result => {
assert.strictEqual(result.result,
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E');
}
);
createTest(
'isConnected',
[],
result => assert(result.result)
);
});

71
test/shamap-test.js Normal file
View File

@@ -0,0 +1,71 @@
/* eslint-disable max-len, valid-jsdoc */
'use strict';
var assert = require('assert');
var SHAMap = require('../src/common/hashes/shamap').SHAMap;
var TYPE_TRANSACTION_NO_METADATA = require('../src/common/hashes/shamap').NodeType.TRANSACTION_NO_METADATA
var HEX_ZERO = '00000000000000000000000000000000' +
'00000000000000000000000000000000';
/**
* Generates data to hash for testing
* @param {number} v int value
* @returns {string} 64 length hex string
*/
function intToVuc(v) {
var ret = '';
for (var i = 0; i < 32; i++) {
ret += '0';
ret += v.toString(16).toUpperCase();
}
return ret;
}
/**
* @param shamap {Object}
* @param keys {Array}
* @param hashes {Array}
*/
function fillShamapTest(shamap, keys, hashes) {
for (var i = 0; i < keys.length; i++) {
var data = intToVuc(i);
shamap.addItem(keys[i].toUpperCase(), data, TYPE_TRANSACTION_NO_METADATA);
assert.equal(shamap.hash, hashes[i]);
}
}
describe('SHAMap', function() {
describe('#addItem', function() {
it('will add new nodes to v1', function() {
var keys = [
'b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8'
];
var hashesv1 = [
'B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C64B6281F7F',
'FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A8831845467FB2ECE266',
'4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51AE756795B75',
'7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC6C74F93E07',
'395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596462B0E3A3E',
'D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D98A84D9DDE4',
'76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF94361143615',
'DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA02BBE7230E5'
];
var shamapv1 = new SHAMap();
assert.equal(shamapv1.hash, HEX_ZERO);
fillShamapTest(shamapv1, keys, hashesv1);
});
});
});

View File

@@ -1,26 +1,22 @@
{
"compilerOptions": {
"pretty": true,
"lib": [
"es2017"
],
"target": "es6",
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"declarationMap": true /* Added 2019-04-13 */,
"sourceMap": true,
"noEmitOnError": true /* Added 2019-04-13 */,
"strict": true /* Enable all strict type-checking options. */,
"strictNullChecks": false,
"noImplicitAny": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": true,
"strict": true /* Enable all strict type-checking options. */,
"preserveConstEnums": false,
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"suppressImplicitAnyIndexErrors": false
}
}

1382
yarn.lock

File diff suppressed because it is too large Load Diff