mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-13 09:05:49 +00:00
Compare commits
449 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2267e599c0 | ||
|
|
687bcc8539 | ||
|
|
e64dba839c | ||
|
|
a651b6154d | ||
|
|
4b5e75a8ad | ||
|
|
bdd24bada9 | ||
|
|
6e4a9179c1 | ||
|
|
b0c38ff566 | ||
|
|
e97a3966c1 | ||
|
|
d0aa86bf97 | ||
|
|
b17eb8ed0d | ||
|
|
7dcfa0a232 | ||
|
|
05c466bebe | ||
|
|
0f7c80d76a | ||
|
|
99599ba673 | ||
|
|
85f75b4543 | ||
|
|
37a22186ca | ||
|
|
007c2e7e5c | ||
|
|
a7581b1ec8 | ||
|
|
06c662cc14 | ||
|
|
7feafaa320 | ||
|
|
1c06baf5db | ||
|
|
a188ae6ace | ||
|
|
c0db5e251a | ||
|
|
bc427204c6 | ||
|
|
976de00242 | ||
|
|
a098b0cf02 | ||
|
|
70d28fc15e | ||
|
|
815eed427f | ||
|
|
55e74671a7 | ||
|
|
e595291c95 | ||
|
|
47db68ff45 | ||
|
|
e227a541e7 | ||
|
|
7a88165e16 | ||
|
|
5ed148af75 | ||
|
|
1329de5344 | ||
|
|
a003d261ea | ||
|
|
37065123e5 | ||
|
|
44dd0ca4a3 | ||
|
|
15304237d1 | ||
|
|
acfc1bbb8c | ||
|
|
75a732e0ad | ||
|
|
a46bff3c5e | ||
|
|
8fcb694f1d | ||
|
|
ef758b17b1 | ||
|
|
b9646c51b8 | ||
|
|
e2f59ce975 | ||
|
|
c3ceba1f05 | ||
|
|
573b38cc19 | ||
|
|
5ca5c73c34 | ||
|
|
5a15c5b1d5 | ||
|
|
c60097f8c5 | ||
|
|
7f13a71252 | ||
|
|
5c8550b364 | ||
|
|
f27be38ca9 | ||
|
|
aed6b7754e | ||
|
|
0772ffb6ed | ||
|
|
37e6d95cbc | ||
|
|
644b611b9b | ||
|
|
b21be326fe | ||
|
|
3d9cea62ff | ||
|
|
a15da9ade1 | ||
|
|
a07b76a28c | ||
|
|
372c508911 | ||
|
|
257e9da563 | ||
|
|
cdfb7989b0 | ||
|
|
b01c15847c | ||
|
|
23528293e0 | ||
|
|
430dfef23d | ||
|
|
215a3f1669 | ||
|
|
f678f47155 | ||
|
|
fa07601a2a | ||
|
|
0d4357232c | ||
|
|
014514a4da | ||
|
|
86c35d7d9c | ||
|
|
9f76907f51 | ||
|
|
4e67167394 | ||
|
|
292b6825fc | ||
|
|
aed87f7437 | ||
|
|
c95d976e84 | ||
|
|
7bbab0759a | ||
|
|
2396a53b03 | ||
|
|
43ee82af57 | ||
|
|
3308e0ea92 | ||
|
|
61a6db1641 | ||
|
|
43eb4c3bfe | ||
|
|
8538b3ccf2 | ||
|
|
f9bb7094e9 | ||
|
|
54b7099d01 | ||
|
|
4ce04a5c90 | ||
|
|
19e29722dc | ||
|
|
99351aa2dc | ||
|
|
ef6aca8d12 | ||
|
|
b44f22c87f | ||
|
|
ca653c42f1 | ||
|
|
5544752dcd | ||
|
|
4082bd2a0a | ||
|
|
626164a8ab | ||
|
|
848c203b81 | ||
|
|
9f92a8dcce | ||
|
|
c8bb6e2a6a | ||
|
|
f135021c52 | ||
|
|
f7dc3b9fa1 | ||
|
|
967317f37e | ||
|
|
b5d29533ee | ||
|
|
a5c1dd1089 | ||
|
|
fd614ce0df | ||
|
|
eaf01312e4 | ||
|
|
5c3f3ff0f1 | ||
|
|
4a2515a6b2 | ||
|
|
c3a8d37e1c | ||
|
|
66af0a0b8e | ||
|
|
1a093c165e | ||
|
|
7182c85f11 | ||
|
|
eed4a932bd | ||
|
|
b5f4fdf918 | ||
|
|
ce9e2c9539 | ||
|
|
1cf0fbbde4 | ||
|
|
10d5725919 | ||
|
|
e9ec3ec3ef | ||
|
|
95c7acb210 | ||
|
|
65b035267c | ||
|
|
f10bc6718c | ||
|
|
5229a3d507 | ||
|
|
e7b6050b82 | ||
|
|
38a6e8b009 | ||
|
|
96b46d2394 | ||
|
|
d401a70a2a | ||
|
|
9ad5337e7b | ||
|
|
03c7b6b2aa | ||
|
|
94e5c3ddf1 | ||
|
|
caac293be5 | ||
|
|
faae634853 | ||
|
|
627acdbfde | ||
|
|
a7532b5f55 | ||
|
|
9596787cad | ||
|
|
6cdeacdb90 | ||
|
|
1b6945f74d | ||
|
|
675a599876 | ||
|
|
94c57bc0d8 | ||
|
|
667919cea1 | ||
|
|
9870761e32 | ||
|
|
8ef6fdea14 | ||
|
|
5c911367b7 | ||
|
|
e34cf6ebcf | ||
|
|
620a33b7bd | ||
|
|
e65bec78a1 | ||
|
|
d33c8e46af | ||
|
|
472a5091ae | ||
|
|
ff5e9fcaa9 | ||
|
|
4a027bdcdf | ||
|
|
dbdfc262b6 | ||
|
|
dc2b69d2c2 | ||
|
|
9a78d36b72 | ||
|
|
d14346ba15 | ||
|
|
992b4c53b8 | ||
|
|
385113c2f5 | ||
|
|
dc04bbe737 | ||
|
|
b6a66e1820 | ||
|
|
bcceceb2e0 | ||
|
|
19c0585ff0 | ||
|
|
97df7472f5 | ||
|
|
f05577d7df | ||
|
|
4d94aef80c | ||
|
|
0902640fc0 | ||
|
|
bcd5e38976 | ||
|
|
dfedc86fb7 | ||
|
|
78d110c8d1 | ||
|
|
4e6ae229c0 | ||
|
|
49cd062cb7 | ||
|
|
1fa196037e | ||
|
|
674ce90eaf | ||
|
|
a306407588 | ||
|
|
5c81441634 | ||
|
|
cff9902802 | ||
|
|
08911ec2ee | ||
|
|
79b698f232 | ||
|
|
5253278c29 | ||
|
|
48aa5b6c01 | ||
|
|
7e5c4af53d | ||
|
|
9b3bb77d0e | ||
|
|
a72995642c | ||
|
|
12562d68f8 | ||
|
|
8805610413 | ||
|
|
b9082a425f | ||
|
|
c1f5705ef8 | ||
|
|
69758cd631 | ||
|
|
05f3a97042 | ||
|
|
1db2187e79 | ||
|
|
ae68e3a1a6 | ||
|
|
96ed994932 | ||
|
|
b2580ec178 | ||
|
|
e065238758 | ||
|
|
34ea528ade | ||
|
|
1355dc135f | ||
|
|
9652a99589 | ||
|
|
8dfdced942 | ||
|
|
e779f98fb8 | ||
|
|
f633beafbf | ||
|
|
81d380fb90 | ||
|
|
4be5468485 | ||
|
|
a8759f1390 | ||
|
|
5b1b41ae80 | ||
|
|
75ed909aeb | ||
|
|
069852a501 | ||
|
|
5def7ba917 | ||
|
|
74ef8f8400 | ||
|
|
0e5431468c | ||
|
|
e65f975e29 | ||
|
|
0f80f6d4ae | ||
|
|
3491fec469 | ||
|
|
4df88b6df5 | ||
|
|
2a763f8f03 | ||
|
|
a306d0d0e1 | ||
|
|
2e39f6d4a9 | ||
|
|
03930ecc9b | ||
|
|
c3215e6387 | ||
|
|
31340c8bc1 | ||
|
|
fd07103e9a | ||
|
|
43319267ad | ||
|
|
37da90e81d | ||
|
|
58bc1b1682 | ||
|
|
60e2aa613a | ||
|
|
3f3b8dbfb9 | ||
|
|
c5390100f5 | ||
|
|
6f8d33005a | ||
|
|
1c5b6b365a | ||
|
|
8447d2c22d | ||
|
|
04b8bf277c | ||
|
|
fb3e15a079 | ||
|
|
8959d9167f | ||
|
|
32d404bd44 | ||
|
|
2415c668e4 | ||
|
|
5c19ad2885 | ||
|
|
73d41872cb | ||
|
|
1eb2652edf | ||
|
|
997e561389 | ||
|
|
45fff5642d | ||
|
|
179dba81cc | ||
|
|
25f9d27206 | ||
|
|
fee2d3ad0e | ||
|
|
6e54f34c4b | ||
|
|
7d631e5864 | ||
|
|
7ad0b0b093 | ||
|
|
9571e7c130 | ||
|
|
b21d7aef0d | ||
|
|
137a6a757e | ||
|
|
12a5c3c701 | ||
|
|
37b653eae6 | ||
|
|
f24be618d8 | ||
|
|
f077624212 | ||
|
|
b4c120cea4 | ||
|
|
5f69cad0cc | ||
|
|
76b5ca9a12 | ||
|
|
37234d2751 | ||
|
|
abfab78f96 | ||
|
|
53d704310a | ||
|
|
ab1c409022 | ||
|
|
bf940fdec5 | ||
|
|
6015ba7d25 | ||
|
|
72eb558f6a | ||
|
|
d74a7e9ea1 | ||
|
|
f259c7ebd7 | ||
|
|
8d13065cf4 | ||
|
|
5c36be59e5 | ||
|
|
d2559dcdb8 | ||
|
|
6194d9bcd9 | ||
|
|
64df821b38 | ||
|
|
0986b37267 | ||
|
|
26220239fa | ||
|
|
73fae67022 | ||
|
|
6dc15c6951 | ||
|
|
bc600a27b3 | ||
|
|
c8e0ef01ac | ||
|
|
db28f9ae46 | ||
|
|
7b4182b322 | ||
|
|
0753d9cf4a | ||
|
|
f337aa6a16 | ||
|
|
7080611247 | ||
|
|
bab9fec836 | ||
|
|
8dd1b62d78 | ||
|
|
9eeb3faba3 | ||
|
|
bba2e10e94 | ||
|
|
943251b387 | ||
|
|
8b76db3942 | ||
|
|
2463fd9588 | ||
|
|
56a8ad3aa5 | ||
|
|
57aa1773cb | ||
|
|
1f22456aed | ||
|
|
e784ad6d27 | ||
|
|
068aea3ac4 | ||
|
|
40eccdb2fd | ||
|
|
3f2afdc39d | ||
|
|
bcaabd36be | ||
|
|
21aed214a7 | ||
|
|
d761208878 | ||
|
|
2f7ce2841c | ||
|
|
3fa3f6fb94 | ||
|
|
6f9c103d78 | ||
|
|
b131bd413c | ||
|
|
8e7d5d37b9 | ||
|
|
5f7a4fe042 | ||
|
|
24ef629da3 | ||
|
|
7e1e8a9503 | ||
|
|
3807c1ba6a | ||
|
|
c3753ffc6a | ||
|
|
41b66fc585 | ||
|
|
95e547aff9 | ||
|
|
b6580dfe40 | ||
|
|
6330777076 | ||
|
|
2858579820 | ||
|
|
2e7fcb0e76 | ||
|
|
8cab1ae200 | ||
|
|
dad760f9ba | ||
|
|
e944f0f5c4 | ||
|
|
379638da43 | ||
|
|
121bca2f1d | ||
|
|
7bf83e5c48 | ||
|
|
33c12ec93f | ||
|
|
7a27bbb78c | ||
|
|
6ce83107c8 | ||
|
|
41fb5e5b22 | ||
|
|
8588e0c78f | ||
|
|
2091d3ed29 | ||
|
|
529aac8c37 | ||
|
|
428d31c4db | ||
|
|
aeba9ba05e | ||
|
|
bf95285d90 | ||
|
|
45292847ee | ||
|
|
aec3df1bf9 | ||
|
|
8b1407613a | ||
|
|
2d68ce5f1b | ||
|
|
0a1d1f2ed4 | ||
|
|
25401bd08d | ||
|
|
0ce98b126f | ||
|
|
db442f613e | ||
|
|
b31d969d63 | ||
|
|
8a8159ee0b | ||
|
|
74c6852743 | ||
|
|
b9e29e8b6c | ||
|
|
9246449590 | ||
|
|
e3a6913cb0 | ||
|
|
6231d0102b | ||
|
|
9630d92e0a | ||
|
|
0b7ff197c8 | ||
|
|
b55c547bb9 | ||
|
|
9b1c47278c | ||
|
|
aa6a9e9559 | ||
|
|
bddf4a8673 | ||
|
|
0aee56f4f1 | ||
|
|
fb94ef3cf3 | ||
|
|
7ff1e89824 | ||
|
|
b5fcab50d1 | ||
|
|
69afa608bf | ||
|
|
9677b09ecc | ||
|
|
1f3eb57fa4 | ||
|
|
a501400caa | ||
|
|
69e44a66bb | ||
|
|
abec5386a6 | ||
|
|
2e15bce98b | ||
|
|
10ac0a5e42 | ||
|
|
b723e031d1 | ||
|
|
18c02cbb42 | ||
|
|
d89f04a622 | ||
|
|
ceea368a5c | ||
|
|
ef83019a4b | ||
|
|
28c90f1923 | ||
|
|
8bc6a3cbcf | ||
|
|
a46d08c6f4 | ||
|
|
7d63cb52cb | ||
|
|
81ff4eec2d | ||
|
|
7a6d46acd3 | ||
|
|
700c6f0430 | ||
|
|
6688ad5d8d | ||
|
|
f3e1b6b39f | ||
|
|
d43bc13f48 | ||
|
|
a4ff3cbaac | ||
|
|
370fab5858 | ||
|
|
75d00f1778 | ||
|
|
d7ecbbf648 | ||
|
|
bff8415ea5 | ||
|
|
0794f7b629 | ||
|
|
b5a50aeba9 | ||
|
|
f6cfcb6217 | ||
|
|
fd3a583abf | ||
|
|
b551753fe4 | ||
|
|
b6283a554d | ||
|
|
a65ba8f87d | ||
|
|
ffd23d34ab | ||
|
|
75859a2c4d | ||
|
|
166e35bf0e | ||
|
|
74da0c8a52 | ||
|
|
d5fb086b51 | ||
|
|
6fa2a220e7 | ||
|
|
27fed3e737 | ||
|
|
13b62d72c6 | ||
|
|
4c6c967576 | ||
|
|
009143f724 | ||
|
|
c545e19e40 | ||
|
|
87790b330e | ||
|
|
c9d081f2c6 | ||
|
|
222d49c4d8 | ||
|
|
ce8fd9ddbe | ||
|
|
ccd1758284 | ||
|
|
ba48078f9e | ||
|
|
f35df54eab | ||
|
|
f1334c6b92 | ||
|
|
7a5631a2ce | ||
|
|
9c9f8fae14 | ||
|
|
dc51b8ba48 | ||
|
|
e3edd5267d | ||
|
|
8b69dfd154 | ||
|
|
8333c3fbfc | ||
|
|
083377651f | ||
|
|
0c18f6157d | ||
|
|
66d71efa78 | ||
|
|
0811dec65c | ||
|
|
b3822cc4c5 | ||
|
|
942bfe88c1 | ||
|
|
63038d3603 | ||
|
|
0f5fdbc772 | ||
|
|
36f8ca1073 | ||
|
|
1e288c31af | ||
|
|
e8144128d0 | ||
|
|
e5421effd5 | ||
|
|
48a2bb9fe4 | ||
|
|
67d100796d | ||
|
|
0f91bf0e98 | ||
|
|
1aaee7526b | ||
|
|
fd0ce52cf6 | ||
|
|
0fd973f58b | ||
|
|
a99856bec0 | ||
|
|
eac195ec18 | ||
|
|
ab5d64ff12 | ||
|
|
5f017b2b85 | ||
|
|
bb6ec3b5c9 | ||
|
|
d870264230 | ||
|
|
aad97c936d | ||
|
|
b78835f195 | ||
|
|
a480e76353 | ||
|
|
8294c78db4 | ||
|
|
3ea426d45f | ||
|
|
bc74543ca2 | ||
|
|
c69966035b | ||
|
|
a1bef6248a | ||
|
|
2f4a9c2c26 | ||
|
|
342d5a1aa1 | ||
|
|
a5206d2959 | ||
|
|
4e876511a3 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -17,7 +17,7 @@
|
||||
|
||||
# Ignore object files.
|
||||
*.o
|
||||
build
|
||||
build/ripple*.js
|
||||
tags
|
||||
bin/rippled
|
||||
Debug/*.*
|
||||
@@ -37,3 +37,8 @@ db/*.db-*
|
||||
rippled.cfg
|
||||
validators.txt
|
||||
test/config.js
|
||||
|
||||
# Ignore coverage files
|
||||
/lib-cov
|
||||
/src-cov
|
||||
/coverage.html
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
build
|
||||
deploy
|
||||
lib-cov
|
||||
coverage.html
|
||||
|
||||
6
.travis.yml
Normal file
6
.travis.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
notifications:
|
||||
email:
|
||||
false
|
||||
11
Gruntfile.js
11
Gruntfile.js
@@ -39,8 +39,10 @@ module.exports = function(grunt) {
|
||||
"src/js/sjcl-custom/sjcl-secp256k1.js",
|
||||
"src/js/sjcl-custom/sjcl-ripemd160.js",
|
||||
"src/js/sjcl-custom/sjcl-extramath.js",
|
||||
"src/js/sjcl-custom/sjcl-montgomery.js",
|
||||
"src/js/sjcl-custom/sjcl-validecc.js",
|
||||
"src/js/sjcl-custom/sjcl-ecdsa-der.js"
|
||||
"src/js/sjcl-custom/sjcl-ecdsa-der.js",
|
||||
"src/js/sjcl-custom/sjcl-jacobi.js"
|
||||
],
|
||||
dest: 'build/sjcl.js'
|
||||
}
|
||||
@@ -50,7 +52,8 @@ module.exports = function(grunt) {
|
||||
entry: "./src/js/ripple/index.js",
|
||||
output: {
|
||||
library: "ripple"
|
||||
}
|
||||
},
|
||||
cache: true
|
||||
},
|
||||
lib: {
|
||||
output: {
|
||||
@@ -79,8 +82,8 @@ module.exports = function(grunt) {
|
||||
tasks: 'concat:sjcl'
|
||||
},
|
||||
lib: {
|
||||
files: 'src/js/*.js',
|
||||
tasks: 'webpack'
|
||||
files: 'src/js/ripple/*.js',
|
||||
tasks: 'webpack:lib_debug'
|
||||
}
|
||||
},
|
||||
dox: {
|
||||
|
||||
45
HISTORY.md
Normal file
45
HISTORY.md
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
##0.7.35
|
||||
|
||||
+ `LastLedgerSequence` is set by default on outgoing transactions. This refers to the last valid ledger index (AKA sequence) for a transaction. By default, this index is set to the current index (at submission time) plus 8. In theory, this allows ripple-lib to deterministically fail a transaction whose submission request timed out, but whose associated server continues to emit ledger_closed events.
|
||||
|
||||
+ Transactions that err with `telINSUF_FEE_P` will be automatically resubmitted. This error indicates that the `Fee` supplied in the transaction submission request was inadquate. Ideally, the `Fee` is tracked by ripple-lib in real-time, and the resubmitted transaction will most likely succeed.
|
||||
|
||||
+ Added Transaction.iff(function(callback) { }). Callback expects first argument to be an Error or null, second argument is a boolean which indicates whether or not to proceed with the transaction submission. If an `iff` function is specified, it will be executed prior to every submission of the transaction (including resubmissions).
|
||||
|
||||
+ Transactions will now emit `presubmit` and `postsubmit` events. They will be emitted before and after a transaction is submitted, respectively.
|
||||
|
||||
+ Added Transaction.summary(). Returns a summary of a transaction in semi-human-readable form. JSON-stringifiable.
|
||||
|
||||
+ Remote.requestAccountTx() with `binary: true` will automatically parse transactions.
|
||||
|
||||
+ Added Remote.requestAccountTx filter, map, and reduce.
|
||||
|
||||
```js
|
||||
remote.requestAccountTx({
|
||||
account: 'retc',
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
limit: 100,
|
||||
binary: true,
|
||||
|
||||
filter: function(transaction) {
|
||||
return transaction.tx.TransactionType === 'Payment';
|
||||
},
|
||||
|
||||
map: function(transaction) {
|
||||
return Number(transaction.tx.Amount);
|
||||
},
|
||||
|
||||
reduce: function(a, b) {
|
||||
return a + b;
|
||||
},
|
||||
|
||||
pluck: 'transactions'
|
||||
}, console.log)
|
||||
```
|
||||
|
||||
+ Added persistence hooks.
|
||||
|
||||
+ General performance improvements, especially for long-running processes.
|
||||
|
||||
62
LICENSE
62
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012,2013 OpenCoin, Inc.
|
||||
Copyright (c) 2012,2013,2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -12,39 +12,39 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
--------------------------------------
|
||||
|
||||
Some code from Tom Wu:
|
||||
This software is covered under the following copyright:
|
||||
|
||||
/*
|
||||
* Copyright (c) 2003-2005 Tom Wu
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||
* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
* IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||||
* INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||||
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||||
* THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||||
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* In addition, the following condition applies:
|
||||
*
|
||||
* All redistributions must retain an intact copy of this copyright notice
|
||||
* and disclaimer.
|
||||
*/
|
||||
Copyright (c) 2003-2005 Tom Wu
|
||||
All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||||
INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||||
THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
In addition, the following condition applies:
|
||||
|
||||
All redistributions must retain an intact copy of this copyright notice
|
||||
and disclaimer.
|
||||
|
||||
Address all questions regarding this license to:
|
||||
|
||||
|
||||
12
Makefile
Normal file
12
Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
test:
|
||||
mocha --reporter spec test/*-test.js
|
||||
|
||||
coverage:
|
||||
rm -rf src-cov
|
||||
mkdir src-cov
|
||||
mkdir src-cov/js
|
||||
jscoverage --no-highlight src/js/ripple src-cov/js/ripple
|
||||
RIPPLE_LIB_COV=1 mocha --reporter html-cov test/*-test.js > coverage.html
|
||||
rm -rf src-cov
|
||||
|
||||
.PHONY: test
|
||||
172
README.md
172
README.md
@@ -1,123 +1,93 @@
|
||||
Ripple JavaScript Library - ripple-lib
|
||||
======================================
|
||||
#The Ripple JavaScript Library
|
||||
|
||||
This library can connect to the Ripple network via the WebSocket protocol and runs in Node.js as well as in the browser.
|
||||
`ripple-lib` connects to the Ripple network via the WebSocket protocol and runs in Node.js as well as in the browser.
|
||||
|
||||
* https://ripple.com/wiki/Ripple_JavaScript_library
|
||||
* https://ripple.com
|
||||
* https://ripple.com/wiki
|
||||
**Use ripple-lib for**
|
||||
|
||||
##Initializing a remote connection
|
||||
+ Connecting to a local or remote rippled in JavaScript (Node.js or browser)
|
||||
+ Issuing [rippled API](https://ripple.com/wiki/JSON_Messages) requests
|
||||
+ Listening to events on the Ripple network (transaction, ledger, etc.)
|
||||
+ Signing and submitting transactions to the Ripple network
|
||||
|
||||
[ripple-lib.remote](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js) is responsible for managing connections to rippled servers.
|
||||
###In this file:
|
||||
|
||||
1. Overview
|
||||
2. [Getting `ripple-lib`](README.md#getting-ripple-lib)
|
||||
3. [Quickstart](README.md#quickstart)
|
||||
4. [Running tests](https://github.com/ripple/ripple-lib#running-tests)
|
||||
|
||||
###For additional documentation see:
|
||||
|
||||
1. [The `ripple-lib` Guides (docs/GUIDES.md)](docs/GUIDES.md)
|
||||
2. [The `ripple-lib` API Reference (docs/REFERENCE.md)](docs/REFERENCE.md)
|
||||
3. https://ripple.com/wiki/Ripple_JavaScript_library
|
||||
|
||||
###Also see:
|
||||
|
||||
+ https://ripple.com/wiki
|
||||
+ https://ripple.com
|
||||
|
||||
##Getting `ripple-lib`
|
||||
|
||||
**Via npm for Node.js**
|
||||
|
||||
```
|
||||
$ npm install ripple-lib
|
||||
```
|
||||
|
||||
**Build from the source using `grunt`**
|
||||
|
||||
```
|
||||
$ git clone https://github.com/ripple/ripple-lib
|
||||
$ npm install
|
||||
$ grunt
|
||||
```
|
||||
|
||||
Then use the minified `build/ripple-*-min.js` in your webpage
|
||||
|
||||
##Quickstart
|
||||
|
||||
`Remote` ([remote.js](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js)) is the module responsible for managing connections to `rippled` servers:
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
|
||||
var remote = new Remote({
|
||||
trusted: false,
|
||||
servers: [
|
||||
{
|
||||
host: ''
|
||||
, port: 1111,
|
||||
, secure: true
|
||||
}
|
||||
// see the API Reference for available options
|
||||
trusted: true,
|
||||
local_signing: true,
|
||||
local_fee: true,
|
||||
fee_cushion: 1.5,
|
||||
servers: [
|
||||
{
|
||||
host: 's1.ripple.com'
|
||||
, port: 443
|
||||
, secure: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
remote.connect();
|
||||
```
|
||||
remote.connect(function() {
|
||||
/* remote connected */
|
||||
|
||||
Once a connection is formed to any of the supplied servers, a `connect` event is emitted, indicating that the remote is ready to begin fulfilling requests. When there are no more connected servers to fulfill requests, a `disconnect` event is emitted. If you send requests before ripple-lib is connected to any servers, requests are deferred until the `connect` event is received.
|
||||
|
||||
```js
|
||||
var remote = new Remote({ /* options */ }).connect();
|
||||
remote.request_server_info(function(err, info) { }); // will defer until connected
|
||||
```
|
||||
|
||||
##Remote functions
|
||||
|
||||
Each remote function returns a `Request` object. is object is an `EventEmitter`. You may listen for success or failure events from each request, or provide a callback. Example:
|
||||
|
||||
```js
|
||||
var request = remote.request_server_info();
|
||||
request.on('success', function(res) {
|
||||
//handle success conditions
|
||||
});
|
||||
request.on('error', function(err) {
|
||||
//handle error conditions
|
||||
});
|
||||
request.request();
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```js
|
||||
remote.request_server_info(function(err, res) {
|
||||
|
||||
// see the API Reference for available functions
|
||||
});
|
||||
```
|
||||
|
||||
**request_server_info([callback])**
|
||||
See [The `ripple-lib` Guides](docs/GUIDES.md) and [The `ripple-lib` API Reference](docs/REFERENCE.md) for walkthroughs and details about all of the available functions and options.
|
||||
|
||||
**request_ledger(ledger, [opts], [callback])**
|
||||
##Running tests
|
||||
|
||||
**request_ledger_hash([callback])**
|
||||
1. Clone the repository
|
||||
|
||||
**request_ledger_header([callback])**
|
||||
2. `cd` into the repository and install dependencies with `npm install`
|
||||
|
||||
**request_ledger_current([callback])**
|
||||
3. `npm test` or `make test` or `node_modules\.bin\mocha test\*-test.js`
|
||||
|
||||
**request_ledger_entry(type, [callback])**
|
||||
**Generating code coverage**
|
||||
|
||||
**request_subscribe(streams, [callback])**
|
||||
|
||||
**request_unsubscribe(streams, [callback])**
|
||||
|
||||
**request_transaction_entry(hash, [callback])**
|
||||
|
||||
**request_tx(hash, [callback])**
|
||||
|
||||
**request_account_info(accountID, [callback])**
|
||||
|
||||
**request_account_lines(accountID, account_index, current, [callback])**
|
||||
|
||||
**request_account_offers(accountID, account_index, current, [callback])**
|
||||
|
||||
**request_account_tx(opts, [callback])**
|
||||
|
||||
**request_book_offers(gets, pays, taker, [callback])**
|
||||
|
||||
**request_wallet_accounts(seed, [callback])**
|
||||
|
||||
+ requires trusted **remote
|
||||
|
||||
**request_sign(secret, tx_json, [callback])**
|
||||
|
||||
+ requires trusted **remote
|
||||
|
||||
**request_submit([callback])**
|
||||
|
||||
**request_account_balance(account, current, [callback])**
|
||||
|
||||
**request_account_flags(account, current, [callback])**
|
||||
|
||||
**request_owner_count(account, current, [callback])**
|
||||
|
||||
**request_ripple_balance(account, issuer, currency, current, [callback])**
|
||||
|
||||
**request_ripple_path_find(src_account, dst_account, dst_amount, src_currencies, [callback])**
|
||||
|
||||
**request_unl_list([callback])**
|
||||
|
||||
**request_unl_add(addr, comment, [callback])**
|
||||
|
||||
**request_unl_delete(node, [callback])**
|
||||
|
||||
**request_peers([callback])**
|
||||
|
||||
**request_connect(ip, port, [callback])**
|
||||
|
||||
**transaction()**
|
||||
|
||||
+ returns a [Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) object
|
||||
ripple-lib uses `jscoverage` to generate code coverage. To generate a file `coverage.html`, run `make coverage`
|
||||
|
||||
44
bench/modpow.js
Normal file
44
bench/modpow.js
Normal file
@@ -0,0 +1,44 @@
|
||||
var Benchmark;
|
||||
try {
|
||||
Benchmark = require('benchmark');
|
||||
} catch (e) {
|
||||
console.error("Please install Benchmark.js: npm install benchmark");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var sjcl = require('../build/sjcl');
|
||||
var jsbn = require('../src/js/jsbn/jsbn');
|
||||
|
||||
var base = "3f70f29d3f3ae354a6d2536ceafba83cfc787cd91e7acd2b6bde05e62beb8295ae18e3f786726f8d034bbc15bf8331df959f59d431736d5f306aaba63dacec279484e39d76db9b527738072af15730e8b9956a64e8e4dbe868f77d1414a8a8b8bf65380a1f008d39c5fabe1a9f8343929342ab7b4f635bdc52532d764701ff3d8072c475c012ff0c59373e8bc423928d99f58c3a6d9f6ab21ee20bc8e8818fc147db09f60c81906f2c6f73dc69725f075853a89f0cd02a30a8dd86b660ccdeffc292f398efb54088c822774445a6afde471f7dd327ef9996296898a5747726ccaeeceeb2e459df98b4128cb5ab8c7cd20c563f960a1aa770f3c81f13f967b6cc";
|
||||
var exponent = "322e393f76a1c22b147e7d193c00c023afb7c1500b006ff1bc1cc8d391fc38bd";
|
||||
var modulus = "c7f1bc1dfb1be82d244aef01228c1409c198894eca9e21430f1669b4aa3864c9f37f3d51b2b4ba1ab9e80f59d267fda1521e88b05117993175e004543c6e3611242f24432ce8efa3b81f0ff660b4f91c5d52f2511a6f38181a7bf9abeef72db056508bbb4eeb5f65f161dd2d5b439655d2ae7081fcc62fdcb281520911d96700c85cdaf12e7d1f15b55ade867240722425198d4ce39019550c4c8a921fc231d3e94297688c2d77cd68ee8fdeda38b7f9a274701fef23b4eaa6c1a9c15b2d77f37634930386fc20ec291be95aed9956801e1c76601b09c413ad915ff03bfdc0b6b233686ae59e8caf11750b509ab4e57ee09202239baee3d6e392d1640185e1cd";
|
||||
var expected = "5b3823974b3eda87286d3f38499de290bd575d8b02f06720acacf3d50950f9ca0ff6b749f3be03913ddca0b291e0b263bdab6c9cb97e4ab47ee9c235ff20931a8ca358726fab93614e2c549594f5c50b1c979b34f840b6d4fc51d6feb2dd072995421d17862cb405e040fc1ed662a3245a1f97bbafa6d1f7f76c7db6a802e3037acdf01ab5053f5da518d6753477193b9c25e1720519dcb9e2f6e70d5786656d356151845a49861dfc40187eff0e85cd18b1f3f3b97c476472edfa090b868b2388edfffecc521c20df8cebb8aacfb3669b020330dd6ea64b2a3067a972b8f249bccc19347eff43893e916f0949bd5789a5cce0f8b7cd87cece909d679345c0d4";
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var jsbnBase = new BigInteger(base, 16);
|
||||
var jsbnExponent = new BigInteger(exponent, 16);
|
||||
var jsbnModulus = new BigInteger(modulus, 16);
|
||||
|
||||
var bn = sjcl.bn;
|
||||
var sjclBase = new bn(base);
|
||||
var sjclExponent = new bn(exponent);
|
||||
var sjclModulus = new bn(modulus);
|
||||
|
||||
var suite = new Benchmark.Suite;
|
||||
|
||||
// add tests
|
||||
suite.add('jsbn#modPow', function() {
|
||||
jsbnBase.modPow(jsbnExponent, jsbnModulus);
|
||||
});
|
||||
suite.add('sjcl#powermodMontgomery', function() {
|
||||
sjclBase.powermodMontgomery(sjclExponent, sjclModulus);
|
||||
});
|
||||
suite.on('cycle', function(event) {
|
||||
console.log(String(event.target));
|
||||
});
|
||||
suite.on('complete', function() {
|
||||
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
|
||||
});
|
||||
// run async
|
||||
console.log("Running benchmark...");
|
||||
suite.run({ 'async': false });
|
||||
47
bin/decode_binary.js
Executable file
47
bin/decode_binary.js
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var SerializedObject = require('../src/js/ripple/serializedobject').SerializedObject;
|
||||
|
||||
var argv = process.argv.slice(2);
|
||||
|
||||
var blob;
|
||||
|
||||
blob = argv.shift();
|
||||
|
||||
if (blob === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
|
||||
function read_input(callback) {
|
||||
tx_json = '';
|
||||
process.stdin.on('data', function(data) { tx_json += data; });
|
||||
process.stdin.on('end', callback);
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function ready() {
|
||||
var valid_arguments = blob;
|
||||
|
||||
if (!valid_arguments) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
} else {
|
||||
decode();
|
||||
}
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
console.log(
|
||||
'Usage: decode_binary.js <hex_blob>\n\n',
|
||||
'Example: decode_binary.js 120000240000000161D6871AFD498D00000000000000000000000000005553440000000000550FC62003E785DC231A1058A05E56E3F09CF4E668400000000000000A732102AE75B908F0A95F740A7BFA96057637E5C2170BC8DAD13B2F7B52AE75FAEBEFCF811450F97A072F1C4357F1AD84566A609479D927C9428314550FC62003E785DC231A1058A05E56E3F09CF4E6'
|
||||
);
|
||||
};
|
||||
|
||||
function decode() {
|
||||
buffer = new SerializedObject(blob);
|
||||
console.log(buffer.to_json());
|
||||
};
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
125
bin/rsign.js
125
bin/rsign.js
@@ -1,65 +1,96 @@
|
||||
#!/usr/bin/node
|
||||
#!/usr/bin/env node
|
||||
|
||||
var Transaction = require('../src/js/ripple/transaction').Transaction;
|
||||
|
||||
var cursor = 2;
|
||||
var argv = process.argv.slice(2);
|
||||
|
||||
var verbose;
|
||||
var secret;
|
||||
var tx_json;
|
||||
|
||||
var usage = function () {
|
||||
if (~argv.indexOf('-v')){
|
||||
argv.splice(argv.indexOf('-v'), 1);
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
secret = argv.shift();
|
||||
tx_json = argv.shift();
|
||||
|
||||
if (tx_json === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
|
||||
function read_input(callback) {
|
||||
tx_json = '';
|
||||
process.stdin.on('data', function(data) { tx_json += data; });
|
||||
process.stdin.on('end', callback);
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function ready() {
|
||||
var valid_arguments = secret && tx_json;
|
||||
|
||||
if (!valid_arguments) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
} else {
|
||||
var valid_json = true;
|
||||
|
||||
try {
|
||||
tx_json = JSON.parse(tx_json);
|
||||
} catch(exception) {
|
||||
valid_json = false;
|
||||
}
|
||||
|
||||
if (!valid_json) {
|
||||
console.error('Invalid JSON\n');
|
||||
print_usage();
|
||||
} else {
|
||||
sign_transaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
console.log(
|
||||
"Usage: rsign.js <secret> <json>\n"
|
||||
+ " Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC '{ \"TransactionType\" : \"Payment\", \"Account\" : \"r3P9vH81KBayazSTrQj6S25jW6kDb779Gi\", \"Destination\" : \"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV\", \"Amount\" : \"200000000\", \"Fee\" : \"10\", \"Sequence\" : \"1\" }'"
|
||||
'Usage: rsign.js <secret> <json>\n\n',
|
||||
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC',
|
||||
JSON.stringify({
|
||||
TransactionType: 'Payment',
|
||||
Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
|
||||
Destination: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
|
||||
Amount: '200000000',
|
||||
Fee: '10',
|
||||
Sequence: 1
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
if (process.argv.length > cursor && process.argv[cursor] === "-v")
|
||||
{
|
||||
verbose = true;
|
||||
cursor++;
|
||||
}
|
||||
function sign_transaction() {
|
||||
var tx = new Transaction();
|
||||
|
||||
if (process.argv.length > cursor)
|
||||
{
|
||||
secret = process.argv[cursor++];
|
||||
}
|
||||
tx.tx_json = tx_json;
|
||||
tx._secret = secret;
|
||||
tx.complete();
|
||||
|
||||
if (process.argv.length > cursor)
|
||||
{
|
||||
tx_json = JSON.parse(process.argv[cursor++]);
|
||||
}
|
||||
var unsigned_blob = tx.serialize().to_hex();
|
||||
var unsigned_hash = tx.signingHash();
|
||||
tx.sign();
|
||||
|
||||
if (process.argv.length !== cursor || !secret || !tx_json)
|
||||
{
|
||||
usage();
|
||||
}
|
||||
else
|
||||
{
|
||||
var tx = new Transaction();
|
||||
if (verbose) {
|
||||
var sim = { };
|
||||
|
||||
tx.tx_json = tx_json;
|
||||
tx._secret = secret;
|
||||
tx.complete();
|
||||
sim.tx_blob = tx.serialize().to_hex();
|
||||
sim.tx_json = tx.tx_json;
|
||||
sim.tx_signing_hash = unsigned_hash;
|
||||
sim.tx_unsigned = unsigned_blob;
|
||||
|
||||
var unsigned = tx.serialize().to_hex();
|
||||
tx.sign();
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
var sim = {};
|
||||
|
||||
sim.tx_blob = tx.serialize().to_hex();
|
||||
sim.tx_json = tx.tx_json;
|
||||
sim.tx_signing_hash = tx.signing_hash().to_hex();
|
||||
sim.tx_unsigned = unsigned;
|
||||
|
||||
console.log(JSON.stringify(sim, undefined, 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log(tx.serialize().to_hex());
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify(sim, null, 2));
|
||||
} else {
|
||||
console.log(tx.serialize().to_hex());
|
||||
}
|
||||
};
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
26
bin/validate_address.js
Executable file
26
bin/validate_address.js
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var UInt160 = require('../').UInt160;
|
||||
var address = process.argv[2];
|
||||
|
||||
if (address === '-') {
|
||||
readInput(validateAddress);
|
||||
} else {
|
||||
validateAddress(address);
|
||||
}
|
||||
|
||||
function readInput(callback) {
|
||||
var result = '';
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', function(data) {
|
||||
result += data;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
callback(result);
|
||||
});
|
||||
};
|
||||
|
||||
function validateAddress(address) {
|
||||
process.stdout.write((UInt160.is_valid(address.trim()) ? '0' : '1') + '\r\n');
|
||||
};
|
||||
4125
build/sjcl.js
Normal file
4125
build/sjcl.js
Normal file
File diff suppressed because it is too large
Load Diff
201
docs/GUIDES.md
Normal file
201
docs/GUIDES.md
Normal file
@@ -0,0 +1,201 @@
|
||||
#`ripple-lib` Guides
|
||||
|
||||
This file provides step-by-step walkthroughs for some of the most common usages of `ripple-lib`.
|
||||
|
||||
###Guides in this document:
|
||||
|
||||
1. [Connecting to the Ripple network with `Remote`](GUIDES.md#1-connecting-to-the-ripple-network-with-remote)
|
||||
2. [Using `Remote` functions and `Request` objects](GUIDES.md#2-using-remote-functions-and-request-objects)
|
||||
3. [Submitting a payment to the network](GUIDES.md#3-submitting-a-payment-to-the-network)
|
||||
* [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)
|
||||
4. [Submitting a trade offer to the network](GUIDES.md#4-submitting-a-trade-offer-to-the-network)
|
||||
5. [Listening to the network](GUIDES.md#5-listening-to-the-network)
|
||||
|
||||
|
||||
###Also see:
|
||||
|
||||
1. [The `ripple-lib` README](../README.md)
|
||||
2. [The `ripple-lib` API Reference](REFERENCE.md)
|
||||
|
||||
##1. Connecting to the Ripple network with `Remote`
|
||||
|
||||
1. [Get `ripple-lib`](README.md#getting-ripple-lib)
|
||||
2. Load the `ripple-lib` module into a Node.js file or webpage:
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
```
|
||||
3. Create a new `Remote` and connect to the network:
|
||||
```js
|
||||
var remote = new Remote({options});
|
||||
|
||||
remote.connect(function() {
|
||||
/* remote connected, use some remote functions here */
|
||||
});
|
||||
```
|
||||
__NOTE:__ See the API Reference for available [`Remote` options](REFERENCE.md#1-remote-options)
|
||||
4. You're connected! Read on to see what to do now.
|
||||
|
||||
|
||||
##2. Using `Remote` functions and `Request` objects
|
||||
|
||||
All `Remote` functions return a `Request` object.
|
||||
|
||||
A `Request` is an `EventEmitter` so you can listen for success or failure events -- or, instead, you can provide a callback to the `Remote` function.
|
||||
|
||||
Here is an example, using `request_server_info()`, of how `Remote` functions can be used with event listeners (the first code block) or with a callback (the second block):
|
||||
|
||||
+ Using a `Remote` function with `Request` event listeners:
|
||||
```js
|
||||
var request = remote.request_server_info();
|
||||
request.on('success', function(res) {
|
||||
//handle success
|
||||
});
|
||||
request.on('error', function(err) {
|
||||
//handle error
|
||||
});
|
||||
request.request(); // this triggers the request if it has not already been sent to the server
|
||||
```
|
||||
|
||||
+ Using a `Remote` function with a callback:
|
||||
```js
|
||||
remote.request_server_info(function(err, res) {
|
||||
if (err) {
|
||||
//handle error
|
||||
} else {
|
||||
//handle success
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
__NOTE:__ See the API Reference for available [`Remote` functions](REFERENCE.md#2-remote-functions)
|
||||
|
||||
|
||||
|
||||
|
||||
##3. Submitting a payment to the network
|
||||
|
||||
Submitting a payment transaction to the Ripple network involves connecting to a `Remote`, creating a transaction, signing it with the user's secret, and submitting it to the `rippled` server. Note that the `Amount` module is used to convert human-readable amounts like '1XRP' or '10.50USD' to the type of Amount object used by the Ripple network.
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
/* Loading ripple-lib Remote and Amount modules in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
// var Amount = ripple.Amount;
|
||||
|
||||
var MY_ADDRESS = 'rrrMyAddress';
|
||||
var MY_SECRET = 'secret';
|
||||
var RECIPIENT = 'rrrRecipient';
|
||||
var AMOUNT = Amount.from_human('1XRP');
|
||||
|
||||
var remote = new Remote({ /* Remote options */ });
|
||||
|
||||
remote.connect(function() {
|
||||
remote.set_secret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.transaction();
|
||||
|
||||
transaction.payment({
|
||||
from: MY_ADDRESS,
|
||||
to: RECIPIENT,
|
||||
amount: AMOUNT
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
/* handle submission errors / success */
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
###A note on transaction fees
|
||||
|
||||
A full description of network transaction fees can be found on the [Ripple Wiki](https://ripple.com/wiki/Transaction_Fee).
|
||||
|
||||
In short, transaction fees are very small amounts (on the order of ~10) of [XRP drops](https://ripple.com/wiki/Ripple_credits#Notes_on_drops) spent and destroyed with every transaction. They are largely used to account for network load and prevent spam. With `ripple-lib`, transaction fees are calculated locally by default and the fee you are willing to pay is submitted along with your transaction.
|
||||
|
||||
Since the fee required for a transaction may change between the time when the original fee was calculated and the time when the transaction is submitted, it is wise to use the [`fee_cushion`](REFERENCE.md#1-remote-options) to ensure that the transaction will go through. For example, suppose the original fee calculated for a transaction was 10 XRP drops but at the instant the transaction is submitted the server is experiencing a higher load and it has raised its minimum fee to 12 XRP drops. Without a `fee_cusion`, this transaction would not be processed by the server, but with a `fee_cusion` of, say, 1.5 it would be processed and you would just pay the 2 extra XRP drops.
|
||||
|
||||
The [`max_fee`](REFERENCE.md#1-remote-options) option can be used to avoid submitting a transaction to a server that is charging unreasonably high fees.
|
||||
|
||||
|
||||
##4. Submitting a trade offer to the network
|
||||
|
||||
Submitting a trade offer to the network is similar to submitting a payment transaction. Here is an example for a trade that expires in 24 hours where you are offering to sell 1 USD in exchange for 100 XRP:
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
/* Loading ripple-lib Remote and Amount modules in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
// var Amount = ripple.Amount;
|
||||
|
||||
var MY_ADDRESS = 'rrrMyAddress';
|
||||
var MY_SECRET = 'secret';
|
||||
|
||||
var BUY_AMOUNT = Amount.from_human('100XRP');
|
||||
var SELL_AMOUNT = Amount.from_human('1USD');
|
||||
|
||||
// EXPIRATION must be a Date object, leave undefined to submit offer that won't expire
|
||||
var now = new Date();
|
||||
var tomorrow = new Date(now.getTime() + (24 * 60 * 60 * 1000));
|
||||
var EXPIRATION = tomorrow;
|
||||
|
||||
var remote = new Remote({ /* Remote options */ });
|
||||
|
||||
remote.connect(function() {
|
||||
remote.set_secret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.transaction();
|
||||
|
||||
transaction.offer_create({
|
||||
from: MY_ADDRESS,
|
||||
buy: BUY_AMOUNT,
|
||||
sell: SELL_AMOUNT,
|
||||
expiration: EXPIRATION
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
/* handle submission errors / success */
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
##5. Listening to the network
|
||||
|
||||
In some (relatively rare) cases you may want to subscribe to the network event feed and listen for transactions and the ledger closings. [Ripple.com](http://www.ripple.com) uses this feature of `ripple-lib` to display the live feed on the top of each page and the ledger closing visualization on the [Developers page](http://ripple.com/devs).
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
|
||||
var remote = new Remote({options});
|
||||
|
||||
remote.connect(function() {
|
||||
remote.on('transaction_all', transactionListener);
|
||||
remote.on('ledger_closed', ledgerListener);
|
||||
});
|
||||
|
||||
function transactionListener (transaction_data) {
|
||||
// handle transaction_data
|
||||
// see https://ripple.com/wiki/RPC_API#transactions_stream_messages for the format of transaction_data
|
||||
}
|
||||
|
||||
function ledgerListener (ledger_data) {
|
||||
// handle ledger_data
|
||||
// see https://ripple.com/wiki/RPC_API#ledger_stream_messages for the format of ledger_data
|
||||
}
|
||||
```
|
||||
* https://ripple.com/wiki/RPC_API#transactions_stream_messages
|
||||
* https://ripple.com/wiki/RPC_API#ledger_stream_messages
|
||||
|
||||
258
docs/REFERENCE.md
Normal file
258
docs/REFERENCE.md
Normal file
@@ -0,0 +1,258 @@
|
||||
#`ripple-lib` API Reference
|
||||
|
||||
__(More examples coming soon!)__
|
||||
|
||||
###In this document:
|
||||
|
||||
1. [`Remote` options](REFERENCE.md#1-remote-options)
|
||||
2. [`Remote` functions](REFERENCE.md#2-remote-functions)
|
||||
+ [Server info functions](REFERENCE.md#server-info-functions)
|
||||
+ [Ledger query functions](REFERENCE.md#ledger-query-functions)
|
||||
+ [Transaction query functions](REFERENCE.md#transaction-query-functions)
|
||||
+ [Account query functions](REFERENCE.md#account-query-functions)
|
||||
+ [Order book query functions](REFERENCE.md#order-book-query-functions)
|
||||
+ [Transaction submission functions](REFERENCE.md#transaction-submission-functions)
|
||||
3. [`Transaction` events](REFERENCE.md#3-transaction-events)
|
||||
4. [`Amount` objects](REFERENCE.md#4-amount-objects)
|
||||
|
||||
|
||||
###Also see:
|
||||
|
||||
1. [The `ripple-lib` README](../README.md)
|
||||
2. [The `ripple-lib` GUIDES](GUIDES.md)
|
||||
|
||||
|
||||
#1. `Remote` options
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
|
||||
var remote = new Remote({options});
|
||||
```
|
||||
|
||||
A new `Remote` can be created with the following options:
|
||||
|
||||
+ `trace` Log all of the events emitted (boolean)
|
||||
+ `max_listeners` Set maxListeners for remote; prevents EventEmitter warnings (number)
|
||||
+ `connection_offset` Connect to remote servers on supplied interval (number in seconds)
|
||||
+ `trusted` truthy, if remote is trusted (boolean)
|
||||
+ `local_fee` Set whether the transaction fee range will be set locally (boolean, default is true, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `fee_cushion` Extra fee multiplier to account for async fee changes (number, e.g. 1.5, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `max_fee` Maximum acceptable transaction fee (number in [XRP drops](https://ripple.com/wiki/Ripple_credits#Notes_on_drops), see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `servers` Array of server objects of the following form:
|
||||
|
||||
```js
|
||||
{
|
||||
host: <string>
|
||||
, port: <number>
|
||||
, secure: <boolean>
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#2. `Remote` functions
|
||||
|
||||
|
||||
|
||||
##Server info functions
|
||||
|
||||
**[request_server_info([callback])](https://ripple.com/wiki/RPC_API#server_info)**
|
||||
|
||||
Returns information about the state of the server. If you are connected to multiple servers and want to select by a particular host, use `request.set_server`. Example:
|
||||
|
||||
```js
|
||||
var request = remote.request_server_info();
|
||||
request.set_server('my.hostname');
|
||||
request.callback(function(err, res) {
|
||||
|
||||
});
|
||||
request.request();
|
||||
```
|
||||
|
||||
**[request_unl_list([callback])](https://ripple.com/wiki/RPC_API#unl_list)**
|
||||
|
||||
**[request_unl_add(addr, comment, [callback])](https://ripple.com/wiki/RPC_API#unl_add)**
|
||||
|
||||
**[request_unl_delete(node, [callback])](https://ripple.com/wiki/RPC_API#unl_delete)**
|
||||
|
||||
**[request_peers([callback])](https://ripple.com/wiki/RPC_API#peers)**
|
||||
|
||||
|
||||
**[request_connect(ip, port, [callback])](https://ripple.com/wiki/RPC_API#connect)**
|
||||
|
||||
|
||||
|
||||
##Ledger query functions
|
||||
|
||||
**[request_ledger(ledger, [opts], [callback])](https://ripple.com/wiki/RPC_API#ledger)**
|
||||
|
||||
**request_ledger_header([callback])**
|
||||
|
||||
**[request_ledger_current([callback])](https://ripple.com/wiki/RPC_API#ledger_current)**
|
||||
|
||||
**[request_ledger_entry(type, [callback])](https://ripple.com/wiki/RPC_API#ledger_entry)**
|
||||
|
||||
**[request_subscribe(streams, [callback])](https://ripple.com/wiki/RPC_API#subscribe)**
|
||||
|
||||
Start receiving selected streams from the server.
|
||||
|
||||
**[request_unsubscribe(streams, [callback])](https://ripple.com/wiki/RPC_API#unsubscribe)**
|
||||
|
||||
Stop receiving selected streams from the server.
|
||||
|
||||
|
||||
|
||||
|
||||
##Transaction query functions
|
||||
|
||||
**[request_transaction_entry(hash, [ledger_hash], [callback])](https://ripple.com/wiki/RPC_API#transaction_entry)**
|
||||
|
||||
Searches a particular ledger for a transaction hash. Default ledger is the open ledger.
|
||||
|
||||
**[request_tx(hash, [callback])](https://ripple.com/wiki/RPC_API#tx)**
|
||||
|
||||
Searches ledger history for validated transaction hashes.
|
||||
|
||||
|
||||
|
||||
|
||||
##Account query functions
|
||||
|
||||
**[request_account_info(account, [callback])](https://ripple.com/wiki/RPC_API#account_info)**
|
||||
|
||||
Return information about the specified account.
|
||||
|
||||
```
|
||||
{
|
||||
ledger_current_index: <number>,
|
||||
account_data: {
|
||||
Account: <string>,
|
||||
Balance: <number>,
|
||||
Flags: <number>,
|
||||
LedgerEntryType: <string>,
|
||||
OwnerCount: <number>,
|
||||
PreviousTxnID: <string>,
|
||||
PreviousTxnLgrSeq: <number>,
|
||||
Sequence: <number> ,
|
||||
index: <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**[request_account_lines(accountID, account_index, current, [callback])](https://ripple.com/wiki/RPC_API#account_lines)**
|
||||
|
||||
**[request_account_offers(accountID, account_index, current, [callback])](https://ripple.com/wiki/RPC_API#account_offers)**
|
||||
|
||||
Return the specified account's outstanding offers.
|
||||
|
||||
**[request_account_tx(opts, [callback])](https://ripple.com/wiki/RPC_API#account_tx)**
|
||||
|
||||
Fetch a list of transactions that applied to this account.
|
||||
|
||||
Options:
|
||||
|
||||
+ `account`
|
||||
+ `ledger_index_min` *deprecated, -1*
|
||||
+ `ledger_index_max` *deprecated, -1*
|
||||
+ `binary` *false*
|
||||
+ `count` *false*
|
||||
+ `descending` *false*
|
||||
+ `offset` *0*
|
||||
+ `limit`
|
||||
+ `forward` *false*
|
||||
+ `fwd_marker`
|
||||
+ `rev_marker`
|
||||
|
||||
**[request_wallet_accounts(seed, [callback])](https://ripple.com/wiki/RPC_API#wallet_accounts)**
|
||||
|
||||
Return a list of accounts for a wallet.
|
||||
|
||||
+ requires trusted remote
|
||||
|
||||
**request_account_balance(account, ledger, [callback])**
|
||||
|
||||
Get the balance for an account. Returns an [Amount](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/amount.js) object.
|
||||
|
||||
**request_account_flags(account, current, [callback])**
|
||||
|
||||
Return the flags for an account.
|
||||
|
||||
**request_owner_count(account, current, [callback])**
|
||||
|
||||
Return the owner count for an account.
|
||||
|
||||
**request_ripple_balance(account, issuer, currency, current, [callback])**
|
||||
|
||||
Return a request to get a ripple balance
|
||||
|
||||
|
||||
|
||||
|
||||
##Order book query functions
|
||||
|
||||
**[request_book_offers(gets, pays, taker, [callback])](https://ripple.com/wiki/RPC_API#book_offers)**
|
||||
|
||||
Return the offers for an order book as one or more pages.
|
||||
|
||||
```js
|
||||
var request = remote.request_book_offers({
|
||||
gets: {
|
||||
'currency':'XRP'
|
||||
},
|
||||
pays: {
|
||||
'currency':'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
});
|
||||
|
||||
request.request();
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
##Transaction submission functions
|
||||
|
||||
**[request_sign(secret, tx_json, [callback])](https://ripple.com/wiki/RPC_API#sign)**
|
||||
|
||||
Sign a transaction.
|
||||
|
||||
+ requires trusted remote
|
||||
|
||||
**[request_submit([callback])](https://ripple.com/wiki/RPC_API#submit)**
|
||||
|
||||
Submit a transaction to the network. This command is used internally to submit transactions with a greater degree of reliability. See [Submitting a payment to the network](GUIDES.md#3-submitting-a-payment-to-the-network) for details.
|
||||
|
||||
|
||||
**[request_ripple_path_find(src_account, dst_account, dst_amount, src_currencies, [callback])](https://ripple.com/wiki/RPC_API#path_find)**
|
||||
|
||||
|
||||
**transaction([destination], [source], [amount], [callback])**
|
||||
|
||||
Returns a [Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) object
|
||||
|
||||
|
||||
#3. Transaction events
|
||||
|
||||
[Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) objects are EventEmitters. They may emit the following events.
|
||||
|
||||
+ `final` Transaction has erred or succeeded. This event indicates that the transaction has finished processing.
|
||||
+ `error` Transaction has erred. This event is a final state.
|
||||
+ `success` Transaction succeeded. This event is a final state.
|
||||
+ `submitted` Transaction has been submitted to the network. The submission may result in a remote error or success.
|
||||
+ `proposed` Transaction has been submitted *successfully* to the network. The transaction at this point is awaiting validation in a ledger.
|
||||
+ `timeout` Transaction submission timed out. The transaction will be resubmitted.
|
||||
+ `resubmit` Transaction is beginning resubmission.
|
||||
+ `fee_adjusted` Transaction fee has been adjusted during its pending state. The transaction fee will only be adjusted if the remote is configured for local fees, which it is by default.
|
||||
+ `abort` Transaction has been aborted. Transactions are only aborted by manual calls to `#abort`.
|
||||
+ `missing` Four ledgers have closed without detecting validated transaction
|
||||
+ `lost` Eight ledgers have closed without detecting validated transaction. Consider the transaction lost and err/finalize.
|
||||
|
||||
|
||||
#4. Amount objects
|
||||
|
||||
Coming Soon
|
||||
32
package.json
32
package.json
@@ -1,22 +1,25 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.7.19",
|
||||
"version": "0.7.35",
|
||||
"description": "Ripple JavaScript client library",
|
||||
"files": [
|
||||
"src/js/ripple/*.js",
|
||||
"build/sjcl.js",
|
||||
"bin/rsign.js"
|
||||
"src/js/*",
|
||||
"bin/*",
|
||||
"build/*",
|
||||
"test/*",
|
||||
"Makefile",
|
||||
"Gruntfile.js"
|
||||
],
|
||||
"main": "src/js/ripple",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.2.9",
|
||||
"ws": "~0.4.27",
|
||||
"extend": "~1.1.3",
|
||||
"simple-jsonrpc": "~0.0.2",
|
||||
"jshint-loader": "~0.5.0"
|
||||
"async": "~0.2.10",
|
||||
"ws": "~0.4.31",
|
||||
"extend": "~1.2.1",
|
||||
"jshint-loader": "~0.5.0",
|
||||
"lru-cache": "~2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.1",
|
||||
@@ -25,15 +28,18 @@
|
||||
"grunt-contrib-watch": "~0.4.4",
|
||||
"grunt-webpack": "~0.10.5",
|
||||
"grunt-dox": "~0.5.0",
|
||||
"buster": "~0.6.12"
|
||||
"mocha": "~1.14.0",
|
||||
"sinon-chai": "~2.4.0",
|
||||
"sinon": "~1.7.3"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/buster/bin/buster-test",
|
||||
"build": "node_modules/.bin/grunt"
|
||||
"pretest": "node_modules/.bin/grunt",
|
||||
"test": "mocha test/*-test.js",
|
||||
"build": "grunt"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/rippleFoundation/ripple-lib.git"
|
||||
"url": "git://github.com/ripple/ripple-lib.git"
|
||||
},
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
|
||||
14
scripts/verify_ledger_json.js
Normal file
14
scripts/verify_ledger_json.js
Normal file
@@ -0,0 +1,14 @@
|
||||
var fs = require('fs');
|
||||
var Ledger = require('../src/js/ripple/ledger').Ledger;
|
||||
|
||||
if (process.argc < 1) {
|
||||
console.error("Usage: scripts/verify_ledger_json path/to/ledger.json");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var json = fs.readFileSync(process.argv[2], 'utf-8');
|
||||
var ledger = Ledger.from_json(JSON.parse(json));
|
||||
|
||||
console.log("Calculated transaction hash: "+ledger.calc_tx_hash().to_hex())
|
||||
console.log("Transaction hash in header: "+ledger.ledger_json.transaction_hash);
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
// Derived from Tom Wu's jsbn code.
|
||||
//
|
||||
// Changes made for clean up and to package as a node.js module.
|
||||
|
||||
// Copyright (c) 2005-2009 Tom Wu
|
||||
// Copyright (c) 2005 Tom Wu
|
||||
// All Rights Reserved.
|
||||
// See "LICENSE" for details.
|
||||
|
||||
// Basic JavaScript BN library - subset useful for RSA encryption.
|
||||
// Extended JavaScript BN functions, required for RSA private ops.
|
||||
// Version 1.1: new BigInteger("0", 10) returns "proper" zero
|
||||
// Version 1.2: square() API, isProbablePrime fix
|
||||
|
||||
// Bits per digit
|
||||
var dbits;
|
||||
@@ -19,15 +12,15 @@ var canary = 0xdeadbeefcafe;
|
||||
var j_lm = ((canary&0xffffff)==0xefcafe);
|
||||
|
||||
// (public) Constructor
|
||||
var BigInteger = function BigInteger(a,b,c) {
|
||||
function BigInteger(a,b,c) {
|
||||
if(a != null)
|
||||
if("number" == typeof a) this.fromNumber(a,b,c);
|
||||
else if(b == null && "string" != typeof a) this.fromString(a,256);
|
||||
else this.fromString(a,b);
|
||||
};
|
||||
}
|
||||
|
||||
// return new, unset BigInteger
|
||||
var nbi = function nbi() { return new BigInteger(null); };
|
||||
function nbi() { return new BigInteger(null); }
|
||||
|
||||
// am: Compute w_j += (x*this_i), propagate carries,
|
||||
// c is initial carry, returns final carry.
|
||||
@@ -74,7 +67,6 @@ function am3(i,x,w,j,c,n) {
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
if(j_lm && 'undefined' !== typeof navigator && (navigator.appName == "Microsoft Internet Explorer")) {
|
||||
BigInteger.prototype.am = am2;
|
||||
dbits = 30;
|
||||
@@ -126,7 +118,7 @@ function bnpFromInt(x) {
|
||||
this.t = 1;
|
||||
this.s = (x<0)?-1:0;
|
||||
if(x > 0) this[0] = x;
|
||||
else if(x < -1) this[0] = x+DV;
|
||||
else if(x < -1) this[0] = x+this.DV;
|
||||
else this.t = 0;
|
||||
}
|
||||
|
||||
@@ -1122,6 +1114,7 @@ function bnpMillerRabin(t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// protected
|
||||
BigInteger.prototype.chunkSize = bnpChunkSize;
|
||||
BigInteger.prototype.toRadix = bnpToRadix;
|
||||
@@ -1137,7 +1130,31 @@ BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo;
|
||||
BigInteger.prototype.modInt = bnpModInt;
|
||||
BigInteger.prototype.millerRabin = bnpMillerRabin;
|
||||
|
||||
BigInteger.prototype.copyTo = bnpCopyTo;
|
||||
BigInteger.prototype.fromInt = bnpFromInt;
|
||||
BigInteger.prototype.fromString = bnpFromString;
|
||||
BigInteger.prototype.clamp = bnpClamp;
|
||||
BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
|
||||
BigInteger.prototype.drShiftTo = bnpDRShiftTo;
|
||||
BigInteger.prototype.lShiftTo = bnpLShiftTo;
|
||||
BigInteger.prototype.rShiftTo = bnpRShiftTo;
|
||||
BigInteger.prototype.subTo = bnpSubTo;
|
||||
BigInteger.prototype.multiplyTo = bnpMultiplyTo;
|
||||
BigInteger.prototype.squareTo = bnpSquareTo;
|
||||
BigInteger.prototype.divRemTo = bnpDivRemTo;
|
||||
BigInteger.prototype.invDigit = bnpInvDigit;
|
||||
BigInteger.prototype.isEven = bnpIsEven;
|
||||
BigInteger.prototype.exp = bnpExp;
|
||||
|
||||
// public
|
||||
BigInteger.prototype.toString = bnToString;
|
||||
BigInteger.prototype.negate = bnNegate;
|
||||
BigInteger.prototype.abs = bnAbs;
|
||||
BigInteger.prototype.compareTo = bnCompareTo;
|
||||
BigInteger.prototype.bitLength = bnBitLength;
|
||||
BigInteger.prototype.mod = bnMod;
|
||||
BigInteger.prototype.modPowInt = bnModPowInt;
|
||||
|
||||
BigInteger.prototype.clone = bnClone;
|
||||
BigInteger.prototype.intValue = bnIntValue;
|
||||
BigInteger.prototype.byteValue = bnByteValue;
|
||||
@@ -1175,6 +1192,10 @@ BigInteger.prototype.isProbablePrime = bnIsProbablePrime;
|
||||
// JSBN-specific extension
|
||||
BigInteger.prototype.square = bnSquare;
|
||||
|
||||
// "constants"
|
||||
BigInteger.ZERO = nbv(0);
|
||||
BigInteger.ONE = nbv(1);
|
||||
|
||||
// BigInteger interfaces not implemented in jsbn:
|
||||
|
||||
// BigInteger(int signum, byte[] magnitude)
|
||||
@@ -1183,37 +1204,7 @@ BigInteger.prototype.square = bnSquare;
|
||||
// int hashCode()
|
||||
// long longValue()
|
||||
// static BigInteger valueOf(long val)
|
||||
// protected
|
||||
BigInteger.prototype.copyTo = bnpCopyTo;
|
||||
BigInteger.prototype.fromInt = bnpFromInt;
|
||||
BigInteger.prototype.fromString = bnpFromString;
|
||||
BigInteger.prototype.clamp = bnpClamp;
|
||||
BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
|
||||
BigInteger.prototype.drShiftTo = bnpDRShiftTo;
|
||||
BigInteger.prototype.lShiftTo = bnpLShiftTo;
|
||||
BigInteger.prototype.rShiftTo = bnpRShiftTo;
|
||||
BigInteger.prototype.subTo = bnpSubTo;
|
||||
BigInteger.prototype.multiplyTo = bnpMultiplyTo;
|
||||
BigInteger.prototype.squareTo = bnpSquareTo;
|
||||
BigInteger.prototype.divRemTo = bnpDivRemTo;
|
||||
BigInteger.prototype.invDigit = bnpInvDigit;
|
||||
BigInteger.prototype.isEven = bnpIsEven;
|
||||
BigInteger.prototype.exp = bnpExp;
|
||||
|
||||
// public
|
||||
BigInteger.prototype.toString = bnToString;
|
||||
BigInteger.prototype.negate = bnNegate;
|
||||
BigInteger.prototype.abs = bnAbs;
|
||||
BigInteger.prototype.compareTo = bnCompareTo;
|
||||
BigInteger.prototype.bitLength = bnBitLength;
|
||||
BigInteger.prototype.mod = bnMod;
|
||||
BigInteger.prototype.modPowInt = bnModPowInt;
|
||||
BigInteger.valueOf = nbi;
|
||||
|
||||
// "constants"
|
||||
BigInteger.ZERO = nbv(0);
|
||||
BigInteger.ONE = nbv(1);
|
||||
|
||||
exports.nbi = nbi;
|
||||
exports.BigInteger = BigInteger;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
exports.BigInteger = BigInteger;
|
||||
@@ -3,75 +3,98 @@
|
||||
// You should not instantiate this class yourself, instead use Remote#account.
|
||||
//
|
||||
// Events:
|
||||
// wallet_clean : True, iff the wallet has been updated.
|
||||
// wallet_dirty : True, iff the wallet needs to be updated.
|
||||
// balance : The current stamp balance.
|
||||
// wallet_clean : True, iff the wallet has been updated.
|
||||
// wallet_dirty : True, iff the wallet needs to be updated.
|
||||
// balance: The current stamp balance.
|
||||
// balance_proposed
|
||||
//
|
||||
|
||||
// var network = require("./network.js");
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var extend = require('extend');
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var TransactionManager = require('./transactionmanager').TransactionManager;
|
||||
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
/**
|
||||
* @constructor Account
|
||||
* @param {Remote} remote
|
||||
* @param {String} account
|
||||
*/
|
||||
|
||||
var extend = require('extend');
|
||||
|
||||
var Account = function (remote, account) {
|
||||
function Account(remote, account) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._account = UInt160.from_json(account);
|
||||
this._remote = remote;
|
||||
this._account = UInt160.from_json(account);
|
||||
this._account_id = this._account.to_json();
|
||||
this._subs = 0;
|
||||
this._subs = 0;
|
||||
|
||||
// Ledger entry object
|
||||
// Important: This must never be overwritten, only extend()-ed
|
||||
this._entry = {};
|
||||
this._entry = { };
|
||||
|
||||
this.on('newListener', function (type, listener) {
|
||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
function listenerAdded(type, listener) {
|
||||
if (~Account.subscribeEvents.indexOf(type)) {
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.request_subscribe()
|
||||
.accounts(self._account_id)
|
||||
.request();
|
||||
.add_account(self._account_id)
|
||||
.broadcast();
|
||||
}
|
||||
self._subs += 1;
|
||||
self._subs += 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.on('removeListener', function (type, listener) {
|
||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
||||
self._subs -= 1;
|
||||
this.on('newListener', listenerAdded);
|
||||
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
function listenerRemoved(type, listener) {
|
||||
if (~Account.subscribeEvents.indexOf(type)) {
|
||||
self._subs -= 1;
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.request_unsubscribe()
|
||||
.accounts(self._account_id)
|
||||
.request();
|
||||
.add_account(self._account_id)
|
||||
.broadcast();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this._remote.on('prepare_subscribe', function (request) {
|
||||
if (self._subs) request.accounts(self._account_id);
|
||||
});
|
||||
this.on('removeListener', listenerRemoved);
|
||||
|
||||
function attachAccount(request) {
|
||||
if (self._account.is_valid() && self._subs) {
|
||||
request.add_account(self._account_id);
|
||||
}
|
||||
};
|
||||
|
||||
this._remote.on('prepare_subscribe', attachAccount);
|
||||
|
||||
function handleTransaction(transaction) {
|
||||
if (!transaction.mmeta) return;
|
||||
|
||||
this.on('transaction', function (msg) {
|
||||
var changed = false;
|
||||
msg.mmeta.each(function (an) {
|
||||
if (an.entryType === 'AccountRoot' &&
|
||||
an.fields.Account === self._account_id) {
|
||||
|
||||
transaction.mmeta.each(function(an) {
|
||||
var isAccount = an.fields.Account === self._account_id;
|
||||
var isAccountRoot = isAccount && (an.entryType === 'AccountRoot');
|
||||
|
||||
if (isAccountRoot) {
|
||||
extend(self._entry, an.fieldsNew, an.fieldsFinal);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
self.emit('entry', self._entry);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.on('transaction', handleTransaction);
|
||||
|
||||
this._transactionManager = new TransactionManager(this);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -81,10 +104,10 @@ util.inherits(Account, EventEmitter);
|
||||
/**
|
||||
* List of events that require a remote subscription to the account.
|
||||
*/
|
||||
Account.subscribe_events = ['transaction', 'entry'];
|
||||
|
||||
Account.prototype.to_json = function ()
|
||||
{
|
||||
Account.subscribeEvents = [ 'transaction', 'entry' ];
|
||||
|
||||
Account.prototype.toJson = function() {
|
||||
return this._account.to_json();
|
||||
};
|
||||
|
||||
@@ -93,36 +116,67 @@ Account.prototype.to_json = function ()
|
||||
*
|
||||
* Note: This does not tell you whether the account exists in the ledger.
|
||||
*/
|
||||
Account.prototype.is_valid = function ()
|
||||
{
|
||||
|
||||
Account.prototype.isValid = function() {
|
||||
return this._account.is_valid();
|
||||
};
|
||||
|
||||
/**
|
||||
* Request account info
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Account.prototype.getInfo = function(callback) {
|
||||
return this._remote.request_account_info(this._account_id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the current AccountRoot entry.
|
||||
*
|
||||
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
|
||||
* "entry" event.
|
||||
*
|
||||
* @param {function (err, entry)} callback Called with the result
|
||||
* @param {Function} callback
|
||||
*/
|
||||
Account.prototype.entry = function (callback)
|
||||
{
|
||||
|
||||
Account.prototype.entry = function(callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
self._remote.request_account_info(this._account_id)
|
||||
.on('success', function (e) {
|
||||
extend(self._entry, e.account_data);
|
||||
function accountInfo(err, info) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
extend(self._entry, info.account_data);
|
||||
self.emit('entry', self._entry);
|
||||
callback(null, info);
|
||||
}
|
||||
};
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
callback(null, e);
|
||||
}
|
||||
})
|
||||
.on('error', function (e) {
|
||||
callback(e);
|
||||
})
|
||||
.request();
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Account.prototype.getNextSequence = function(callback) {
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
function accountInfo(err, info) {
|
||||
if (err &&
|
||||
"object" === typeof err &&
|
||||
"object" === typeof err.remote &&
|
||||
err.remote.error === "actNotFound") {
|
||||
// New accounts will start out as sequence zero
|
||||
callback(null, 0);
|
||||
} else if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, info.account_data.Sequence);
|
||||
}
|
||||
};
|
||||
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -133,25 +187,55 @@ Account.prototype.entry = function (callback)
|
||||
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
|
||||
* "lines" event. (Not yet implemented.)
|
||||
*
|
||||
* @param {function (err, lines)} callback Called with the result
|
||||
* @param {function(err, lines)} callback Called with the result
|
||||
*/
|
||||
Account.prototype.lines = function (callback)
|
||||
{
|
||||
|
||||
Account.prototype.lines = function(callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
self._remote.request_account_lines(this._account_id)
|
||||
.on('success', function (e) {
|
||||
self._lines = e.lines;
|
||||
function accountLines(err, res) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
self._lines = res.lines;
|
||||
self.emit('lines', self._lines);
|
||||
callback(null, res);
|
||||
}
|
||||
}
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
callback(null, e);
|
||||
}
|
||||
})
|
||||
.on('error', function (e) {
|
||||
callback(e);
|
||||
})
|
||||
.request();
|
||||
this._remote.requestAccountLines(this._account_id, accountLines);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve this account's single trust line.
|
||||
*
|
||||
* @param {string} currency Currency
|
||||
* @param {string} address Ripple address
|
||||
* @param {function(err, line)} callback Called with the result
|
||||
* @returns {Account}
|
||||
*/
|
||||
|
||||
Account.prototype.line = function(currency,address,callback) {
|
||||
var self = this;
|
||||
var found;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
self.lines(function(err, data) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
var line = data.lines.filter(function(line) {
|
||||
if (line.account === address && line.currency === currency) {
|
||||
return line;
|
||||
}
|
||||
})[0];
|
||||
|
||||
callback(null, line);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -161,17 +245,39 @@ Account.prototype.lines = function (callback)
|
||||
*
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*
|
||||
* @param {Object} message
|
||||
*/
|
||||
Account.prototype.notifyTx = function (message)
|
||||
{
|
||||
|
||||
Account.prototype.notify =
|
||||
Account.prototype.notifyTx = function(transaction) {
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (this._subs) {
|
||||
this.emit('transaction', message);
|
||||
this.emit('transaction', transaction);
|
||||
|
||||
var account = transaction.transaction.Account;
|
||||
|
||||
if (!account) return;
|
||||
|
||||
var isThisAccount = account === this._account_id;
|
||||
|
||||
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound', transaction);
|
||||
}
|
||||
};
|
||||
|
||||
exports.Account = Account;
|
||||
/**
|
||||
* Submit a transaction to an account's
|
||||
* transaction manager
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
|
||||
Account.prototype.submit = function(transaction) {
|
||||
this._transactionManager.submit(transaction);
|
||||
};
|
||||
|
||||
exports.Account = Account;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,89 +1,83 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var utils = require('./utils');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var Base = {};
|
||||
|
||||
var alphabets = Base.alphabets = {
|
||||
'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
|
||||
'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz",
|
||||
'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
var alphabets = Base.alphabets = {
|
||||
ripple : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz",
|
||||
tipple : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz",
|
||||
bitcoin : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
};
|
||||
|
||||
extend(Base, {
|
||||
'VER_NONE' : 1,
|
||||
'VER_NODE_PUBLIC' : 28,
|
||||
'VER_NODE_PRIVATE' : 32,
|
||||
'VER_ACCOUNT_ID' : 0,
|
||||
'VER_ACCOUNT_PUBLIC' : 35,
|
||||
'VER_ACCOUNT_PRIVATE' : 34,
|
||||
'VER_FAMILY_GENERATOR' : 41,
|
||||
'VER_FAMILY_SEED' : 33
|
||||
VER_NONE : 1,
|
||||
VER_NODE_PUBLIC : 28,
|
||||
VER_NODE_PRIVATE : 32,
|
||||
VER_ACCOUNT_ID : 0,
|
||||
VER_ACCOUNT_PUBLIC : 35,
|
||||
VER_ACCOUNT_PRIVATE : 34,
|
||||
VER_FAMILY_GENERATOR : 41,
|
||||
VER_FAMILY_SEED : 33
|
||||
});
|
||||
|
||||
var sha256 = function (bytes) {
|
||||
function sha256(bytes) {
|
||||
return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
|
||||
};
|
||||
|
||||
var sha256hash = function (bytes) {
|
||||
function sha256hash(bytes) {
|
||||
return sha256(sha256(bytes));
|
||||
};
|
||||
|
||||
// --> input: big-endian array of bytes.
|
||||
// <-- string at least as long as input.
|
||||
Base.encode = function (input, alpha) {
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_q = nbi();
|
||||
var bi_r = nbi();
|
||||
var bi_value = new BigInteger(input);
|
||||
var buffer = [];
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_q = new BigInteger();
|
||||
var bi_r = new BigInteger();
|
||||
var bi_value = new BigInteger(input);
|
||||
var buffer = [];
|
||||
|
||||
while (bi_value.compareTo(BigInteger.ZERO) > 0)
|
||||
{
|
||||
while (bi_value.compareTo(BigInteger.ZERO) > 0) {
|
||||
bi_value.divRemTo(bi_base, bi_q, bi_r);
|
||||
bi_q.copyTo(bi_value);
|
||||
|
||||
buffer.push(alphabet[bi_r.intValue()]);
|
||||
}
|
||||
|
||||
var i;
|
||||
|
||||
for (i = 0; i != input.length && !input[i]; i += 1) {
|
||||
for (var i=0; i !== input.length && !input[i]; i += 1) {
|
||||
buffer.push(alphabet[0]);
|
||||
}
|
||||
|
||||
return buffer.reverse().join("");
|
||||
return buffer.reverse().join('');
|
||||
};
|
||||
|
||||
// --> input: String
|
||||
// <-- array of bytes or undefined.
|
||||
Base.decode = function (input, alpha) {
|
||||
if ("string" !== typeof input) return undefined;
|
||||
if (typeof input !== 'string') {
|
||||
return void(0);
|
||||
}
|
||||
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_value = nbi();
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_value = new BigInteger();
|
||||
var i;
|
||||
|
||||
for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1)
|
||||
;
|
||||
;
|
||||
|
||||
for (; i != input.length; i += 1) {
|
||||
var v = alphabet.indexOf(input[i]);
|
||||
for (; i !== input.length; i += 1) {
|
||||
var v = alphabet.indexOf(input[i]);
|
||||
|
||||
if (v < 0)
|
||||
return undefined;
|
||||
|
||||
var r = nbi();
|
||||
if (v < 0) {
|
||||
return void(0);
|
||||
}
|
||||
|
||||
var r = new BigInteger();
|
||||
r.fromInt(v);
|
||||
|
||||
bi_value = bi_value.multiply(bi_base).add(r);
|
||||
}
|
||||
|
||||
@@ -93,69 +87,83 @@ Base.decode = function (input, alpha) {
|
||||
var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; });
|
||||
var extra = 0;
|
||||
|
||||
while (extra != bytes.length && !bytes[extra])
|
||||
while (extra != bytes.length && !bytes[extra]) {
|
||||
extra += 1;
|
||||
}
|
||||
|
||||
if (extra)
|
||||
if (extra) {
|
||||
bytes = bytes.slice(extra);
|
||||
}
|
||||
|
||||
var zeros = 0;
|
||||
|
||||
while (zeros !== input.length && input[zeros] === alphabet[0])
|
||||
while (zeros !== input.length && input[zeros] === alphabet[0]) {
|
||||
zeros += 1;
|
||||
}
|
||||
|
||||
return [].concat(utils.arraySet(zeros, 0), bytes);
|
||||
};
|
||||
|
||||
Base.verify_checksum = function (bytes) {
|
||||
var computed = sha256hash(bytes.slice(0, -4)).slice(0, 4);
|
||||
var checksum = bytes.slice(-4);
|
||||
var computed = sha256hash(bytes.slice(0, -4)).slice(0, 4);
|
||||
var checksum = bytes.slice(-4);
|
||||
var result = true;
|
||||
|
||||
for (var i = 0; i < 4; i++)
|
||||
if (computed[i] !== checksum[i])
|
||||
return false;
|
||||
for (var i=0; i<4; i++) {
|
||||
if (computed[i] !== checksum[i]) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return result;
|
||||
};
|
||||
|
||||
// --> input: Array
|
||||
// <-- String
|
||||
Base.encode_check = function (version, input, alphabet) {
|
||||
var buffer = [].concat(version, input);
|
||||
var check = sha256(sha256(buffer)).slice(0, 4);
|
||||
var buffer = [].concat(version, input);
|
||||
var check = sha256(sha256(buffer)).slice(0, 4);
|
||||
|
||||
return Base.encode([].concat(buffer, check), alphabet);
|
||||
}
|
||||
};
|
||||
|
||||
// --> input : String
|
||||
// <-- NaN || BigInteger
|
||||
Base.decode_check = function (version, input, alphabet) {
|
||||
var buffer = Base.decode(input, alphabet);
|
||||
|
||||
if (!buffer || buffer.length < 5)
|
||||
if (!buffer || buffer.length < 5) {
|
||||
return NaN;
|
||||
|
||||
// Single valid version
|
||||
if ("number" === typeof version && buffer[0] !== version)
|
||||
return NaN;
|
||||
|
||||
// Multiple allowed versions
|
||||
if ("object" === typeof version && Array.isArray(version)) {
|
||||
var match = false;
|
||||
for (var i = 0, l = version.length; i < l; i++) {
|
||||
match |= version[i] === buffer[0];
|
||||
}
|
||||
if (!match) return NaN;
|
||||
}
|
||||
|
||||
if (!Base.verify_checksum(buffer))
|
||||
// Single valid version
|
||||
if (typeof version === 'number' && buffer[0] !== version) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// Multiple allowed versions
|
||||
if (Array.isArray(version)) {
|
||||
var match = false;
|
||||
|
||||
for (var i=0, l=version.length; i<l; i++) {
|
||||
match |= version[i] === buffer[0];
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Base.verify_checksum(buffer)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
|
||||
// intrepret the value as a negative number
|
||||
buffer[0] = 0;
|
||||
|
||||
return new BigInteger(buffer.slice(0, -4), 256);
|
||||
}
|
||||
};
|
||||
|
||||
exports.Base = Base;
|
||||
|
||||
@@ -1,90 +1,431 @@
|
||||
var ST = require("./serializedtypes");
|
||||
/**
|
||||
* Data type map.
|
||||
*
|
||||
* Mapping of type ids to data types. The type id is specified by the high
|
||||
*/
|
||||
var TYPES_MAP = exports.types = [
|
||||
void(0),
|
||||
|
||||
// Common
|
||||
'Int16', // 1
|
||||
'Int32', // 2
|
||||
'Int64', // 3
|
||||
'Hash128', // 4
|
||||
'Hash256', // 5
|
||||
'Amount', // 6
|
||||
'VL', // 7
|
||||
'Account', // 8
|
||||
|
||||
// 9-13 reserved
|
||||
void(0), // 9
|
||||
void(0), // 10
|
||||
void(0), // 11
|
||||
void(0), // 12
|
||||
void(0), // 13
|
||||
|
||||
'Object', // 14
|
||||
'Array', // 15
|
||||
|
||||
// Uncommon
|
||||
'Int8', // 16
|
||||
'Hash160', // 17
|
||||
'PathSet', // 18
|
||||
'Vector256' // 19
|
||||
];
|
||||
|
||||
/**
|
||||
* Field type map.
|
||||
*
|
||||
* Mapping of field type id to field type name.
|
||||
*/
|
||||
|
||||
var FIELDS_MAP = exports.fields = {
|
||||
// Common types
|
||||
1: { // Int16
|
||||
1: 'LedgerEntryType',
|
||||
2: 'TransactionType'
|
||||
},
|
||||
2: { // Int32
|
||||
2: 'Flags',
|
||||
3: 'SourceTag',
|
||||
4: 'Sequence',
|
||||
5: 'PreviousTxnLgrSeq',
|
||||
6: 'LedgerSequence',
|
||||
7: 'CloseTime',
|
||||
8: 'ParentCloseTime',
|
||||
9: 'SigningTime',
|
||||
10: 'Expiration',
|
||||
11: 'TransferRate',
|
||||
12: 'WalletSize',
|
||||
13: 'OwnerCount',
|
||||
14: 'DestinationTag',
|
||||
// Skip 15
|
||||
16: 'HighQualityIn',
|
||||
17: 'HighQualityOut',
|
||||
18: 'LowQualityIn',
|
||||
19: 'LowQualityOut',
|
||||
20: 'QualityIn',
|
||||
21: 'QualityOut',
|
||||
22: 'StampEscrow',
|
||||
23: 'BondAmount',
|
||||
24: 'LoadFee',
|
||||
25: 'OfferSequence',
|
||||
26: 'FirstLedgerSequence',
|
||||
27: 'LastLedgerSequence',
|
||||
28: 'TransactionIndex',
|
||||
29: 'OperationLimit',
|
||||
30: 'ReferenceFeeUnits',
|
||||
31: 'ReserveBase',
|
||||
32: 'ReserveIncrement',
|
||||
33: 'SetFlag',
|
||||
34: 'ClearFlag'
|
||||
},
|
||||
3: { // Int64
|
||||
1: 'IndexNext',
|
||||
2: 'IndexPrevious',
|
||||
3: 'BookNode',
|
||||
4: 'OwnerNode',
|
||||
5: 'BaseFee',
|
||||
6: 'ExchangeRate',
|
||||
7: 'LowNode',
|
||||
8: 'HighNode'
|
||||
},
|
||||
4: { // Hash128
|
||||
1: 'EmailHash'
|
||||
},
|
||||
5: { // Hash256
|
||||
1: 'LedgerHash',
|
||||
2: 'ParentHash',
|
||||
3: 'TransactionHash',
|
||||
4: 'AccountHash',
|
||||
5: 'PreviousTxnID',
|
||||
6: 'LedgerIndex',
|
||||
7: 'WalletLocator',
|
||||
8: 'RootIndex',
|
||||
9: 'AccountTxnID',
|
||||
16: 'BookDirectory',
|
||||
17: 'InvoiceID',
|
||||
18: 'Nickname',
|
||||
19: 'Feature'
|
||||
},
|
||||
6: { // Amount
|
||||
1: 'Amount',
|
||||
2: 'Balance',
|
||||
3: 'LimitAmount',
|
||||
4: 'TakerPays',
|
||||
5: 'TakerGets',
|
||||
6: 'LowLimit',
|
||||
7: 'HighLimit',
|
||||
8: 'Fee',
|
||||
9: 'SendMax',
|
||||
16: 'MinimumOffer',
|
||||
17: 'RippleEscrow',
|
||||
18: 'DeliveredAmount'
|
||||
},
|
||||
7: { // VL
|
||||
1: 'PublicKey',
|
||||
2: 'MessageKey',
|
||||
3: 'SigningPubKey',
|
||||
4: 'TxnSignature',
|
||||
5: 'Generator',
|
||||
6: 'Signature',
|
||||
7: 'Domain',
|
||||
8: 'FundCode',
|
||||
9: 'RemoveCode',
|
||||
10: 'ExpireCode',
|
||||
11: 'CreateCode',
|
||||
12: 'MemoType',
|
||||
13: 'MemoData'
|
||||
},
|
||||
8: { // Account
|
||||
1: 'Account',
|
||||
2: 'Owner',
|
||||
3: 'Destination',
|
||||
4: 'Issuer',
|
||||
7: 'Target',
|
||||
8: 'RegularKey'
|
||||
},
|
||||
14: { // Object
|
||||
1: void(0), //end of Object
|
||||
2: 'TransactionMetaData',
|
||||
3: 'CreatedNode',
|
||||
4: 'DeletedNode',
|
||||
5: 'ModifiedNode',
|
||||
6: 'PreviousFields',
|
||||
7: 'FinalFields',
|
||||
8: 'NewFields',
|
||||
9: 'TemplateEntry',
|
||||
10: 'Memo'
|
||||
},
|
||||
15: { // Array
|
||||
1: void(0), //end of Array
|
||||
2: 'SigningAccounts',
|
||||
3: 'TxnSignatures',
|
||||
4: 'Signatures',
|
||||
5: 'Template',
|
||||
6: 'Necessary',
|
||||
7: 'Sufficient',
|
||||
8: 'AffectedNodes',
|
||||
9: 'Memos'
|
||||
},
|
||||
|
||||
// Uncommon types
|
||||
16: { // Int8
|
||||
1: 'CloseResolution',
|
||||
2: 'TemplateEntryType',
|
||||
3: 'TransactionResult'
|
||||
},
|
||||
17: { // Hash160
|
||||
1: 'TakerPaysCurrency',
|
||||
2: 'TakerPaysIssuer',
|
||||
3: 'TakerGetsCurrency',
|
||||
4: 'TakerGetsIssuer'
|
||||
},
|
||||
18: { // PathSet
|
||||
1: 'Paths'
|
||||
},
|
||||
19: { // Vector256
|
||||
1: 'Indexes',
|
||||
2: 'Hashes',
|
||||
3: 'Features'
|
||||
}
|
||||
};
|
||||
|
||||
var INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
|
||||
|
||||
Object.keys(FIELDS_MAP).forEach(function(k1) {
|
||||
Object.keys(FIELDS_MAP[k1]).forEach(function(k2) {
|
||||
INVERSE_FIELDS_MAP[FIELDS_MAP[k1][k2]] = [ Number(k1), Number(k2) ];
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var REQUIRED = exports.REQUIRED = 0,
|
||||
OPTIONAL = exports.OPTIONAL = 1,
|
||||
DEFAULT = exports.DEFAULT = 2;
|
||||
|
||||
ST.Int16.id = 1;
|
||||
ST.Int32.id = 2;
|
||||
ST.Int64.id = 3;
|
||||
ST.Hash128.id = 4;
|
||||
ST.Hash256.id = 5;
|
||||
ST.Amount.id = 6;
|
||||
ST.VariableLength.id = 7;
|
||||
ST.Account.id = 8;
|
||||
ST.Object.id = 14;
|
||||
ST.Array.id = 15;
|
||||
ST.Int8.id = 16;
|
||||
ST.Hash160.id = 17;
|
||||
ST.PathSet.id = 18;
|
||||
ST.Vector256.id = 19;
|
||||
|
||||
var base = [
|
||||
[ 'TransactionType' , REQUIRED, 2, ST.Int16 ],
|
||||
[ 'Flags' , OPTIONAL, 2, ST.Int32 ],
|
||||
[ 'SourceTag' , OPTIONAL, 3, ST.Int32 ],
|
||||
[ 'Account' , REQUIRED, 1, ST.Account ],
|
||||
[ 'Sequence' , REQUIRED, 4, ST.Int32 ],
|
||||
[ 'Fee' , REQUIRED, 8, ST.Amount ],
|
||||
[ 'OperationLimit' , OPTIONAL, 29, ST.Int32 ],
|
||||
[ 'SigningPubKey' , REQUIRED, 3, ST.VariableLength ],
|
||||
[ 'TxnSignature' , OPTIONAL, 4, ST.VariableLength ]
|
||||
[ 'TransactionType' , REQUIRED ],
|
||||
[ 'Flags' , OPTIONAL ],
|
||||
[ 'SourceTag' , OPTIONAL ],
|
||||
[ 'Account' , REQUIRED ],
|
||||
[ 'Sequence' , REQUIRED ],
|
||||
[ 'Fee' , REQUIRED ],
|
||||
[ 'OperationLimit' , OPTIONAL ],
|
||||
[ 'SigningPubKey' , REQUIRED ],
|
||||
[ 'TxnSignature' , OPTIONAL ]
|
||||
];
|
||||
|
||||
exports.tx = {
|
||||
AccountSet: [3].concat(base, [
|
||||
[ 'EmailHash' , OPTIONAL, 1, ST.Hash128 ],
|
||||
[ 'WalletLocator' , OPTIONAL, 7, ST.Hash256 ],
|
||||
[ 'WalletSize' , OPTIONAL, 12, ST.Int32 ],
|
||||
[ 'MessageKey' , OPTIONAL, 2, ST.VariableLength ],
|
||||
[ 'Domain' , OPTIONAL, 7, ST.VariableLength ],
|
||||
[ 'TransferRate' , OPTIONAL, 11, ST.Int32 ]
|
||||
[ 'EmailHash' , OPTIONAL ],
|
||||
[ 'WalletLocator' , OPTIONAL ],
|
||||
[ 'WalletSize' , OPTIONAL ],
|
||||
[ 'MessageKey' , OPTIONAL ],
|
||||
[ 'Domain' , OPTIONAL ],
|
||||
[ 'TransferRate' , OPTIONAL ]
|
||||
]),
|
||||
TrustSet: [20].concat(base, [
|
||||
[ 'LimitAmount' , OPTIONAL, 3, ST.Amount ],
|
||||
[ 'QualityIn' , OPTIONAL, 20, ST.Int32 ],
|
||||
[ 'QualityOut' , OPTIONAL, 21, ST.Int32 ]
|
||||
[ 'LimitAmount' , OPTIONAL ],
|
||||
[ 'QualityIn' , OPTIONAL ],
|
||||
[ 'QualityOut' , OPTIONAL ]
|
||||
]),
|
||||
OfferCreate: [7].concat(base, [
|
||||
[ 'TakerPays' , REQUIRED, 4, ST.Amount ],
|
||||
[ 'TakerGets' , REQUIRED, 5, ST.Amount ],
|
||||
[ 'Expiration' , OPTIONAL, 10, ST.Int32 ]
|
||||
[ 'TakerPays' , REQUIRED ],
|
||||
[ 'TakerGets' , REQUIRED ],
|
||||
[ 'Expiration' , OPTIONAL ]
|
||||
]),
|
||||
OfferCancel: [8].concat(base, [
|
||||
[ 'OfferSequence' , REQUIRED, 25, ST.Int32 ]
|
||||
[ 'OfferSequence' , REQUIRED ]
|
||||
]),
|
||||
SetRegularKey: [5].concat(base, [
|
||||
[ 'RegularKey' , REQUIRED, 8, ST.Account ]
|
||||
[ 'RegularKey' , REQUIRED ]
|
||||
]),
|
||||
Payment: [0].concat(base, [
|
||||
[ 'Destination' , REQUIRED, 3, ST.Account ],
|
||||
[ 'Amount' , REQUIRED, 1, ST.Amount ],
|
||||
[ 'SendMax' , OPTIONAL, 9, ST.Amount ],
|
||||
[ 'Paths' , DEFAULT , 1, ST.PathSet ],
|
||||
[ 'InvoiceID' , OPTIONAL, 17, ST.Hash256 ],
|
||||
[ 'DestinationTag' , OPTIONAL, 14, ST.Int32 ]
|
||||
[ 'Destination' , REQUIRED ],
|
||||
[ 'Amount' , REQUIRED ],
|
||||
[ 'SendMax' , OPTIONAL ],
|
||||
[ 'Paths' , DEFAULT ],
|
||||
[ 'InvoiceID' , OPTIONAL ],
|
||||
[ 'DestinationTag' , OPTIONAL ]
|
||||
]),
|
||||
Contract: [9].concat(base, [
|
||||
[ 'Expiration' , REQUIRED, 10, ST.Int32 ],
|
||||
[ 'BondAmount' , REQUIRED, 23, ST.Int32 ],
|
||||
[ 'StampEscrow' , REQUIRED, 22, ST.Int32 ],
|
||||
[ 'RippleEscrow' , REQUIRED, 17, ST.Amount ],
|
||||
[ 'CreateCode' , OPTIONAL, 11, ST.VariableLength ],
|
||||
[ 'FundCode' , OPTIONAL, 8, ST.VariableLength ],
|
||||
[ 'RemoveCode' , OPTIONAL, 9, ST.VariableLength ],
|
||||
[ 'ExpireCode' , OPTIONAL, 10, ST.VariableLength ]
|
||||
[ 'Expiration' , REQUIRED ],
|
||||
[ 'BondAmount' , REQUIRED ],
|
||||
[ 'StampEscrow' , REQUIRED ],
|
||||
[ 'RippleEscrow' , REQUIRED ],
|
||||
[ 'CreateCode' , OPTIONAL ],
|
||||
[ 'FundCode' , OPTIONAL ],
|
||||
[ 'RemoveCode' , OPTIONAL ],
|
||||
[ 'ExpireCode' , OPTIONAL ]
|
||||
]),
|
||||
RemoveContract: [10].concat(base, [
|
||||
[ 'Target' , REQUIRED, 7, ST.Account ]
|
||||
[ 'Target' , REQUIRED ]
|
||||
]),
|
||||
EnableFeature: [100].concat(base, [
|
||||
[ 'Feature' , REQUIRED, 19, ST.Hash256 ]
|
||||
[ 'Feature' , REQUIRED ]
|
||||
]),
|
||||
SetFee: [101].concat(base, [
|
||||
[ 'Features' , REQUIRED, 9, ST.Array ],
|
||||
[ 'BaseFee' , REQUIRED, 5, ST.Int64 ],
|
||||
[ 'ReferenceFeeUnits' , REQUIRED, 30, ST.Int32 ],
|
||||
[ 'ReserveBase' , REQUIRED, 31, ST.Int32 ],
|
||||
[ 'ReserveIncrement' , REQUIRED, 32, ST.Int32 ]
|
||||
[ 'Features' , REQUIRED ],
|
||||
[ 'BaseFee' , REQUIRED ],
|
||||
[ 'ReferenceFeeUnits' , REQUIRED ],
|
||||
[ 'ReserveBase' , REQUIRED ],
|
||||
[ 'ReserveIncrement' , REQUIRED ]
|
||||
])
|
||||
};
|
||||
|
||||
exports.ledger = {
|
||||
AccountRoot: [97],
|
||||
Contract: [99],
|
||||
DirectoryNode: [100],
|
||||
Features: [102],
|
||||
GeneratorMap: [103],
|
||||
LedgerHashes: [104],
|
||||
Nickname: [110],
|
||||
Offer: [111],
|
||||
RippleState: [114],
|
||||
FeeSettings: [115]
|
||||
};
|
||||
/*
|
||||
TODO:
|
||||
Need `base` factored out
|
||||
AccountRoot needs AccountTxnID
|
||||
|
||||
{
|
||||
'AccountRoot': [97,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['Sequence', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['TransferRate', OPTIONAL],
|
||||
['WalletSize', OPTIONAL],
|
||||
['OwnerCount', REQUIRED],
|
||||
['EmailHash', OPTIONAL],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['WalletLocator', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['MessageKey', OPTIONAL,],
|
||||
['Domain', OPTIONAL,],
|
||||
['Account', REQUIRED],
|
||||
['RegularKey', OPTIONAL]],
|
||||
'Contract': [99,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['Expiration', REQUIRED],
|
||||
['BondAmount', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['FundCode', OPTIONAL],
|
||||
['RemoveCode', OPTIONAL],
|
||||
['ExpireCode', OPTIONAL],
|
||||
['CreateCode', OPTIONAL],
|
||||
['Account', REQUIRED],
|
||||
['Owner', REQUIRED],
|
||||
['Issuer', REQUIRED]],
|
||||
'DirectoryNode': [100,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['IndexNext', OPTIONAL],
|
||||
['IndexPrevious', OPTIONAL],
|
||||
['ExchangeRate', OPTIONAL],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['RootIndex', REQUIRED],
|
||||
['Owner', OPTIONAL],
|
||||
['TakerPaysCurrency', OPTIONAL],
|
||||
['TakerPaysIssuer', OPTIONAL],
|
||||
['TakerGetsCurrency', OPTIONAL],
|
||||
['TakerGetsIssuer', OPTIONAL],
|
||||
['Indexes', REQUIRED]],
|
||||
'EnabledFeatures': [102,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Features', REQUIRED]],
|
||||
'FeeSettings': [115,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['ReferenceFeeUnits', REQUIRED],
|
||||
['ReserveBase', REQUIRED],
|
||||
['ReserveIncrement', REQUIRED],
|
||||
['BaseFee', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL]],
|
||||
'GeneratorMap': [103,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Generator', REQUIRED,]],
|
||||
'LedgerHashes': [104,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['FirstLedgerSequence', OPTIONAL],
|
||||
['LastLedgerSequence', OPTIONAL],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Hashes', REQUIRED]],
|
||||
'Nickname': [110,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['MinimumOffer', OPTIONAL],
|
||||
['Account', REQUIRED]],
|
||||
'Offer': [111,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['Sequence', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['Expiration', OPTIONAL],
|
||||
['BookNode', REQUIRED],
|
||||
['OwnerNode', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['BookDirectory', REQUIRED],
|
||||
['TakerPays', REQUIRED],
|
||||
['TakerGets', REQUIRED],
|
||||
['Account', REQUIRED]],
|
||||
'RippleState': [114,
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['HighQualityIn', OPTIONAL],
|
||||
['HighQualityOut', OPTIONAL],
|
||||
['LowQualityIn', OPTIONAL],
|
||||
['LowQualityOut', OPTIONAL],
|
||||
['LowNode', OPTIONAL],
|
||||
['HighNode', OPTIONAL],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['LowLimit', REQUIRED],
|
||||
['HighLimit', REQUIRED]]
|
||||
}
|
||||
*/
|
||||
|
||||
exports.metadata = [
|
||||
[ 'TransactionIndex' , REQUIRED ],
|
||||
[ 'TransactionResult' , REQUIRED ],
|
||||
[ 'AffectedNodes' , REQUIRED ]
|
||||
];
|
||||
|
||||
exports.ter = {
|
||||
tesSUCCESS: 0,
|
||||
tecCLAIM: 100,
|
||||
tecPATH_PARTIAL: 101,
|
||||
tecUNFUNDED_ADD: 102,
|
||||
tecUNFUNDED_OFFER: 103,
|
||||
tecUNFUNDED_PAYMENT: 104,
|
||||
tecFAILED_PROCESSING: 105,
|
||||
tecDIR_FULL: 121,
|
||||
tecINSUF_RESERVE_LINE: 122,
|
||||
tecINSUF_RESERVE_OFFER: 123,
|
||||
tecNO_DST: 124,
|
||||
tecNO_DST_INSUF_XRP: 125,
|
||||
tecNO_LINE_INSUF_RESERVE: 126,
|
||||
tecNO_LINE_REDUNDANT: 127,
|
||||
tecPATH_DRY: 128,
|
||||
tecUNFUNDED: 129,
|
||||
tecMASTER_DISABLED: 130,
|
||||
tecNO_REGULAR_KEY: 131,
|
||||
tecOWNERS: 132
|
||||
};
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
|
||||
var extend = require('extend');
|
||||
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Float = require('./float').Float;
|
||||
var utils = require('./utils');
|
||||
|
||||
//
|
||||
// Currency support
|
||||
//
|
||||
|
||||
// XXX Internal form should be UInt160.
|
||||
var Currency = function () {
|
||||
var Currency = extend(function () {
|
||||
// Internal form: 0 = XRP. 3 letter-code.
|
||||
// XXX Internal should be 0 or hex with three letter annotation when valid.
|
||||
|
||||
@@ -14,85 +19,131 @@ var Currency = function () {
|
||||
// XXX Should support hex, C++ doesn't currently allow it.
|
||||
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
// Given "USD" return the json.
|
||||
Currency.json_rewrite = function (j) {
|
||||
return Currency.from_json(j).to_json();
|
||||
};
|
||||
this._update();
|
||||
}, UInt160);
|
||||
|
||||
Currency.from_json = function (j) {
|
||||
if (j instanceof Currency) {
|
||||
Currency.prototype = extend({}, UInt160.prototype);
|
||||
Currency.prototype.constructor = Currency;
|
||||
|
||||
Currency.HEX_CURRENCY_BAD = "0000000000000000000000005852500000000000";
|
||||
|
||||
Currency.from_json = function (j, shouldInterpretXrpAsIou) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return new Currency().parse_json(j);
|
||||
return (new this()).parse_json(j, shouldInterpretXrpAsIou);
|
||||
}
|
||||
};
|
||||
|
||||
Currency.from_bytes = function (j) {
|
||||
if (j instanceof Currency) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return new Currency().parse_bytes(j);
|
||||
}
|
||||
};
|
||||
|
||||
Currency.is_valid = function (j) {
|
||||
return Currency.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
Currency.prototype.clone = function() {
|
||||
return this.copyTo(new Currency());
|
||||
};
|
||||
|
||||
// Returns copy.
|
||||
Currency.prototype.copyTo = function (d) {
|
||||
d._value = this._value;
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
Currency.prototype.equals = function (d) {
|
||||
return ('string' !== typeof this._value && isNaN(this._value))
|
||||
|| ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value;
|
||||
};
|
||||
|
||||
// this._value = NaN on error.
|
||||
Currency.prototype.parse_json = function (j) {
|
||||
if (j instanceof Currency) {
|
||||
this._value = j;
|
||||
} else if ('string' === typeof j) {
|
||||
if (j === "" || j === "0" || j === "XRP") {
|
||||
// XRP is never allowed as a Currency object
|
||||
this._value = 0;
|
||||
} else if (j.length === 3) {
|
||||
this._value = j;
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
} else if ('number' === typeof j) {
|
||||
// XXX This is a hack
|
||||
this._value = j;
|
||||
} else if ('string' != typeof j || 3 !== j.length) {
|
||||
this._value = NaN;
|
||||
} else {
|
||||
this._value = j;
|
||||
Currency.prototype.parse_json = function (j, shouldInterpretXrpAsIou) {
|
||||
this._value = NaN;
|
||||
|
||||
switch (typeof j) {
|
||||
case 'string':
|
||||
if (!j || /^(0|XRP)$/.test(j)) {
|
||||
if (shouldInterpretXrpAsIou) {
|
||||
this.parse_hex(Currency.HEX_CURRENCY_BAD);
|
||||
} else {
|
||||
this.parse_hex(Currency.HEX_ZERO);
|
||||
}
|
||||
} else if (/^[a-zA-Z0-9]{3}$/.test(j)) {
|
||||
var currencyCode = j.toUpperCase();
|
||||
var currencyData = utils.arraySet(20, 0);
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
|
||||
this.parse_bytes(currencyData);
|
||||
} else {
|
||||
this.parse_hex(j);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
if (!isNaN(j)) {
|
||||
this.parse_number(j);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (j instanceof Currency) {
|
||||
this._value = j.copyTo({})._value;
|
||||
this._update();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Recalculate internal representation.
|
||||
*
|
||||
* You should never need to call this.
|
||||
*/
|
||||
Currency.prototype._update = function () {
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
if (!bytes) {
|
||||
return "XRP";
|
||||
}
|
||||
|
||||
this._native = false;
|
||||
this._type = -1;
|
||||
this._interest_start = new Date();
|
||||
this._interest_period = NaN;
|
||||
this._iso_code = '';
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || bytes[i]===0);
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
this._iso_code = String.fromCharCode(bytes[12])
|
||||
+ String.fromCharCode(bytes[13])
|
||||
+ String.fromCharCode(bytes[14]);
|
||||
|
||||
if (this._iso_code === "\0\0\0") {
|
||||
this._native = true;
|
||||
this._iso_code = "XRP";
|
||||
}
|
||||
|
||||
this._type = 0;
|
||||
} else if (bytes[0] === 0x01) { // Demurrage currency
|
||||
this._iso_code = String.fromCharCode(bytes[1])
|
||||
+ String.fromCharCode(bytes[2])
|
||||
+ String.fromCharCode(bytes[3]);
|
||||
|
||||
this._type = 1;
|
||||
this._interest_start = (bytes[4] << 24) +
|
||||
(bytes[5] << 16) +
|
||||
(bytes[6] << 8) +
|
||||
(bytes[7] );
|
||||
this._interest_period = Float.fromBytes(bytes.slice(8, 16));
|
||||
}
|
||||
};
|
||||
|
||||
// XXX Probably not needed anymore?
|
||||
/*
|
||||
Currency.prototype.parse_bytes = function (byte_array) {
|
||||
if (Array.isArray(byte_array) && byte_array.length == 20) {
|
||||
if (Array.isArray(byte_array) && byte_array.length === 20) {
|
||||
var result;
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byte_array[0]===0)
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
var currencyCode = String.fromCharCode(byte_array[12]) + String.fromCharCode(byte_array[13]) + String.fromCharCode(byte_array[14]);
|
||||
if (/^[A-Z]{3}$/.test(currencyCode) && currencyCode !== "XRP" ) {
|
||||
var currencyCode = String.fromCharCode(byte_array[12])
|
||||
+ String.fromCharCode(byte_array[13])
|
||||
+ String.fromCharCode(byte_array[14]);
|
||||
if (/^[A-Z0-9]{3}$/.test(currencyCode) && currencyCode !== "XRP" ) {
|
||||
this._value = currencyCode;
|
||||
} else if (currencyCode === "\0\0\0") {
|
||||
this._value = 0;
|
||||
@@ -108,21 +159,70 @@ Currency.prototype.parse_bytes = function (byte_array) {
|
||||
}
|
||||
return this;
|
||||
};
|
||||
*/
|
||||
|
||||
Currency.prototype.is_native = function () {
|
||||
return !isNaN(this._value) && !this._value;
|
||||
return this._native;
|
||||
};
|
||||
|
||||
Currency.prototype.is_valid = function () {
|
||||
return 'string' === typeof this._value || !isNaN(this._value);
|
||||
/**
|
||||
* Whether this currency is an interest-bearing/demurring currency.
|
||||
*/
|
||||
Currency.prototype.has_interest = function () {
|
||||
return this._type === 1 && this._interest_start && !isNaN(this._interest_period);
|
||||
};
|
||||
|
||||
Currency.prototype.get_interest_at = function (referenceDate) {
|
||||
if (!this.has_interest) return 1;
|
||||
|
||||
if (referenceDate instanceof Date) {
|
||||
referenceDate = utils.fromTimestamp(referenceDate.getTime());
|
||||
}
|
||||
|
||||
return Math.pow(Math.E, (referenceDate - this._interest_start) / this._interest_period);
|
||||
};
|
||||
|
||||
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
|
||||
//
|
||||
// We could be doing further checks into the internal format of the
|
||||
// currency data, since there are some values that are invalid.
|
||||
//
|
||||
//Currency.prototype.is_valid = function () {
|
||||
// return this._value instanceof BigInteger && ...;
|
||||
//};
|
||||
|
||||
Currency.prototype.to_json = function () {
|
||||
return this._value ? this._value : "XRP";
|
||||
if (!this.is_valid()) {
|
||||
// XXX This is backwards compatible behavior, but probably not very good.
|
||||
return "XRP";
|
||||
}
|
||||
|
||||
// Any currency with standard properties and a valid code can be abbreviated
|
||||
// in the JSON wire format as the three character code.
|
||||
if (/^[A-Z0-9]{3}$/.test(this._iso_code) && !this.has_interest()) {
|
||||
return this._iso_code;
|
||||
}
|
||||
|
||||
// Fallback to returning the raw currency hex
|
||||
var currencyHex = this.to_hex();
|
||||
|
||||
// XXX This is to maintain backwards compatibility, but it is very, very odd
|
||||
// behavior, so we should deprecate it and get rid of it as soon as
|
||||
// possible.
|
||||
if (currencyHex === Currency.HEX_ONE) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return currencyHex;
|
||||
};
|
||||
|
||||
Currency.prototype.to_human = function () {
|
||||
return this._value ? this._value : "XRP";
|
||||
// to_human() will always print the human-readable currency code if available.
|
||||
if (/^[A-Z0-9]{3}$/.test(this._iso_code)) {
|
||||
return this._iso_code;
|
||||
}
|
||||
|
||||
return this.to_json();
|
||||
};
|
||||
|
||||
exports.Currency = Currency;
|
||||
|
||||
56
src/js/ripple/float.js
Normal file
56
src/js/ripple/float.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* IEEE 754 floating-point.
|
||||
*
|
||||
* Supports single- or double-precision
|
||||
*/
|
||||
var Float = exports.Float = {};
|
||||
|
||||
var allZeros = /^0+$/;
|
||||
var allOnes = /^1+$/;
|
||||
|
||||
Float.fromBytes = function (bytes) {
|
||||
// Render in binary. Hackish.
|
||||
var b = "";
|
||||
for (var i = 0, n = bytes.length; i < n; i++) {
|
||||
var bits = (bytes[i] & 0xff).toString(2);
|
||||
while (bits.length < 8) bits = "0" + bits;
|
||||
b += bits;
|
||||
}
|
||||
|
||||
// Determine configuration. This could have all been precomputed but it is fast enough.
|
||||
var exponentBits = bytes.length === 4 ? 4 : 11;
|
||||
var mantissaBits = (bytes.length * 8) - exponentBits - 1;
|
||||
var bias = Math.pow(2, exponentBits - 1) - 1;
|
||||
var minExponent = 1 - bias - mantissaBits;
|
||||
|
||||
// Break up the binary representation into its pieces for easier processing.
|
||||
var s = b[0];
|
||||
var e = b.substring(1, exponentBits + 1);
|
||||
var m = b.substring(exponentBits + 1);
|
||||
|
||||
var value = 0;
|
||||
var multiplier = (s === "0" ? 1 : -1);
|
||||
|
||||
if (allZeros.test(e)) {
|
||||
// Zero or denormalized
|
||||
if (allZeros.test(m)) {
|
||||
// Value is zero
|
||||
} else {
|
||||
value = parseInt(m, 2) * Math.pow(2, minExponent);
|
||||
}
|
||||
} else if (allOnes.test(e)) {
|
||||
// Infinity or NaN
|
||||
if (allZeros.test(m)) {
|
||||
value = Infinity;
|
||||
} else {
|
||||
value = NaN;
|
||||
}
|
||||
} else {
|
||||
// Normalized
|
||||
var exponent = parseInt(e, 2) - bias;
|
||||
var mantissa = parseInt(m, 2);
|
||||
value = (1 + (mantissa * Math.pow(2, -mantissaBits))) * Math.pow(2, exponent);
|
||||
}
|
||||
|
||||
return value * multiplier;
|
||||
};
|
||||
23
src/js/ripple/hashprefixes.js
Normal file
23
src/js/ripple/hashprefixes.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Prefix for hashing functions.
|
||||
*
|
||||
* These prefixes are inserted before the source material used to
|
||||
* generate various hashes. This is done to put each hash in its own
|
||||
* "space." This way, two different types of objects with the
|
||||
* same binary data will produce different hashes.
|
||||
*
|
||||
* Each prefix is a 4-byte value with the last byte set to zero
|
||||
* and the first three bytes formed from the ASCII equivalent of
|
||||
* some arbitrary string. For example "TXN".
|
||||
*/
|
||||
|
||||
// transaction plus signature to give transaction ID
|
||||
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
// transaction plus metadata
|
||||
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
|
||||
// inner node in tree
|
||||
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
|
||||
// inner transaction to sign
|
||||
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
|
||||
// inner transaction to sign (TESTNET)
|
||||
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
|
||||
@@ -1,15 +1,20 @@
|
||||
exports.Remote = require('./remote').Remote;
|
||||
exports.Amount = require('./amount').Amount;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.Base = require('./base').Base;
|
||||
exports.UInt160 = require('./amount').UInt160;
|
||||
exports.Seed = require('./amount').Seed;
|
||||
exports.Transaction = require('./transaction').Transaction;
|
||||
exports.Meta = require('./meta').Meta;
|
||||
exports.Remote = require('./remote').Remote;
|
||||
exports.Request = require('./request').Request;
|
||||
exports.Amount = require('./amount').Amount;
|
||||
exports.Account = require('./account').Account;
|
||||
exports.Transaction = require('./transaction').Transaction;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.Base = require('./base').Base;
|
||||
exports.UInt160 = require('./uint160').UInt160;
|
||||
exports.UInt256 = require('./uint256').UInt256;
|
||||
exports.Seed = require('./seed').Seed;
|
||||
exports.Meta = require('./meta').Meta;
|
||||
exports.SerializedObject = require('./serializedobject').SerializedObject;
|
||||
exports.RippleError = require('./rippleerror').RippleError;
|
||||
|
||||
exports.binformat = require('./binformat');
|
||||
exports.utils = require('./utils');
|
||||
exports.binformat = require('./binformat');
|
||||
exports.utils = require('./utils');
|
||||
exports.Server = require('./server').Server;
|
||||
|
||||
// Important: We do not guarantee any specific version of SJCL or for any
|
||||
// specific features to be included. The version and configuration may change at
|
||||
@@ -18,8 +23,32 @@ exports.utils = require('./utils');
|
||||
// However, for programs that are tied to a specific version of ripple.js like
|
||||
// the official client, it makes sense to expose the SJCL instance so we don't
|
||||
// have to include it twice.
|
||||
exports.sjcl = require('../../../build/sjcl');
|
||||
exports.sjcl = require('./utils').sjcl;
|
||||
|
||||
exports.config = require('./config');
|
||||
exports.config = require('./config');
|
||||
|
||||
// camelCase to under_scored API conversion
|
||||
function attachUnderscored(c) {
|
||||
var o = exports[c];
|
||||
|
||||
Object.keys(o.prototype).forEach(function(key) {
|
||||
var UPPERCASE = /([A-Z]{1})[a-z]+/g;
|
||||
|
||||
if (!UPPERCASE.test(key)) return;
|
||||
|
||||
var underscored = key.replace(UPPERCASE, function(c) {
|
||||
return '_' + c.toLowerCase();
|
||||
});
|
||||
|
||||
o.prototype[underscored] = o.prototype[key];
|
||||
});
|
||||
};
|
||||
|
||||
[ 'Remote',
|
||||
'Request',
|
||||
'Transaction',
|
||||
'Account',
|
||||
'Server'
|
||||
].forEach(attachUnderscored);
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
var KeyPair = function ()
|
||||
{
|
||||
this._curve = sjcl.ecc.curves['c256'];
|
||||
function KeyPair() {
|
||||
this._curve = sjcl.ecc.curves['c256'];
|
||||
this._secret = null;
|
||||
this._pubkey = null;
|
||||
};
|
||||
|
||||
KeyPair.from_bn_secret = function (j)
|
||||
{
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_bn_secret(j);
|
||||
}
|
||||
KeyPair.from_bn_secret = function (j) {
|
||||
return j instanceof this ? j.clone() : (new this()).parse_bn_secret(j);
|
||||
};
|
||||
|
||||
KeyPair.prototype.parse_bn_secret = function (j)
|
||||
{
|
||||
KeyPair.prototype.parse_bn_secret = function (j) {
|
||||
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], j);
|
||||
return this;
|
||||
};
|
||||
@@ -29,8 +24,7 @@ KeyPair.prototype.parse_bn_secret = function (j)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
KeyPair.prototype._pub = function ()
|
||||
{
|
||||
KeyPair.prototype._pub = function () {
|
||||
var curve = this._curve;
|
||||
|
||||
if (!this._pubkey && this._secret) {
|
||||
@@ -41,26 +35,61 @@ KeyPair.prototype._pub = function ()
|
||||
return this._pubkey;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns public key in compressed format as bit array.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
KeyPair.prototype._pub_bits = function () {
|
||||
var pub = this._pub();
|
||||
|
||||
if (!pub) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var point = pub._point, y_even = point.y.mod(2).equals(0);
|
||||
|
||||
return sjcl.bitArray.concat(
|
||||
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
|
||||
point.x.toBits(this._curve.r.bitLength())
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns public key as hex.
|
||||
*
|
||||
* Key will be returned as a compressed pubkey - 33 bytes converted to hex.
|
||||
*/
|
||||
KeyPair.prototype.to_hex_pub = function ()
|
||||
{
|
||||
var pub = this._pub();
|
||||
if (!pub) return null;
|
||||
KeyPair.prototype.to_hex_pub = function () {
|
||||
var bits = this._pub_bits();
|
||||
|
||||
var point = pub._point, y_even = point.y.mod(2).equals(0);
|
||||
return sjcl.codec.hex.fromBits(sjcl.bitArray.concat(
|
||||
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
|
||||
point.x.toBits(this._curve.r.bitLength())
|
||||
)).toUpperCase();
|
||||
if (!bits) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sjcl.codec.hex.fromBits(bits).toUpperCase();
|
||||
};
|
||||
|
||||
KeyPair.prototype.sign = function (hash)
|
||||
{
|
||||
hash = UInt256.from_json(hash);
|
||||
function SHA256_RIPEMD160(bits) {
|
||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
}
|
||||
|
||||
KeyPair.prototype.get_address = function () {
|
||||
var bits = this._pub_bits();
|
||||
|
||||
if (!bits) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var hash = SHA256_RIPEMD160(bits);
|
||||
|
||||
var address = UInt160.from_bits(hash);
|
||||
address.set_version(Base.VER_ACCOUNT_ID);
|
||||
return address;
|
||||
};
|
||||
|
||||
KeyPair.prototype.sign = function (hash) {
|
||||
var hash = UInt256.from_json(hash);
|
||||
return this._secret.signDER(hash.to_bits(), 0);
|
||||
};
|
||||
|
||||
|
||||
40
src/js/ripple/ledger.js
Normal file
40
src/js/ripple/ledger.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// Ledger
|
||||
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var SHAMap = require('./shamap').SHAMap;
|
||||
var SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
var stypes = require('./serializedtypes');
|
||||
|
||||
function Ledger()
|
||||
{
|
||||
this.ledger_json = {};
|
||||
}
|
||||
|
||||
Ledger.from_json = function (v) {
|
||||
var ledger = new Ledger();
|
||||
ledger.parse_json(v);
|
||||
return ledger;
|
||||
};
|
||||
|
||||
Ledger.prototype.parse_json = function (v) {
|
||||
this.ledger_json = v;
|
||||
};
|
||||
|
||||
Ledger.prototype.calc_tx_hash = function () {
|
||||
var tx_map = new SHAMap();
|
||||
|
||||
this.ledger_json.transactions.forEach(function (tx_json) {
|
||||
var tx = Transaction.from_json(tx_json);
|
||||
var meta = SerializedObject.from_json(tx_json.metaData);
|
||||
|
||||
var data = new SerializedObject();
|
||||
stypes.VariableLength.serialize(data, tx.serialize().to_hex());
|
||||
stypes.VariableLength.serialize(data, meta.to_hex());
|
||||
tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
|
||||
});
|
||||
|
||||
return tx_map.hash();
|
||||
};
|
||||
|
||||
exports.Ledger = Ledger;
|
||||
103
src/js/ripple/log.js
Normal file
103
src/js/ripple/log.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Logging functionality for ripple-lib and any applications built on it.
|
||||
*/
|
||||
var Log = function (namespace) {
|
||||
if (!namespace) {
|
||||
this._namespace = [];
|
||||
} else if (Array.isArray(namespace)) {
|
||||
this._namespace = namespace;
|
||||
} else {
|
||||
this._namespace = [""+namespace];
|
||||
}
|
||||
|
||||
this._prefix = this._namespace.concat(['']).join(': ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a sub-logger.
|
||||
*
|
||||
* You can have a hierarchy of loggers.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var log = require('ripple').log.sub('server');
|
||||
*
|
||||
* log.info('connection successful');
|
||||
* // prints: "server: connection successful"
|
||||
*/
|
||||
Log.prototype.sub = function (namespace) {
|
||||
var subNamespace = this._namespace.slice();
|
||||
if (namespace && "string" === typeof namespace) subNamespace.push(namespace);
|
||||
var subLogger = new Log(subNamespace);
|
||||
subLogger._setParent(this);
|
||||
return subLogger;
|
||||
};
|
||||
|
||||
Log.prototype._setParent = function (parentLogger) {
|
||||
this._parent = parentLogger;
|
||||
};
|
||||
|
||||
Log.makeLevel = function (level) {
|
||||
return function () {
|
||||
arguments[0] = this._prefix + arguments[0];
|
||||
|
||||
Log.engine.logObject.apply(Log, Array.prototype.slice.call(arguments));
|
||||
};
|
||||
};
|
||||
|
||||
Log.prototype.debug = Log.makeLevel(1);
|
||||
Log.prototype.info = Log.makeLevel(2);
|
||||
Log.prototype.warn = Log.makeLevel(3);
|
||||
Log.prototype.error = Log.makeLevel(4);
|
||||
|
||||
/**
|
||||
* Basic logging connector.
|
||||
*
|
||||
* This engine has no formatting and works with the most basic of "console.log"
|
||||
* implementations. This is the logging engine used in Node.js.
|
||||
*/
|
||||
var BasicLogEngine = {
|
||||
logObject: function logObject(msg) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
args = args.map(function(arg) {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
});
|
||||
|
||||
args.unshift(msg);
|
||||
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Null logging connector.
|
||||
*
|
||||
* This engine simply swallows all messages. Used when console.log is not
|
||||
* available.
|
||||
*/
|
||||
var NullLogEngine = {
|
||||
logObject: function () {}
|
||||
};
|
||||
|
||||
Log.engine = NullLogEngine;
|
||||
|
||||
if (console && console.log) Log.engine = BasicLogEngine;
|
||||
|
||||
/**
|
||||
* Provide a root logger as our main export.
|
||||
*
|
||||
* This means you can use the logger easily on the fly:
|
||||
* ripple.log.debug('My object is', myObj);
|
||||
*/
|
||||
module.exports = new Log();
|
||||
|
||||
/**
|
||||
* This is the logger for ripple-lib internally.
|
||||
*/
|
||||
module.exports.internal = module.exports.sub();
|
||||
|
||||
/**
|
||||
* Expose the class as well.
|
||||
*/
|
||||
module.exports.Log = Log;
|
||||
30
src/js/ripple/log.web.js
Normal file
30
src/js/ripple/log.web.js
Normal file
@@ -0,0 +1,30 @@
|
||||
var exports = module.exports = require('./log.js');
|
||||
|
||||
/**
|
||||
* Log engine for browser consoles.
|
||||
*
|
||||
* Browsers tend to have better consoles that support nicely formatted
|
||||
* JavaScript objects. This connector passes objects through to the logging
|
||||
* function without any stringification.
|
||||
*/
|
||||
var InteractiveLogEngine = {
|
||||
logObject: function (msg, obj) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
args = args.map(function(arg) {
|
||||
if (/MSIE/.test(navigator.userAgent)) {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
|
||||
args.unshift(msg);
|
||||
|
||||
console.log.apply(console, args);
|
||||
}
|
||||
};
|
||||
|
||||
if (window.console && window.console.log) {
|
||||
exports.Log.engine = InteractiveLogEngine;
|
||||
}
|
||||
@@ -1,37 +1,56 @@
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Amount = require('./amount').Amount;
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
/**
|
||||
* Meta data processing facility.
|
||||
* Meta data processing facility
|
||||
*/
|
||||
var Meta = function (raw_data)
|
||||
{
|
||||
this.nodes = [];
|
||||
|
||||
for (var i = 0, l = raw_data.AffectedNodes.length; i < l; i++) {
|
||||
var an = raw_data.AffectedNodes[i],
|
||||
result = {};
|
||||
function Meta(raw_data) {
|
||||
var self = this;
|
||||
|
||||
["CreatedNode", "ModifiedNode", "DeletedNode"].forEach(function (x) {
|
||||
if (an[x]) result.diffType = x;
|
||||
});
|
||||
this.nodes = [ ];
|
||||
|
||||
if (!result.diffType) return null;
|
||||
raw_data.AffectedNodes.forEach(function(an) {
|
||||
var result = { };
|
||||
|
||||
an = an[result.diffType];
|
||||
if (result.diffType = self.diffType(an)) {
|
||||
an = an[result.diffType];
|
||||
|
||||
result.entryType = an.LedgerEntryType;
|
||||
result.ledgerIndex = an.LedgerIndex;
|
||||
result.entryType = an.LedgerEntryType;
|
||||
result.ledgerIndex = an.LedgerIndex;
|
||||
result.fields = extend({}, an.PreviousFields, an.NewFields, an.FinalFields);
|
||||
result.fieldsPrev = an.PreviousFields || {};
|
||||
result.fieldsNew = an.NewFields || {};
|
||||
result.fieldsFinal = an.FinalFields || {};
|
||||
|
||||
result.fields = extend({}, an.PreviousFields, an.NewFields, an.FinalFields);
|
||||
result.fieldsPrev = an.PreviousFields || {};
|
||||
result.fieldsNew = an.NewFields || {};
|
||||
result.fieldsFinal = an.FinalFields || {};
|
||||
// getAffectedBooks will set this
|
||||
// result.bookKey = undefined;
|
||||
|
||||
this.nodes.push(result);
|
||||
self.nodes.push(result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Meta.node_types = [
|
||||
'CreatedNode',
|
||||
'ModifiedNode',
|
||||
'DeletedNode'
|
||||
];
|
||||
|
||||
Meta.prototype.diffType = function(an) {
|
||||
var result = false;
|
||||
|
||||
for (var i=0; i<Meta.node_types.length; i++) {
|
||||
var x = Meta.node_types[i];
|
||||
if (an.hasOwnProperty(x)) {
|
||||
result = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -69,29 +88,41 @@ var Meta = function (raw_data)
|
||||
* The second parameter to the callback is the index of the node in the metadata
|
||||
* (first entry is index 0).
|
||||
*/
|
||||
Meta.prototype.each = function (fn)
|
||||
{
|
||||
Meta.prototype.each = function (fn) {
|
||||
for (var i = 0, l = this.nodes.length; i < l; i++) {
|
||||
fn(this.nodes[i], i);
|
||||
}
|
||||
};
|
||||
|
||||
([
|
||||
'forEach',
|
||||
'map',
|
||||
'filter',
|
||||
'every',
|
||||
'reduce'
|
||||
]).forEach(function(fn) {
|
||||
Meta.prototype[fn] = function() {
|
||||
return Array.prototype[fn].apply(this.nodes, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
var amountFieldsAffectingIssuer = [
|
||||
"LowLimit", "HighLimit", "TakerPays", "TakerGets"
|
||||
'LowLimit',
|
||||
'HighLimit',
|
||||
'TakerPays',
|
||||
'TakerGets'
|
||||
];
|
||||
Meta.prototype.getAffectedAccounts = function ()
|
||||
{
|
||||
var accounts = [];
|
||||
|
||||
Meta.prototype.getAffectedAccounts = function () {
|
||||
var accounts = [ ];
|
||||
|
||||
// This code should match the behavior of the C++ method:
|
||||
// TransactionMetaSet::getAffectedAccounts
|
||||
this.each(function (an) {
|
||||
var fields = (an.diffType === "CreatedNode") ? an.fieldsNew : an.fieldsFinal;
|
||||
|
||||
this.nodes.forEach(function (an) {
|
||||
var fields = (an.diffType === 'CreatedNode') ? an.fieldsNew : an.fieldsFinal;
|
||||
for (var i in fields) {
|
||||
var field = fields[i];
|
||||
|
||||
if ("string" === typeof field && UInt160.is_valid(field)) {
|
||||
if (typeof field === 'string' && UInt160.is_valid(field)) {
|
||||
accounts.push(field);
|
||||
} else if (amountFieldsAffectingIssuer.indexOf(i) !== -1) {
|
||||
var amount = Amount.from_json(field);
|
||||
@@ -103,16 +134,13 @@ Meta.prototype.getAffectedAccounts = function ()
|
||||
}
|
||||
});
|
||||
|
||||
accounts = utils.arrayUnique(accounts);
|
||||
|
||||
return accounts;
|
||||
return utils.arrayUnique(accounts);
|
||||
};
|
||||
|
||||
Meta.prototype.getAffectedBooks = function ()
|
||||
{
|
||||
var books = [];
|
||||
Meta.prototype.getAffectedBooks = function () {
|
||||
var books = [ ];
|
||||
|
||||
this.each(function (an) {
|
||||
this.nodes.forEach(function (an) {
|
||||
if (an.entryType !== 'Offer') return;
|
||||
|
||||
var gets = Amount.from_json(an.fields.TakerGets);
|
||||
@@ -124,14 +152,17 @@ Meta.prototype.getAffectedBooks = function ()
|
||||
var paysKey = pays.currency().to_json();
|
||||
if (paysKey !== 'XRP') paysKey += '/' + pays.issuer().to_json();
|
||||
|
||||
var key = getsKey + ":" + paysKey;
|
||||
var key = [ getsKey, paysKey ].join(':');
|
||||
|
||||
// Hell of a lot of work, so we are going to cache this. We can use this
|
||||
// later to good effect in OrderBook.notify to make sure we only process
|
||||
// pertinent offers.
|
||||
an.bookKey = key;
|
||||
|
||||
books.push(key);
|
||||
});
|
||||
|
||||
books = utils.arrayUnique(books);
|
||||
|
||||
return books;
|
||||
return utils.arrayUnique(books);
|
||||
};
|
||||
|
||||
exports.Meta = Meta;
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// Access to the Ripple network via multiple untrusted servers or a single trusted server.
|
||||
//
|
||||
// Overview:
|
||||
// Network configuration.
|
||||
// Can leverage local storage to remember network configuration
|
||||
// Aquires the network
|
||||
// events:
|
||||
// online
|
||||
// offline
|
||||
//
|
||||
|
||||
var remote = require("./remote.js");
|
||||
|
||||
var opts_default = {
|
||||
DEFAULT_VALIDATORS_SITE : "redstem.com",
|
||||
|
||||
ips = {
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// opts : {
|
||||
// cache : undefined || {
|
||||
// get : function () { return cached_value; },
|
||||
// set : function (value) { cached_value = value; },
|
||||
// },
|
||||
//
|
||||
// // Where to get validators.txt if needed.
|
||||
// DEFAULT_VALIDATORS_SITE : _domain_,
|
||||
//
|
||||
// // Validator.txt to use.
|
||||
// validators : _txt_,
|
||||
// }
|
||||
//
|
||||
|
||||
var Network = function (opts) {
|
||||
|
||||
};
|
||||
|
||||
// Set the network configuration.
|
||||
Network.protocol.configure = function () {
|
||||
|
||||
};
|
||||
|
||||
// Target state: connectted
|
||||
Network.protocol.start = function () {
|
||||
|
||||
};
|
||||
|
||||
// Target state: disconnect
|
||||
Network.protocol.stop = function () {
|
||||
|
||||
};
|
||||
|
||||
exports.Network = Network;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -10,14 +10,12 @@
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
var extend = require('extend');
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Currency = require('./currency').Currency;
|
||||
|
||||
var extend = require('extend');
|
||||
|
||||
var OrderBook = function (remote, currency_gets, issuer_gets, currency_pays, issuer_pays) {
|
||||
function OrderBook(remote, currency_gets, issuer_gets, currency_pays, issuer_pays, key) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
@@ -28,44 +26,50 @@ var OrderBook = function (remote, currency_gets, issuer_gets, currency_pays, iss
|
||||
this._currency_pays = currency_pays;
|
||||
this._issuer_pays = issuer_pays;
|
||||
this._subs = 0;
|
||||
this._key = key;
|
||||
|
||||
// We consider ourselves synchronized if we have a current copy of the offers,
|
||||
// we are online and subscribed to updates.
|
||||
this._sync = false;
|
||||
|
||||
// Offers
|
||||
this._offers = [];
|
||||
this._offers = [ ];
|
||||
|
||||
this.on('newListener', function (type, listener) {
|
||||
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
function listenerAdded(type, listener) {
|
||||
if (~OrderBook.subscribe_events.indexOf(type)) {
|
||||
self._subs += 1;
|
||||
if (self._subs == 1 && self._remote._connected) {
|
||||
self._subscribe();
|
||||
}
|
||||
self._subs += 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.on('removeListener', function (type, listener) {
|
||||
this.on('newListener', listenerAdded);
|
||||
|
||||
function listenerRemoved(type, listener) {
|
||||
if (~OrderBook.subscribe_events.indexOf(type)) {
|
||||
self._subs -= 1;
|
||||
self._subs -= 1;
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._sync = false;
|
||||
self._remote.request_unsubscribe()
|
||||
.books([self.to_json()])
|
||||
.request();
|
||||
.books([self.to_json()])
|
||||
.request();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.on('removeListener', listenerRemoved);
|
||||
|
||||
// ST: This *should* call _prepareSubscribe.
|
||||
this._remote.on('prepare_subscribe', function(request) {
|
||||
self._subscribe(request);
|
||||
});
|
||||
|
||||
this._remote.on('connect', function () {
|
||||
if (self._subs) {
|
||||
self._subscribe();
|
||||
}
|
||||
});
|
||||
|
||||
this._remote.on('disconnect', function () {
|
||||
function remoteDisconnected() {
|
||||
self._sync = false;
|
||||
});
|
||||
};
|
||||
|
||||
this._remote.on('disconnect', remoteDisconnected);
|
||||
|
||||
return this;
|
||||
};
|
||||
@@ -82,36 +86,66 @@ OrderBook.subscribe_events = ['transaction', 'model', 'trade'];
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
OrderBook.prototype._subscribe = function () {
|
||||
OrderBook.prototype._subscribe = function (request) {
|
||||
var self = this;
|
||||
self._remote.request_subscribe()
|
||||
.books([self.to_json()], true)
|
||||
.on('error', function () {
|
||||
// XXX What now?
|
||||
})
|
||||
.on('success', function (res) {
|
||||
|
||||
if (self.is_valid() && self._subs) {
|
||||
var request = this._remote.request_subscribe();
|
||||
request.addBook(self.to_json(), true);
|
||||
|
||||
request.once('success', function(res) {
|
||||
self._sync = true;
|
||||
self._offers = res.offers;
|
||||
self.emit('model', self._offers);
|
||||
})
|
||||
.request();
|
||||
});
|
||||
|
||||
request.once('error', function(err) {
|
||||
// XXX What now?
|
||||
});
|
||||
|
||||
request.request();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds this orderbook to a subscription request.
|
||||
|
||||
// ST: Currently this is not working because the server cannot give snapshots
|
||||
// for more than one order book in the same subscribe message.
|
||||
|
||||
OrderBook.prototype._prepareSubscribe = function (request) {
|
||||
var self = this;
|
||||
if (self.is_valid() && self._subs) {
|
||||
request.addBook(self.to_json(), true);
|
||||
request.once('success', function(res) {
|
||||
self._sync = true;
|
||||
self._offers = res.offers;
|
||||
self.emit('model', self._offers);
|
||||
});
|
||||
request.once('error', function(err) {
|
||||
// XXX What now?
|
||||
});
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
OrderBook.prototype.to_json = function () {
|
||||
var json = {
|
||||
'taker_gets': {
|
||||
'currency': this._currency_gets
|
||||
taker_gets: {
|
||||
currency: this._currency_gets
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': this._currency_pays
|
||||
taker_pays: {
|
||||
currency: this._currency_pays
|
||||
}
|
||||
};
|
||||
|
||||
if (this._currency_gets !== 'XRP')
|
||||
if (this._currency_gets !== 'XRP') {
|
||||
json['taker_gets']['issuer'] = this._issuer_gets;
|
||||
}
|
||||
|
||||
if (this._currency_pays !== 'XRP')
|
||||
if (this._currency_pays !== 'XRP') {
|
||||
json['taker_pays']['issuer'] = this._issuer_pays;
|
||||
}
|
||||
|
||||
return json;
|
||||
};
|
||||
@@ -135,9 +169,9 @@ OrderBook.prototype.is_valid = function () {
|
||||
|
||||
OrderBook.prototype.trade = function(type) {
|
||||
var tradeStr = '0'
|
||||
+ (this['_currency_' + type] === 'XRP') ? '' : '/'
|
||||
+ this['_currency_' + type ] + '/'
|
||||
+ this['_issuer_' + type];
|
||||
+ ((this['_currency_' + type] === 'XRP') ? '' : '/'
|
||||
+ this['_currency_' + type ] + '/'
|
||||
+ this['_issuer_' + type]);
|
||||
return Amount.from_json(tradeStr);
|
||||
};
|
||||
|
||||
@@ -147,14 +181,14 @@ OrderBook.prototype.trade = function(type) {
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*/
|
||||
OrderBook.prototype.notifyTx = function (message) {
|
||||
OrderBook.prototype.notify = function (message) {
|
||||
var self = this;
|
||||
var changed = false;
|
||||
var trade_gets = this.trade('gets');
|
||||
var trade_pays = this.trade('pays');
|
||||
|
||||
message.mmeta.each(function (an) {
|
||||
if (an.entryType !== 'Offer') return;
|
||||
function handleTransaction(an) {
|
||||
if (an.entryType !== 'Offer' || an.bookKey !== self._key) return;
|
||||
|
||||
var i, l, offer;
|
||||
|
||||
@@ -169,6 +203,8 @@ OrderBook.prototype.notifyTx = function (message) {
|
||||
if (deletedNode) {
|
||||
self._offers.splice(i, 1);
|
||||
} else {
|
||||
// TODO: This assumes no fields are deleted, which is probably a
|
||||
// safe assumption, but should be checked.
|
||||
extend(offer, an.fieldsFinal);
|
||||
}
|
||||
changed = true;
|
||||
@@ -187,7 +223,7 @@ OrderBook.prototype.notifyTx = function (message) {
|
||||
trade_pays = trade_pays.subtract(an.fieldsFinal.TakerPays);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'CreatedNode':
|
||||
var price = Amount.from_json(an.fields.TakerPays).ratio_human(an.fields.TakerGets);
|
||||
|
||||
@@ -205,18 +241,26 @@ OrderBook.prototype.notifyTx = function (message) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
message.mmeta.each(handleTransaction);
|
||||
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (this._subs) {
|
||||
this.emit('transaction', message);
|
||||
if (changed) this.emit('model', this._offers);
|
||||
if (!trade_gets.is_zero()) this.emit('trade', trade_pays, trade_gets);
|
||||
if (changed) {
|
||||
this.emit('model', this._offers);
|
||||
}
|
||||
if (!trade_gets.is_zero()) {
|
||||
this.emit('trade', trade_pays, trade_gets);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OrderBook.prototype.notifyTx = OrderBook.prototype.notify;
|
||||
|
||||
/**
|
||||
* Get offers model asynchronously.
|
||||
*
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
var extend = require('extend');
|
||||
var util = require('util');
|
||||
var Amount = require('./amount').Amount;
|
||||
var extend = require('extend');
|
||||
|
||||
/**
|
||||
* Represents a persistent path finding request.
|
||||
@@ -12,9 +10,7 @@ var extend = require('extend');
|
||||
* find request is triggered it will supercede the existing one, making it emit
|
||||
* the 'end' and 'superceded' events.
|
||||
*/
|
||||
var PathFind = function (remote, src_account, dst_account,
|
||||
dst_amount, src_currencies)
|
||||
{
|
||||
function PathFind(remote, src_account, dst_account, dst_amount, src_currencies) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.remote = remote;
|
||||
@@ -36,8 +32,7 @@ util.inherits(PathFind, EventEmitter);
|
||||
* so you should only have to call it if the path find was closed or superceded
|
||||
* and you wish to restart it.
|
||||
*/
|
||||
PathFind.prototype.create = function ()
|
||||
{
|
||||
PathFind.prototype.create = function () {
|
||||
var self = this;
|
||||
|
||||
var req = this.remote.request_path_find_create(this.src_account,
|
||||
@@ -48,24 +43,25 @@ PathFind.prototype.create = function ()
|
||||
|
||||
function handleInitialPath(err, msg) {
|
||||
if (err) {
|
||||
// XXX Handle error
|
||||
return;
|
||||
self.emit('error', err);
|
||||
} else {
|
||||
self.notify_update(msg);
|
||||
}
|
||||
self.notify_update(msg);
|
||||
}
|
||||
|
||||
// XXX We should add ourselves to prepare_subscribe or a similar mechanism so
|
||||
// that we can resubscribe after a reconnection.
|
||||
|
||||
req.request();
|
||||
};
|
||||
|
||||
PathFind.prototype.close = function ()
|
||||
{
|
||||
PathFind.prototype.close = function () {
|
||||
this.remote.request_path_find_close().request();
|
||||
this.emit('end');
|
||||
this.emit('close');
|
||||
};
|
||||
|
||||
PathFind.prototype.notify_update = function (message)
|
||||
{
|
||||
PathFind.prototype.notify_update = function (message) {
|
||||
var src_account = message.source_account;
|
||||
var dst_account = message.destination_account;
|
||||
var dst_amount = Amount.from_json(message.destination_amount);
|
||||
@@ -79,8 +75,10 @@ PathFind.prototype.notify_update = function (message)
|
||||
}
|
||||
};
|
||||
|
||||
PathFind.prototype.notify_superceded = function ()
|
||||
{
|
||||
PathFind.prototype.notify_superceded = function () {
|
||||
// XXX If we're set to re-subscribe whenever we connect to a new server, then
|
||||
// we should cancel that behavior here. See PathFind#create.
|
||||
|
||||
this.emit('end');
|
||||
this.emit('superceded');
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
340
src/js/ripple/request.js
Normal file
340
src/js/ripple/request.js
Normal file
@@ -0,0 +1,340 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Currency = require('./currency').Currency;
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var Account = require('./account').Account;
|
||||
var Meta = require('./meta').Meta;
|
||||
var OrderBook = require('./orderbook').OrderBook;
|
||||
var RippleError = require('./rippleerror').RippleError;
|
||||
|
||||
// Request events emitted:
|
||||
// 'success' : Request successful.
|
||||
// 'error' : Request failed.
|
||||
// 'remoteError'
|
||||
// 'remoteUnexpected'
|
||||
// 'remoteDisconnected'
|
||||
function Request(remote, command) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.remote = remote;
|
||||
this.requested = false;
|
||||
this.message = {
|
||||
command : command,
|
||||
id : void(0)
|
||||
};
|
||||
};
|
||||
|
||||
util.inherits(Request, EventEmitter);
|
||||
|
||||
Request.prototype.broadcast = function() {
|
||||
this._broadcast = true;
|
||||
return this.request();
|
||||
};
|
||||
|
||||
// Send the request to a remote.
|
||||
Request.prototype.request = function(remote) {
|
||||
if (this.requested) return;
|
||||
|
||||
this.requested = true;
|
||||
this.on('error', new Function);
|
||||
this.emit('request', remote);
|
||||
|
||||
if (this._broadcast) {
|
||||
this.remote._servers.forEach(function(server) {
|
||||
this.setServer(server);
|
||||
this.remote.request(this);
|
||||
}, this );
|
||||
} else {
|
||||
this.remote.request(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.callback = function(callback, successEvent, errorEvent) {
|
||||
if (callback && typeof callback === 'function') {
|
||||
var self = this;
|
||||
|
||||
function request_success(message) {
|
||||
callback.call(self, null, message);
|
||||
}
|
||||
|
||||
function request_error(error) {
|
||||
if (!(error instanceof RippleError)) {
|
||||
error = new RippleError(error);
|
||||
}
|
||||
callback.call(self, error);
|
||||
}
|
||||
|
||||
this.once(successEvent || 'success', request_success);
|
||||
this.once(errorEvent || 'error' , request_error);
|
||||
this.request();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.timeout = function(duration, callback) {
|
||||
var self = this;
|
||||
|
||||
if (!this.requested) {
|
||||
function requested() {
|
||||
self.timeout(duration, callback);
|
||||
}
|
||||
this.once('request', requested);
|
||||
return;
|
||||
}
|
||||
|
||||
var emit = this.emit;
|
||||
var timed_out = false;
|
||||
|
||||
var timeout = setTimeout(function() {
|
||||
timed_out = true;
|
||||
if (typeof callback === 'function') callback();
|
||||
emit.call(self, 'timeout');
|
||||
}, duration);
|
||||
|
||||
this.emit = function() {
|
||||
if (!timed_out) {
|
||||
clearTimeout(timeout);
|
||||
emit.apply(self, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setServer = function(server) {
|
||||
var selected = null;
|
||||
|
||||
switch (typeof server) {
|
||||
case 'object':
|
||||
selected = server;
|
||||
break;
|
||||
case 'string':
|
||||
var servers = this.remote._servers;
|
||||
for (var i=0, s; s=servers[i]; i++) {
|
||||
if (s._host === server) {
|
||||
selected = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
};
|
||||
|
||||
this.server = selected;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.buildPath = function(build) {
|
||||
|
||||
if (this.remote.local_signing) {
|
||||
throw new Error(
|
||||
'`build_path` is completely ignored when doing local signing as ' +
|
||||
'`Paths` is a component of the signed blob. The `tx_blob` is signed,' +
|
||||
'sealed and delivered, and the txn unmodified after' );
|
||||
}
|
||||
|
||||
if (build) {
|
||||
this.message.build_path = true;
|
||||
} else {
|
||||
// ND: rippled currently intreprets the mere presence of `build_path` as the
|
||||
// value being `truthy`
|
||||
delete this.message.build_path
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.ledgerChoose = function(current) {
|
||||
if (current) {
|
||||
this.message.ledger_index = this.remote._ledger_current_index;
|
||||
} else {
|
||||
this.message.ledger_hash = this.remote._ledger_hash;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
// Set the ledger for a request.
|
||||
// - ledger_entry
|
||||
// - transaction_entry
|
||||
Request.prototype.ledgerHash = function(hash) {
|
||||
this.message.ledger_hash = hash;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Set the ledger_index for a request.
|
||||
// - ledger_entry
|
||||
Request.prototype.ledgerIndex = function(ledger_index) {
|
||||
this.message.ledger_index = ledger_index;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.ledgerSelect = function(ledger_spec) {
|
||||
switch (ledger_spec) {
|
||||
case 'current':
|
||||
case 'closed':
|
||||
case 'verified':
|
||||
this.message.ledger_index = ledger_spec;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (Number(ledger_spec)) {
|
||||
this.message.ledger_index = ledger_spec;
|
||||
} else {
|
||||
this.message.ledger_hash = ledger_spec;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.accountRoot = function(account) {
|
||||
this.message.account_root = UInt160.json_rewrite(account);
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.index = function(hash) {
|
||||
this.message.index = hash;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Provide the information id an offer.
|
||||
// --> account
|
||||
// --> seq : sequence number of transaction creating offer (integer)
|
||||
Request.prototype.offerId = function(account, sequence) {
|
||||
this.message.offer = {
|
||||
account: UInt160.json_rewrite(account),
|
||||
seq: sequence
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
// --> index : ledger entry index.
|
||||
Request.prototype.offerIndex = function(index) {
|
||||
this.message.offer = index;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.secret = function(secret) {
|
||||
if (secret) {
|
||||
this.message.secret = secret;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txHash = function(hash) {
|
||||
this.message.tx_hash = hash;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txJson = function(json) {
|
||||
this.message.tx_json = json;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txBlob = function(json) {
|
||||
this.message.tx_blob = json;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.rippleState = function(account, issuer, currency) {
|
||||
this.message.ripple_state = {
|
||||
currency : currency,
|
||||
accounts : [
|
||||
UInt160.json_rewrite(account),
|
||||
UInt160.json_rewrite(issuer)
|
||||
]
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.accounts = function(accounts, proposed) {
|
||||
if (!Array.isArray(accounts)) {
|
||||
accounts = [ accounts ];
|
||||
}
|
||||
|
||||
// Process accounts parameters
|
||||
var processedAccounts = accounts.map(function(account) {
|
||||
return UInt160.json_rewrite(account);
|
||||
});
|
||||
|
||||
if (proposed) {
|
||||
this.message.accounts_proposed = processedAccounts;
|
||||
} else {
|
||||
this.message.accounts = processedAccounts;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addAccount = function(account, proposed) {
|
||||
var processedAccount = UInt160.json_rewrite(account);
|
||||
|
||||
if (proposed) {
|
||||
this.message.accounts_proposed = (this.message.accounts_proposed || []).concat(processedAccount);
|
||||
} else {
|
||||
this.message.accounts = (this.message.accounts || []).concat(processedAccount);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.rtAccounts =
|
||||
Request.prototype.accountsProposed = function(accounts) {
|
||||
return this.accounts(accounts, true);
|
||||
};
|
||||
|
||||
Request.prototype.addAccountProposed = function(account) {
|
||||
return this.addAccount(account, true);
|
||||
};
|
||||
|
||||
Request.prototype.books = function(books, snapshot) {
|
||||
// Reset list of books (this method overwrites the current list)
|
||||
this.message.books = [ ];
|
||||
|
||||
for (var i = 0, l = books.length; i < l; i++) {
|
||||
var book = books[i];
|
||||
this.addBook(book, snapshot);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addBook = function (book, snapshot) {
|
||||
if (!Array.isArray(this.message.books)) {
|
||||
this.message.books = [];
|
||||
}
|
||||
|
||||
var json = { };
|
||||
|
||||
function processSide(side) {
|
||||
if (!book[side]) {
|
||||
throw new Error('Missing ' + side);
|
||||
}
|
||||
|
||||
var obj = json[side] = {
|
||||
currency: Currency.json_rewrite(book[side].currency)
|
||||
};
|
||||
|
||||
if (obj.currency !== 'XRP') {
|
||||
obj.issuer = UInt160.json_rewrite(book[side].issuer);
|
||||
}
|
||||
}
|
||||
|
||||
[ 'taker_gets', 'taker_pays' ].forEach(processSide);
|
||||
|
||||
if (snapshot) {
|
||||
json.snapshot = true;
|
||||
}
|
||||
|
||||
if (book.both) {
|
||||
json.both = true;
|
||||
}
|
||||
|
||||
this.message.books.push(json);
|
||||
};
|
||||
|
||||
exports.Request = Request;
|
||||
30
src/js/ripple/rippleerror.js
Normal file
30
src/js/ripple/rippleerror.js
Normal file
@@ -0,0 +1,30 @@
|
||||
var util = require('util');
|
||||
var extend = require('extend');
|
||||
|
||||
function RippleError(code, message) {
|
||||
switch (typeof code) {
|
||||
case 'object':
|
||||
extend(this, code);
|
||||
break;
|
||||
case 'string':
|
||||
this.result = code;
|
||||
this.result_message = message;
|
||||
break;
|
||||
}
|
||||
|
||||
this.engine_result = this.result = (this.result || this.engine_result || this.error || 'Error');
|
||||
this.engine_result_message = this.result_message = (this.result_message || this.engine_result_message || this.error_message || 'Error');
|
||||
this.result_message = this.message = (this.result_message);
|
||||
|
||||
var stack;
|
||||
if (!!Error.captureStackTrace)
|
||||
Error.captureStackTrace(this, code || this);
|
||||
else if (stack = new Error().stack)
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
util.inherits(RippleError, Error);
|
||||
|
||||
RippleError.prototype.name = 'RippleError';
|
||||
|
||||
exports.RippleError = RippleError;
|
||||
@@ -2,17 +2,16 @@
|
||||
// Seed support
|
||||
//
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var jsbn = require('./jsbn');
|
||||
var sjcl = utils.sjcl;
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var Base = require('./base').Base,
|
||||
UInt = require('./uint').UInt,
|
||||
UInt256 = require('./uint256').UInt256,
|
||||
KeyPair = require('./keypair').KeyPair;
|
||||
var Base = require('./base').Base;
|
||||
var UInt = require('./uint').UInt;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var KeyPair = require('./keypair').KeyPair;
|
||||
|
||||
var Seed = extend(function () {
|
||||
// Internal form: NaN or BigInteger
|
||||
@@ -27,7 +26,7 @@ Seed.prototype.constructor = Seed;
|
||||
// value = NaN on error.
|
||||
// One day this will support rfc1751 too.
|
||||
Seed.prototype.parse_json = function (j) {
|
||||
if ('string' === typeof j) {
|
||||
if (typeof j === 'string') {
|
||||
if (!j.length) {
|
||||
this._value = NaN;
|
||||
// XXX Should actually always try and continue if it failed.
|
||||
@@ -47,7 +46,7 @@ Seed.prototype.parse_json = function (j) {
|
||||
};
|
||||
|
||||
Seed.prototype.parse_passphrase = function (j) {
|
||||
if ("string" !== typeof j) {
|
||||
if (typeof j !== 'string') {
|
||||
throw new Error("Passphrase must be a string");
|
||||
}
|
||||
|
||||
@@ -60,8 +59,9 @@ Seed.prototype.parse_passphrase = function (j) {
|
||||
};
|
||||
|
||||
Seed.prototype.to_json = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
if (!(this._value instanceof BigInteger)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
|
||||
|
||||
@@ -70,18 +70,18 @@ Seed.prototype.to_json = function () {
|
||||
|
||||
function append_int(a, i) {
|
||||
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff);
|
||||
}
|
||||
};
|
||||
|
||||
function firstHalfOfSHA512(bytes) {
|
||||
return sjcl.bitArray.bitSlice(
|
||||
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
|
||||
0, 256
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function SHA256_RIPEMD160(bits) {
|
||||
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
}
|
||||
};
|
||||
|
||||
Seed.prototype.get_key = function (account_id) {
|
||||
if (!this.is_valid()) {
|
||||
@@ -89,11 +89,10 @@ Seed.prototype.get_key = function (account_id) {
|
||||
}
|
||||
// XXX Should loop over keys until we find the right one
|
||||
|
||||
var private_gen, public_gen;
|
||||
var curve = this._curve;
|
||||
var seq = 0, i = 0;
|
||||
|
||||
var seq = 0;
|
||||
|
||||
var private_gen, public_gen, i = 0;
|
||||
do {
|
||||
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.to_bytes(), i)));
|
||||
i++;
|
||||
@@ -103,6 +102,7 @@ Seed.prototype.get_key = function (account_id) {
|
||||
|
||||
var sec;
|
||||
i = 0;
|
||||
|
||||
do {
|
||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
||||
i++;
|
||||
|
||||
@@ -1,54 +1,115 @@
|
||||
var binformat = require('./binformat'),
|
||||
sjcl = require('../../../build/sjcl'),
|
||||
extend = require('extend'),
|
||||
stypes = require('./serializedtypes');
|
||||
var binformat = require('./binformat');
|
||||
var extend = require('extend');
|
||||
var stypes = require('./serializedtypes');
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var assert = require('assert');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var SerializedObject = function (buf) {
|
||||
if (Array.isArray(buf)) {
|
||||
var TRANSACTION_TYPES = { };
|
||||
|
||||
Object.keys(binformat.tx).forEach(function(key) {
|
||||
TRANSACTION_TYPES[binformat.tx[key][0]] = key;
|
||||
});
|
||||
|
||||
var LEDGER_ENTRY_TYPES = {};
|
||||
|
||||
Object.keys(binformat.ledger).forEach(function(key) {
|
||||
LEDGER_ENTRY_TYPES[binformat.ledger[key][0]] = key;
|
||||
});
|
||||
|
||||
var TRANSACTION_RESULTS = {};
|
||||
|
||||
Object.keys(binformat.ter).forEach(function(key) {
|
||||
TRANSACTION_RESULTS[binformat.ter[key]] = key;
|
||||
});
|
||||
|
||||
function SerializedObject(buf) {
|
||||
if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf)) ) {
|
||||
this.buffer = buf;
|
||||
} else if ("string" === typeof buf) {
|
||||
} else if (typeof buf === 'string') {
|
||||
this.buffer = sjcl.codec.bytes.fromBits(sjcl.codec.hex.toBits(buf));
|
||||
} else if (!buf) {
|
||||
this.buffer = [];
|
||||
} else {
|
||||
throw new Error("Invalid buffer passed.");
|
||||
throw new Error('Invalid buffer passed.');
|
||||
}
|
||||
this.pointer = 0;
|
||||
};
|
||||
|
||||
SerializedObject.from_json = function (obj) {
|
||||
var typedef;
|
||||
var so = new SerializedObject();
|
||||
|
||||
// Create a copy of the object so we don't modify it
|
||||
obj = extend({}, obj);
|
||||
var obj = extend({}, obj);
|
||||
var so = new SerializedObject;
|
||||
var typedef;
|
||||
|
||||
if ("number" === typeof obj.TransactionType) {
|
||||
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
|
||||
if (!obj.TransactionType) {
|
||||
throw new Error("Transaction type ID is invalid.");
|
||||
throw new Error('Transaction type ID is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if ("string" === typeof obj.TransactionType) {
|
||||
typedef = binformat.tx[obj.TransactionType].slice();
|
||||
typedef = binformat.tx[obj.TransactionType];
|
||||
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('Transaction type is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.TransactionType = typedef.shift();
|
||||
} else if ("undefined" !== typeof obj.LedgerEntryType) {
|
||||
// XXX: TODO
|
||||
throw new Error("Ledger entry binary format not yet implemented.");
|
||||
} else throw new Error("Object to be serialized must contain either " +
|
||||
"TransactionType or LedgerEntryType.");
|
||||
throw new Error('Ledger entry binary format not yet implemented.');
|
||||
} else if ("object" === typeof obj.AffectedNodes) {
|
||||
typedef = binformat.metadata;
|
||||
} else {
|
||||
throw new Error('Object to be serialized must contain either' +
|
||||
' TransactionType, LedgerEntryType or AffectedNodes.');
|
||||
}
|
||||
|
||||
// ND: This from_*json* seems a reasonable place to put validation of `json`
|
||||
SerializedObject.check_no_missing_fields(typedef, obj);
|
||||
so.serialize(typedef, obj);
|
||||
|
||||
return so;
|
||||
};
|
||||
|
||||
SerializedObject.check_no_missing_fields = function (typedef, obj) {
|
||||
var missing_fields = [];
|
||||
|
||||
for (var i = typedef.length - 1; i >= 0; i--) {
|
||||
var spec = typedef[i];
|
||||
var field = spec[0]
|
||||
var requirement = spec[1];
|
||||
|
||||
if (binformat.REQUIRED === requirement && obj[field] == null) {
|
||||
missing_fields.push(field);
|
||||
};
|
||||
};
|
||||
|
||||
if (missing_fields.length > 0) {
|
||||
var object_name;
|
||||
if (obj.TransactionType != null) {
|
||||
object_name = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
} else {
|
||||
object_name = "TransactionMetaData";
|
||||
} /*else {
|
||||
TODO: LedgerEntryType ...
|
||||
}*/
|
||||
throw new Error(object_name + " is missing fields: " +
|
||||
JSON.stringify(missing_fields));
|
||||
};
|
||||
}
|
||||
|
||||
SerializedObject.prototype.append = function (bytes) {
|
||||
if (bytes instanceof SerializedObject) {
|
||||
bytes = bytes.buffer;
|
||||
}
|
||||
this.buffer = this.buffer.concat(bytes);
|
||||
this.pointer += bytes.length;
|
||||
};
|
||||
@@ -57,21 +118,30 @@ SerializedObject.prototype.resetPointer = function () {
|
||||
this.pointer = 0;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.read = function (numberOfBytes) {
|
||||
var start = this.pointer;
|
||||
var end = start+numberOfBytes;
|
||||
if (end > this.buffer.length) {
|
||||
throw new Error("There aren't that many bytes left to read.");
|
||||
} else {
|
||||
var result = this.buffer.slice(start,end);
|
||||
this.pointer = end;
|
||||
return result;
|
||||
function readOrPeek(advance) {
|
||||
return function(bytes) {
|
||||
var start = this.pointer;
|
||||
var end = start + bytes;
|
||||
|
||||
if (end > this.buffer.length) {
|
||||
throw new Error('Buffer length exceeded');
|
||||
}
|
||||
|
||||
var result = this.buffer.slice(start, end);
|
||||
|
||||
if (advance) {
|
||||
this.pointer = end;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
SerializedObject.prototype.read = readOrPeek(true);
|
||||
|
||||
SerializedObject.prototype.to_bits = function ()
|
||||
{
|
||||
SerializedObject.prototype.peek = readOrPeek(false);
|
||||
|
||||
SerializedObject.prototype.to_bits = function () {
|
||||
return sjcl.codec.bytes.toBits(this.buffer);
|
||||
};
|
||||
|
||||
@@ -79,91 +149,151 @@ SerializedObject.prototype.to_hex = function () {
|
||||
return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
|
||||
};
|
||||
|
||||
SerializedObject.prototype.serialize = function (typedef, obj)
|
||||
{
|
||||
// Ensure canonical order
|
||||
typedef = SerializedObject._sort_typedef(typedef.slice());
|
||||
SerializedObject.prototype.to_json = function() {
|
||||
var old_pointer = this.pointer;
|
||||
this.resetPointer();
|
||||
var output = { };
|
||||
|
||||
// Serialize fields
|
||||
for (var i = 0, l = typedef.length; i < l; i++) {
|
||||
var spec = typedef[i];
|
||||
this.serialize_field(spec, obj);
|
||||
while (this.pointer < this.buffer.length) {
|
||||
var key_and_value = stypes.parse(this);
|
||||
var key = key_and_value[0];
|
||||
var value = key_and_value[1];
|
||||
output[key] = SerializedObject.jsonify_structure(value, key);
|
||||
}
|
||||
|
||||
this.pointer = old_pointer;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
SerializedObject.jsonify_structure = function(structure, field_name) {
|
||||
var output;
|
||||
|
||||
switch (typeof structure) {
|
||||
case 'number':
|
||||
switch (field_name) {
|
||||
case 'LedgerEntryType':
|
||||
output = LEDGER_ENTRY_TYPES[structure];
|
||||
break;
|
||||
case 'TransactionResult':
|
||||
output = TRANSACTION_RESULTS[structure];
|
||||
break;
|
||||
case 'TransactionType':
|
||||
output = TRANSACTION_TYPES[structure];
|
||||
break;
|
||||
default:
|
||||
output = structure;
|
||||
}
|
||||
break;
|
||||
case 'object':
|
||||
if (!structure) break; //null
|
||||
if (typeof structure.to_json === 'function') {
|
||||
output = structure.to_json();
|
||||
} else if (structure instanceof BigInteger) {
|
||||
output = structure.toString(16).toUpperCase();
|
||||
} else {
|
||||
output = new structure.constructor; //new Array or Object
|
||||
var keys = Object.keys(structure);
|
||||
for (var i=0, l=keys.length; i<l; i++) {
|
||||
var key = keys[i];
|
||||
output[key] = SerializedObject.jsonify_structure(structure[key], key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
output = structure;
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.signing_hash = function (prefix)
|
||||
{
|
||||
SerializedObject.prototype.serialize = function (typedef, obj) {
|
||||
// Serialize object without end marker
|
||||
stypes.Object.serialize(this, obj, true);
|
||||
|
||||
// ST: Old serialization
|
||||
/*
|
||||
// Ensure canonical order
|
||||
typedef = SerializedObject.sort_typedef(typedef);
|
||||
|
||||
// Serialize fields
|
||||
for (var i=0, l=typedef.length; i<l; i++) {
|
||||
this.serialize_field(typedef[i], obj);
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
SerializedObject.prototype.hash = function (prefix) {
|
||||
var sign_buffer = new SerializedObject();
|
||||
stypes.Int32.serialize(sign_buffer, prefix);
|
||||
sign_buffer.append(this.buffer);
|
||||
return sign_buffer.hash_sha512_half();
|
||||
};
|
||||
|
||||
SerializedObject.prototype.hash_sha512_half = function ()
|
||||
{
|
||||
var bits = sjcl.codec.bytes.toBits(this.buffer),
|
||||
hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256);
|
||||
// DEPRECATED
|
||||
SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash;
|
||||
|
||||
SerializedObject.prototype.hash_sha512_half = function () {
|
||||
var bits = sjcl.codec.bytes.toBits(this.buffer);
|
||||
var hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256);
|
||||
return UInt256.from_hex(sjcl.codec.hex.fromBits(hash));
|
||||
};
|
||||
|
||||
SerializedObject.prototype.serialize_field = function (spec, obj)
|
||||
{
|
||||
spec = spec.slice();
|
||||
|
||||
var name = spec.shift(),
|
||||
presence = spec.shift(),
|
||||
field_id = spec.shift(),
|
||||
Type = spec.shift();
|
||||
|
||||
if ("undefined" !== typeof obj[name]) {
|
||||
//console.log(name, Type.id, field_id);
|
||||
this.append(SerializedObject.get_field_header(Type.id, field_id));
|
||||
SerializedObject.prototype.serialize_field = function (spec, obj) {
|
||||
var name = spec[0];
|
||||
var presence = spec[1];
|
||||
var field_id = spec[2];
|
||||
var Type = stypes[spec[3]];
|
||||
|
||||
if (typeof obj[name] !== 'undefined') {
|
||||
// ST: Old serialization code
|
||||
//this.append(SerializedObject.get_field_header(Type.id, field_id));
|
||||
try {
|
||||
Type.serialize(this, obj[name]);
|
||||
// ST: Old serialization code
|
||||
//Type.serialize(this, obj[name]);
|
||||
stypes.serialize(this, name, obj[name]);
|
||||
} catch (e) {
|
||||
// Add field name to message and rethrow
|
||||
e.message = "Error serializing '"+name+"': "+e.message;
|
||||
e.message = 'Error serializing "' + name + '": ' + e.message;
|
||||
throw e;
|
||||
}
|
||||
} else if (presence === binformat.REQUIRED) {
|
||||
throw new Error('Missing required field '+name);
|
||||
throw new Error('Missing required field ' + name);
|
||||
}
|
||||
};
|
||||
|
||||
SerializedObject.get_field_header = function (type_id, field_id)
|
||||
{
|
||||
var buffer = [0];
|
||||
if (type_id > 0xf) buffer.push(type_id & 0xff);
|
||||
else buffer[0] += (type_id & 0xf) << 4;
|
||||
SerializedObject.get_field_header = function (type_id, field_id) {
|
||||
var buffer = [ 0 ];
|
||||
|
||||
if (field_id > 0xf) buffer.push(field_id & 0xff);
|
||||
else buffer[0] += field_id & 0xf;
|
||||
if (type_id > 0xF) {
|
||||
buffer.push(type_id & 0xFF);
|
||||
} else {
|
||||
buffer[0] += (type_id & 0xF) << 4;
|
||||
}
|
||||
|
||||
if (field_id > 0xF) {
|
||||
buffer.push(field_id & 0xFF);
|
||||
} else {
|
||||
buffer[0] += field_id & 0xF;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
function sort_field_compare(a, b) {
|
||||
// Sort by type id first, then by field id
|
||||
return a[3].id !== b[3].id ?
|
||||
a[3].id - b[3].id :
|
||||
a[2] - b[2];
|
||||
};
|
||||
SerializedObject._sort_typedef = function (typedef) {
|
||||
SerializedObject.sort_typedef = function (typedef) {
|
||||
assert(Array.isArray(typedef));
|
||||
|
||||
function sort_field_compare(a, b) {
|
||||
// Sort by type id first, then by field id
|
||||
return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2];
|
||||
};
|
||||
|
||||
return typedef.sort(sort_field_compare);
|
||||
};
|
||||
|
||||
SerializedObject.lookup_type_tx = function (id) {
|
||||
for (var i in binformat.tx) {
|
||||
if (!binformat.tx.hasOwnProperty(i)) continue;
|
||||
|
||||
if (binformat.tx[i][0] === id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
assert(typeof id === 'number');
|
||||
return TRANSACTION_TYPES[id];
|
||||
};
|
||||
|
||||
exports.SerializedObject = SerializedObject;
|
||||
|
||||
@@ -6,29 +6,52 @@
|
||||
* SerializedObject.parse() or SerializedObject.serialize().
|
||||
*/
|
||||
|
||||
var extend = require('extend'),
|
||||
utils = require('./utils'),
|
||||
sjcl = require('../../../build/sjcl');
|
||||
var assert = require('assert');
|
||||
var extend = require('extend');
|
||||
var binformat = require('./binformat');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
|
||||
var amount = require('./amount'),
|
||||
UInt128 = require('./uint128').UInt128,
|
||||
UInt160 = require('./uint160').UInt160,
|
||||
UInt256 = require('./uint256').UInt256,
|
||||
Amount = amount.Amount,
|
||||
Currency= amount.Currency;
|
||||
var UInt128 = require('./uint128').UInt128;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
var amount = require('./amount');
|
||||
var Amount = amount.Amount;
|
||||
var Currency = amount.Currency;
|
||||
|
||||
// Shortcuts
|
||||
var hex = sjcl.codec.hex,
|
||||
bytes = sjcl.codec.bytes;
|
||||
var hex = sjcl.codec.hex;
|
||||
var bytes = sjcl.codec.bytes;
|
||||
|
||||
var jsbn = require('./jsbn');
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
|
||||
var SerializedType = function (methods) {
|
||||
extend(this, methods);
|
||||
};
|
||||
|
||||
function isNumber(val) {
|
||||
return typeof val === 'number' && isFinite(val);
|
||||
};
|
||||
|
||||
function isString(val) {
|
||||
return typeof val === 'string';
|
||||
};
|
||||
|
||||
function isHexInt64String(val) {
|
||||
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
|
||||
};
|
||||
|
||||
function isCurrencyString(val) {
|
||||
return isString(val) && /^[A-Z0-9]{3}$/.test(val);
|
||||
};
|
||||
|
||||
function isBigInteger(val) {
|
||||
return val instanceof BigInteger;
|
||||
};
|
||||
|
||||
function serialize_hex(so, hexData, noLength) {
|
||||
var byteData = bytes.fromBits(hex.toBits(hexData));
|
||||
if (!noLength) {
|
||||
@@ -37,84 +60,99 @@ function serialize_hex(so, hexData, noLength) {
|
||||
so.append(byteData);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* parses bytes as hex
|
||||
*/
|
||||
function convert_bytes_to_hex (byte_array) {
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array));
|
||||
}
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase();
|
||||
};
|
||||
|
||||
SerializedType.serialize_varint = function (so, val) {
|
||||
if (val < 0) {
|
||||
throw new Error("Variable integers are unsigned.");
|
||||
throw new Error('Variable integers are unsigned.');
|
||||
}
|
||||
|
||||
if (val <= 192) {
|
||||
so.append([val]);
|
||||
} else if (val <= 12,480) {
|
||||
} else if (val <= 12480) {
|
||||
val -= 193;
|
||||
so.append([193 + (val >>> 8), val & 0xff]);
|
||||
} else if (val <= 918744) {
|
||||
val -= 12481;
|
||||
so.append([
|
||||
241 + (val >>> 16),
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
241 + (val >>> 16),
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
]);
|
||||
} else throw new Error("Variable integer overflow.");
|
||||
} else {
|
||||
throw new Error('Variable integer overflow.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
SerializedType.parse_varint = function (so) {
|
||||
SerializedType.prototype.parse_varint = function (so) {
|
||||
var b1 = so.read(1)[0], b2, b3;
|
||||
var result;
|
||||
|
||||
if (b1 > 254) {
|
||||
throw new Error('Invalid varint length indicator');
|
||||
}
|
||||
|
||||
if (b1 <= 192) {
|
||||
return b1;
|
||||
result = b1;
|
||||
} else if (b1 <= 240) {
|
||||
b2 = so.read(1)[0];
|
||||
return 193 + (b1-193)*256 + b2;
|
||||
result = 193 + (b1 - 193) * 256 + b2;
|
||||
} else if (b1 <= 254) {
|
||||
b2 = so.read(1)[0];
|
||||
b3 = so.read(1)[0];
|
||||
return 12481 + (b1-241)*65536 + b2*256 + b3
|
||||
}
|
||||
else {
|
||||
throw new Error("Invalid varint length indicator");
|
||||
result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// In the following, we assume that the inputs are in the proper range. Is this correct?
|
||||
|
||||
// Helper functions for 1-, 2-, and 4-byte integers.
|
||||
|
||||
/**
|
||||
* Convert an integer value into an array of bytes.
|
||||
*
|
||||
* The result is appended to the serialized object ("so").
|
||||
* The result is appended to the serialized object ('so').
|
||||
*/
|
||||
function append_byte_array(so, val, bytes) {
|
||||
if ("number" !== typeof val) {
|
||||
throw new Error("Integer is not a number");
|
||||
if (!isNumber(val)) {
|
||||
throw new Error('Value is not a number');
|
||||
}
|
||||
if (val < 0 || val >= (Math.pow(256, bytes))) {
|
||||
throw new Error("Integer out of bounds");
|
||||
}
|
||||
var newBytes = [];
|
||||
for (var i=0; i<bytes; i++) {
|
||||
newBytes.unshift(val >>> (i*8) & 0xff);
|
||||
}
|
||||
so.append(newBytes);
|
||||
}
|
||||
|
||||
// Convert a certain number of bytes from the serialized object ("so") into an integer.
|
||||
if (val < 0 || val >= Math.pow(256, bytes)) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
|
||||
var newBytes = [ ];
|
||||
|
||||
for (var i=0; i<bytes; i++) {
|
||||
newBytes.unshift(val >>> (i * 8) & 0xff);
|
||||
}
|
||||
|
||||
so.append(newBytes);
|
||||
};
|
||||
|
||||
// Convert a certain number of bytes from the serialized object ('so') into an integer.
|
||||
function readAndSum(so, bytes) {
|
||||
var sum = 0;
|
||||
for (var i = 0; i<bytes; i++) {
|
||||
sum += (so.read(1)[0] << (8*(bytes-1-i)) );
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
if (bytes > 4) {
|
||||
throw new Error("This function only supports up to four bytes.");
|
||||
}
|
||||
|
||||
for (var i=0; i<bytes; i++) {
|
||||
var byte = so.read(1)[0];
|
||||
sum += (byte << (8 * (bytes - i - 1)));
|
||||
}
|
||||
|
||||
// Convert to unsigned integer
|
||||
return sum >>> 0;
|
||||
};
|
||||
|
||||
var STInt8 = exports.Int8 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
@@ -125,83 +163,80 @@ var STInt8 = exports.Int8 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STInt8.id = 16;
|
||||
|
||||
var STInt16 = exports.Int16 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 2);
|
||||
/*so.append([
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
]);*/
|
||||
},
|
||||
parse: function (so) {
|
||||
return readAndSum(so, 2);
|
||||
}
|
||||
});
|
||||
|
||||
STInt16.id = 1;
|
||||
|
||||
var STInt32 = exports.Int32 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
append_byte_array(so, val, 4)
|
||||
/*so.append([
|
||||
val >>> 24 & 0xff,
|
||||
val >>> 16 & 0xff,
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
]);*/
|
||||
append_byte_array(so, val, 4);
|
||||
},
|
||||
parse: function (so) {
|
||||
return readAndSum(so, 4);
|
||||
}
|
||||
});
|
||||
|
||||
STInt32.id = 2;
|
||||
|
||||
var STInt64 = exports.Int64 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var bigNumObject;
|
||||
if ("number" === typeof val) {
|
||||
|
||||
if (isNumber(val)) {
|
||||
val = Math.floor(val);
|
||||
if (val < 0) {
|
||||
throw new Error("Negative value for unsigned Int64 is invalid.");
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = new BigInteger(""+val, 10);
|
||||
} else if ("string" === typeof val) {
|
||||
if (!/^[0-9A-F]{0,16}$/i.test(val)) {
|
||||
throw new Error("Not a valid hex Int64.");
|
||||
bigNumObject = new BigInteger(String(val), 10);
|
||||
} else if (isString(val)) {
|
||||
if (!isHexInt64String(val)) {
|
||||
throw new Error('Not a valid hex Int64.');
|
||||
}
|
||||
bigNumObject = new BigInteger(val, 16);
|
||||
} else if (val instanceof BigInteger) {
|
||||
} else if (isBigInteger(val)) {
|
||||
if (val.compareTo(BigInteger.ZERO) < 0) {
|
||||
throw new Error("Negative value for unsigned Int64 is invalid.");
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = val;
|
||||
} else {
|
||||
throw new Error("Invalid type for Int64");
|
||||
throw new Error('Invalid type for Int64');
|
||||
}
|
||||
|
||||
var hex = bigNumObject.toString(16);
|
||||
|
||||
if (hex.length > 16) {
|
||||
throw new Error("Int64 is too large");
|
||||
throw new Error('Int64 is too large');
|
||||
}
|
||||
|
||||
while (hex.length < 16) {
|
||||
hex = "0" + hex;
|
||||
hex = '0' + hex;
|
||||
}
|
||||
return serialize_hex(so, hex, true); //noLength = true
|
||||
|
||||
serialize_hex(so, hex, true); //noLength = true
|
||||
},
|
||||
parse: function (so) {
|
||||
var hi = readAndSum(so, 4);
|
||||
var lo = readAndSum(so, 4);
|
||||
|
||||
var result = new BigInteger(hi);
|
||||
result.shiftLeft(32);
|
||||
result.add(lo);
|
||||
var result = new BigInteger(so.read(8), 256);
|
||||
assert(result instanceof BigInteger);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
STInt64.id = 3;
|
||||
|
||||
var STHash128 = exports.Hash128 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt128.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error("Invalid Hash128");
|
||||
throw new Error('Invalid Hash128');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
@@ -210,11 +245,13 @@ var STHash128 = exports.Hash128 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash128.id = 4;
|
||||
|
||||
var STHash256 = exports.Hash256 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt256.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error("Invalid Hash256");
|
||||
throw new Error('Invalid Hash256');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
@@ -223,11 +260,13 @@ var STHash256 = exports.Hash256 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash256.id = 5;
|
||||
|
||||
var STHash160 = exports.Hash160 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var hash = UInt160.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error("Invalid Hash160");
|
||||
throw new Error('Invalid Hash160');
|
||||
}
|
||||
serialize_hex(so, hash.to_hex(), true); //noLength = true
|
||||
},
|
||||
@@ -236,34 +275,28 @@ var STHash160 = exports.Hash160 = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STHash160.id = 17;
|
||||
|
||||
// Internal
|
||||
var STCurrency = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var currency = val.to_json();
|
||||
if ("XRP" === currency) {
|
||||
serialize_hex(so, UInt160.HEX_ZERO, true);
|
||||
} else if ("string" === typeof currency && currency.length === 3) {
|
||||
var currencyCode = currency.toUpperCase(),
|
||||
currencyData = utils.arraySet(20, 0);
|
||||
serialize: function (so, val, xrp_as_ascii) {
|
||||
var currencyData = val.to_bytes();
|
||||
|
||||
if (!/^[A-Z]{3}$/.test(currencyCode) || currencyCode === "XRP" ) {
|
||||
throw new Error('Invalid currency code');
|
||||
}
|
||||
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
|
||||
|
||||
so.append(currencyData);
|
||||
} else {
|
||||
if (!currencyData) {
|
||||
throw new Error('Tried to serialize invalid/unimplemented currency type.');
|
||||
}
|
||||
|
||||
so.append(currencyData);
|
||||
},
|
||||
parse: function (so) {
|
||||
var currency = Currency.from_bytes(so.read(20));
|
||||
if (!currency.is_valid()) {
|
||||
throw new Error("Invalid currency");
|
||||
}
|
||||
var bytes = so.read(20);
|
||||
var currency = Currency.from_bytes(bytes);
|
||||
// XXX Disabled check. Theoretically, the Currency class should support any
|
||||
// UInt160 value and consider it valid. But it doesn't, so for the
|
||||
// deserialization to be usable, we need to allow invalid results for now.
|
||||
//if (!currency.is_valid()) {
|
||||
// throw new Error('Invalid currency: '+convert_bytes_to_hex(bytes));
|
||||
//}
|
||||
return currency;
|
||||
}
|
||||
});
|
||||
@@ -272,11 +305,12 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var amount = Amount.from_json(val);
|
||||
if (!amount.is_valid()) {
|
||||
throw new Error("Not a valid Amount object.");
|
||||
throw new Error('Not a valid Amount object.');
|
||||
}
|
||||
|
||||
// Amount (64-bit integer)
|
||||
var valueBytes = utils.arraySet(8, 0);
|
||||
|
||||
if (amount.is_native()) {
|
||||
var valueHex = amount._value.toString(16);
|
||||
|
||||
@@ -284,8 +318,9 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
if (valueHex.length > 16) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
|
||||
while (valueHex.length < 16) {
|
||||
valueHex = "0" + valueHex;
|
||||
valueHex = '0' + valueHex;
|
||||
}
|
||||
|
||||
valueBytes = bytes.fromBits(hex.toBits(valueHex));
|
||||
@@ -303,10 +338,8 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
if (!amount.is_zero()) {
|
||||
// Second bit: non-negative?
|
||||
if (!amount.is_negative()) hi |= 1 << 30;
|
||||
|
||||
// Next eight bits: offset/exponent
|
||||
hi |= ((97 + amount._offset) & 0xff) << 22;
|
||||
|
||||
// Remaining 52 bits: mantissa
|
||||
hi |= amount._value.shiftRight(32).intValue() & 0x3fffff;
|
||||
lo = amount._value.intValue() & 0xffffffff;
|
||||
@@ -320,7 +353,7 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
if (!amount.is_native()) {
|
||||
// Currency (160-bit hash)
|
||||
var currency = amount.currency();
|
||||
STCurrency.serialize(so, currency);
|
||||
STCurrency.serialize(so, currency, true);
|
||||
|
||||
// Issuer (160-bit hash)
|
||||
so.append(amount.issuer().to_bytes());
|
||||
@@ -330,22 +363,24 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
var amount = new Amount();
|
||||
var value_bytes = so.read(8);
|
||||
var is_zero = !(value_bytes[0] & 0x7f);
|
||||
|
||||
for (var i=1; i<8; i++) {
|
||||
is_zero = is_zero && !value_bytes[i];
|
||||
}
|
||||
|
||||
if (value_bytes[0] & 0x80) {
|
||||
//non-native
|
||||
var currency = STCurrency.parse(so);
|
||||
var issuer_bytes = so.read(20);
|
||||
var issuer = UInt160.from_bytes(issuer_bytes);
|
||||
|
||||
issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
|
||||
var mantissa_bytes = value_bytes.slice(1);
|
||||
mantissa_bytes[0] &= 0x3f;
|
||||
var value = new BigInteger(mantissa_bytes, 256);
|
||||
|
||||
if (value.equals(BigInteger.ZERO) && !is_zero ) {
|
||||
throw new Error("Invalid zero representation");
|
||||
throw new Error('Invalid zero representation');
|
||||
}
|
||||
|
||||
amount._value = value;
|
||||
@@ -353,7 +388,6 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
amount._currency = currency;
|
||||
amount._issuer = issuer;
|
||||
amount._is_native = false;
|
||||
|
||||
} else {
|
||||
//native
|
||||
var integer_bytes = value_bytes.slice();
|
||||
@@ -366,10 +400,15 @@ var STAmount = exports.Amount = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
var STVL = exports.VariableLength = new SerializedType({
|
||||
STAmount.id = 6;
|
||||
|
||||
var STVL = exports.VariableLength = exports.VL = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
if ("string" === typeof val) serialize_hex(so, val);
|
||||
else throw new Error("Unknown datatype.");
|
||||
if (typeof val === 'string') {
|
||||
serialize_hex(so, val);
|
||||
} else {
|
||||
throw new Error('Unknown datatype.');
|
||||
}
|
||||
},
|
||||
parse: function (so) {
|
||||
var len = this.parse_varint(so);
|
||||
@@ -377,54 +416,69 @@ var STVL = exports.VariableLength = new SerializedType({
|
||||
}
|
||||
});
|
||||
|
||||
STVL.id = 7;
|
||||
|
||||
var STAccount = exports.Account = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var account = UInt160.from_json(val);
|
||||
if (!account.is_valid()) {
|
||||
throw new Error('Invalid account!');
|
||||
}
|
||||
serialize_hex(so, account.to_hex());
|
||||
},
|
||||
parse: function (so) {
|
||||
var len = this.parse_varint(so);
|
||||
|
||||
if (len !== 20) {
|
||||
throw new Error("Non-standard-length account ID");
|
||||
throw new Error('Non-standard-length account ID');
|
||||
}
|
||||
|
||||
var result = UInt160.from_bytes(so.read(len));
|
||||
if (!result.is_valid()) {
|
||||
throw new Error("Invalid Account");
|
||||
result.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
//console.log('PARSED 160:', result.to_json());
|
||||
if (false && !result.is_valid()) {
|
||||
throw new Error('Invalid Account');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
STAccount.id = 8;
|
||||
|
||||
var STPathSet = exports.PathSet = new SerializedType({
|
||||
typeBoundary: 0xff,
|
||||
typeEnd: 0x00,
|
||||
typeAccount: 0x01,
|
||||
typeCurrency: 0x10,
|
||||
typeIssuer: 0x20,
|
||||
typeBoundary: 0xff,
|
||||
typeEnd: 0x00,
|
||||
typeAccount: 0x01,
|
||||
typeCurrency: 0x10,
|
||||
typeIssuer: 0x20,
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
for (var i = 0, l = val.length; i < l; i++) {
|
||||
for (var i=0, l=val.length; i<l; i++) {
|
||||
// Boundary
|
||||
if (i) STInt8.serialize(so, this.typeBoundary);
|
||||
if (i) {
|
||||
STInt8.serialize(so, this.typeBoundary);
|
||||
}
|
||||
|
||||
for (var j = 0, l2 = val[i].length; j < l2; j++) {
|
||||
for (var j=0, l2=val[i].length; j<l2; j++) {
|
||||
var entry = val[i][j];
|
||||
|
||||
//if (entry.hasOwnProperty('_value')) {entry = entry._value;}
|
||||
var type = 0;
|
||||
|
||||
if (entry.account) type |= this.typeAccount;
|
||||
if (entry.account) type |= this.typeAccount;
|
||||
if (entry.currency) type |= this.typeCurrency;
|
||||
if (entry.issuer) type |= this.typeIssuer;
|
||||
if (entry.issuer) type |= this.typeIssuer;
|
||||
|
||||
STInt8.serialize(so, type);
|
||||
|
||||
if (entry.account) {
|
||||
so.append(UInt160.from_json(entry.account).to_bytes());
|
||||
}
|
||||
|
||||
if (entry.currency) {
|
||||
var currency = Currency.from_json(entry.currency);
|
||||
var currency = Currency.from_json(entry.currency, entry.non_native);
|
||||
STCurrency.serialize(so, currency);
|
||||
}
|
||||
|
||||
if (entry.issuer) {
|
||||
so.append(UInt160.from_json(entry.issuer).to_bytes());
|
||||
}
|
||||
@@ -433,40 +487,244 @@ var STPathSet = exports.PathSet = new SerializedType({
|
||||
STInt8.serialize(so, this.typeEnd);
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing PathSet not implemented");
|
||||
// should return a list of lists:
|
||||
/*
|
||||
[
|
||||
[entry, entry],
|
||||
[entry, entry, entry],
|
||||
[entry],
|
||||
[]
|
||||
]
|
||||
|
||||
each entry has one or more of the following attributes: amount, currency, issuer.
|
||||
*/
|
||||
|
||||
var path_list = [];
|
||||
var current_path = [];
|
||||
var tag_byte;
|
||||
|
||||
while ((tag_byte = so.read(1)[0]) !== this.typeEnd) {
|
||||
//TODO: try/catch this loop, and catch when we run out of data without reaching the end of the data structure.
|
||||
//Now determine: is this an end, boundary, or entry-begin-tag?
|
||||
//console.log('Tag byte:', tag_byte);
|
||||
if (tag_byte === this.typeBoundary) {
|
||||
//console.log('Boundary');
|
||||
if (current_path) { //close the current path, if there is one,
|
||||
path_list.push(current_path);
|
||||
}
|
||||
current_path = []; //and start a new one.
|
||||
} else {
|
||||
//It's an entry-begin tag.
|
||||
//console.log('It's an entry-begin tag.');
|
||||
var entry = {};
|
||||
|
||||
if (tag_byte & this.typeAccount) {
|
||||
//console.log('entry.account');
|
||||
/*var bta = so.read(20);
|
||||
console.log('BTA:', bta);*/
|
||||
entry.account = STHash160.parse(so);
|
||||
entry.account.set_version(Base.VER_ACCOUNT_ID);
|
||||
}
|
||||
if (tag_byte & this.typeCurrency) {
|
||||
//console.log('entry.currency');
|
||||
entry.currency = STCurrency.parse(so);
|
||||
if (entry.currency.to_json() === "XRP" &&
|
||||
!entry.currency.is_native()) {
|
||||
entry.non_native = true;
|
||||
}
|
||||
}
|
||||
if (tag_byte & this.typeIssuer) {
|
||||
//console.log('entry.issuer');
|
||||
entry.issuer = STHash160.parse(so);
|
||||
// Enable and set correct type of base-58 encoding
|
||||
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
//console.log('DONE WITH ISSUER!');
|
||||
}
|
||||
|
||||
if (entry.account || entry.currency || entry.issuer) {
|
||||
current_path.push(entry);
|
||||
} else {
|
||||
throw new Error('Invalid path entry'); //It must have at least something in it.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current_path) {
|
||||
//close the current path, if there is one,
|
||||
path_list.push(current_path);
|
||||
}
|
||||
|
||||
return path_list;
|
||||
}
|
||||
});
|
||||
|
||||
STPathSet.id = 18;
|
||||
|
||||
var STVector256 = exports.Vector256 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Vector256 not implemented");
|
||||
serialize: function (so, val) { //Assume val is an array of STHash256 objects.
|
||||
var length_as_varint = SerializedType.serialize_varint(so, val.length * 32);
|
||||
for (var i=0, l=val.length; i<l; i++) {
|
||||
STHash256.serialize(so, val[i]);
|
||||
}
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Vector256 not implemented");
|
||||
var length = this.parse_varint(so);
|
||||
var output = [];
|
||||
// length is number of bytes not number of Hash256
|
||||
for (var i=0; i<length / 32; i++) {
|
||||
output.push(STHash256.parse(so));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STVector256.id = 19;
|
||||
|
||||
exports.serialize = exports.serialize_whatever = serialize;
|
||||
|
||||
function serialize(so, field_name, value) {
|
||||
//so: a byte-stream to serialize into.
|
||||
//field_name: a string for the field name ('LedgerEntryType' etc.)
|
||||
//value: the value of that field.
|
||||
var field_coordinates = binformat.fieldsInverseMap[field_name];
|
||||
var type_bits = field_coordinates[0];
|
||||
var field_bits = field_coordinates[1];
|
||||
var tag_byte = (type_bits < 16 ? type_bits << 4 : 0) | (field_bits < 16 ? field_bits : 0);
|
||||
|
||||
if (field_name === "LedgerEntryType" && "string" === typeof value) {
|
||||
value = binformat.ledger[value][0];
|
||||
}
|
||||
|
||||
if (field_name === "TransactionResult" && "string" === typeof value) {
|
||||
value = binformat.ter[value];
|
||||
}
|
||||
|
||||
STInt8.serialize(so, tag_byte);
|
||||
|
||||
if (type_bits >= 16) {
|
||||
STInt8.serialize(so, type_bits);
|
||||
}
|
||||
|
||||
if (field_bits >= 16) {
|
||||
STInt8.serialize(so, field_bits);
|
||||
}
|
||||
|
||||
// Get the serializer class (ST...) for a field based on the type bits.
|
||||
var serialized_object_type = exports[binformat.types[type_bits]];
|
||||
//do something with val[keys] and val[keys[i]];
|
||||
serialized_object_type.serialize(so, value);
|
||||
}
|
||||
|
||||
//Take the serialized object, figure out what type/field it is, and return the parsing of that.
|
||||
exports.parse = exports.parse_whatever = parse;
|
||||
|
||||
function parse(so) {
|
||||
var tag_byte = so.read(1)[0];
|
||||
var type_bits = tag_byte >> 4;
|
||||
|
||||
if (type_bits === 0) {
|
||||
type_bits = so.read(1)[0];
|
||||
}
|
||||
|
||||
// Get the parser class (ST...) for a field based on the type bits.
|
||||
var type = exports[binformat.types[type_bits]];
|
||||
|
||||
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
var field_bits = tag_byte & 0x0f;
|
||||
var field_name = (field_bits === 0)
|
||||
? field_name = binformat.fields[type_bits][so.read(1)[0]]
|
||||
: field_name = binformat.fields[type_bits][field_bits];
|
||||
|
||||
assert(field_name, 'Unknown field - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
return [ field_name, type.parse(so) ]; //key, value
|
||||
};
|
||||
|
||||
function sort_fields(keys) {
|
||||
function sort_field_compare(a, b) {
|
||||
var a_field_coordinates = binformat.fieldsInverseMap[a];
|
||||
var a_type_bits = a_field_coordinates[0];
|
||||
var a_field_bits = a_field_coordinates[1];
|
||||
var b_field_coordinates = binformat.fieldsInverseMap[b];
|
||||
var b_type_bits = b_field_coordinates[0];
|
||||
var b_field_bits = b_field_coordinates[1];
|
||||
|
||||
// Sort by type id first, then by field id
|
||||
return a_type_bits !== b_type_bits ? a_type_bits - b_type_bits : a_field_bits - b_field_bits;
|
||||
};
|
||||
|
||||
return keys.sort(sort_field_compare);
|
||||
}
|
||||
|
||||
var STObject = exports.Object = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Object not implemented");
|
||||
serialize: function (so, val, no_marker) {
|
||||
var keys = Object.keys(val);
|
||||
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
keys = keys.filter(function (key) {
|
||||
return key[0] !== key[0].toLowerCase();
|
||||
});
|
||||
|
||||
keys.forEach(function (key) {
|
||||
if ("undefined" === typeof binformat.fieldsInverseMap[key]) {
|
||||
throw new Error("JSON contains unknown field: '" + key + "'");
|
||||
}
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
serialize(so, keys[i], val[keys[i]]);
|
||||
}
|
||||
if (!no_marker) STInt8.serialize(so, 0xe1); //Object ending marker
|
||||
},
|
||||
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Object not implemented");
|
||||
var output = {};
|
||||
while (so.peek(1)[0] !== 0xe1) {
|
||||
var keyval = parse(so);
|
||||
output[keyval[0]] = keyval[1];
|
||||
}
|
||||
so.read(1);
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STObject.id = 14;
|
||||
|
||||
var STArray = exports.Array = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Array not implemented");
|
||||
for (var i=0, l=val.length; i<l; i++) {
|
||||
var keys = Object.keys(val[i]);
|
||||
|
||||
if (keys.length !== 1) {
|
||||
throw Error('Cannot serialize an array containing non-single-key objects');
|
||||
}
|
||||
|
||||
var field_name = keys[0];
|
||||
var value = val[i][field_name];
|
||||
serialize(so, field_name, value);
|
||||
}
|
||||
STInt8.serialize(so, 0xf1); //Array ending marker
|
||||
},
|
||||
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Array not implemented");
|
||||
var output = [ ];
|
||||
|
||||
while (so.peek(1)[0] !== 0xf1) {
|
||||
var keyval = parse(so);
|
||||
var obj = { };
|
||||
obj[keyval[0]] = keyval[1];
|
||||
output.push(obj);
|
||||
}
|
||||
|
||||
so.read(1);
|
||||
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STArray.id = 15;
|
||||
|
||||
@@ -1,44 +1,102 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var Amount = require('./amount').Amount;
|
||||
var utils = require('./utils');
|
||||
var log = require('./log').internal.sub('server');
|
||||
|
||||
/**
|
||||
* @constructor Server
|
||||
* @param remote The Remote object
|
||||
* @param cfg Configuration parameters.
|
||||
* @param {Remote} Reference to a Remote object
|
||||
* @param {Object} Options
|
||||
*
|
||||
* Keys for cfg:
|
||||
* url
|
||||
*/
|
||||
* host: String
|
||||
* port: String or Number
|
||||
* secure: Boolean
|
||||
*/
|
||||
|
||||
var Server = function (remote, opts) {
|
||||
function Server(remote, opts) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
if (typeof opts !== 'object' || typeof opts.url !== 'string') {
|
||||
throw new Error('Invalid server configuration.');
|
||||
if (typeof opts !== 'object') {
|
||||
throw new TypeError('Server configuration is not an Object');
|
||||
}
|
||||
|
||||
if (!opts.host) opts.host = opts.websocket_ip;
|
||||
if (!opts.port) opts.port = opts.websocket_port;
|
||||
if (!opts.secure) opts.secure = opts.websocket_ssl;
|
||||
|
||||
if (typeof opts.secure === 'undefined') {
|
||||
opts.secure = false;
|
||||
}
|
||||
|
||||
var domainRE = /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/;
|
||||
|
||||
if (!domainRE.test(opts.host)) {
|
||||
throw new Error('Server host is malformed, use "host" and "port" server configuration');
|
||||
}
|
||||
|
||||
if (typeof opts.port !== 'number') {
|
||||
throw new TypeError('Server configuration "port" is not a Number');
|
||||
}
|
||||
|
||||
if (typeof opts.secure !== 'boolean') {
|
||||
throw new TypeError('Server "secure" configuration is not a Boolean');
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._opts = opts;
|
||||
this._remote = remote;
|
||||
this._opts = opts;
|
||||
this._host = opts.host;
|
||||
this._port = opts.port;
|
||||
this._secure = opts.secure;
|
||||
this._ws = void(0);
|
||||
this._connected = false;
|
||||
this._shouldConnect = false;
|
||||
this._state = 'offline';
|
||||
this._id = 0;
|
||||
this._retry = 0;
|
||||
this._requests = { };
|
||||
this._load_base = 256;
|
||||
this._load_factor = 256;
|
||||
this._fee_ref = 10;
|
||||
this._fee_base = 10;
|
||||
this._reserve_base = void(0);
|
||||
this._reserve_inc = void(0);
|
||||
this._fee_cushion = this._remote.fee_cushion;
|
||||
|
||||
this._ws = void(0);
|
||||
this._connected = false;
|
||||
this._should_connect = false;
|
||||
this._state = void(0);
|
||||
|
||||
this._id = 0;
|
||||
this._retry = 0;
|
||||
|
||||
this._requests = { };
|
||||
this._opts.url = (opts.secure ? 'wss://' : 'ws://') + opts.host + ':' + opts.port;
|
||||
|
||||
this.on('message', function(message) {
|
||||
self._handle_message(message);
|
||||
self._handleMessage(message);
|
||||
});
|
||||
|
||||
this.on('response_subscribe', function(message) {
|
||||
self._handle_response_subscribe(message);
|
||||
self._handleResponseSubscribe(message);
|
||||
});
|
||||
|
||||
function checkServerActivity() {
|
||||
if (isNaN(self._lastLedgerClose)) return;
|
||||
|
||||
var delta = (Date.now() - self._lastLedgerClose);
|
||||
|
||||
if (delta > (1000 * 20)) {
|
||||
self.reconnect();
|
||||
}
|
||||
};
|
||||
|
||||
function setActivityInterval() {
|
||||
self._activityInterval = setInterval(checkServerActivity, 1000);
|
||||
};
|
||||
|
||||
this.on('disconnect', function onDisconnect() {
|
||||
clearInterval(self._activityInterval);
|
||||
//self.once('ledger_closed', setActivityInterval);
|
||||
});
|
||||
|
||||
this.once('ledger_closed', function() {
|
||||
//setActiviyInterval();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -50,201 +108,274 @@ util.inherits(Server, EventEmitter);
|
||||
* Our requirements are that the server can process transactions and notify
|
||||
* us of changes.
|
||||
*/
|
||||
Server.online_states = [
|
||||
'syncing'
|
||||
, 'tracking'
|
||||
, 'proposing'
|
||||
, 'validating'
|
||||
, 'full'
|
||||
|
||||
Server.onlineStates = [
|
||||
'syncing',
|
||||
'tracking',
|
||||
'proposing',
|
||||
'validating',
|
||||
'full'
|
||||
];
|
||||
|
||||
Server.prototype._is_online = function (status) {
|
||||
return Server.online_states.indexOf(status) !== -1;
|
||||
};
|
||||
/**
|
||||
* Set server state
|
||||
*
|
||||
* @param {String} state
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._set_state = function (state) {
|
||||
Server.prototype._setState = function(state) {
|
||||
if (state !== this._state) {
|
||||
this._remote.trace && log.info('set_state:', state);
|
||||
this._state = state;
|
||||
|
||||
this.emit('state', state);
|
||||
|
||||
if (state === 'online') {
|
||||
this._connected = true;
|
||||
this.emit('connect');
|
||||
} else if (state === 'offline') {
|
||||
this._connected = false;
|
||||
this.emit('disconnect');
|
||||
switch (state) {
|
||||
case 'online':
|
||||
this._connected = true;
|
||||
this.emit('connect');
|
||||
break;
|
||||
case 'offline':
|
||||
this._connected = false;
|
||||
this.emit('disconnect');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype.connect = function () {
|
||||
/**
|
||||
* Get the remote address for a server.
|
||||
* Incompatible with ripple-lib client build
|
||||
*/
|
||||
|
||||
Server.prototype._remoteAddress = function() {
|
||||
try { var address = this._ws._socket.remoteAddress; } catch (e) { }
|
||||
return address;
|
||||
};
|
||||
|
||||
/** This is the final interface between client code and a socket connection to a
|
||||
* `rippled` server. As such, this is a decent hook point to allow a WebSocket
|
||||
* interface conforming object to be used as a basis to mock rippled. This
|
||||
* avoids the need to bind a websocket server to a port and allows a more
|
||||
* synchronous style of code to represent a client <-> server message sequence.
|
||||
* We can also use this to log a message sequence to a buffer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.websocketConstructor = function() {
|
||||
// We require this late, because websocket shims may be loaded after
|
||||
// ripple-lib in the browser
|
||||
return require('ws');
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect from rippled WebSocket server
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.disconnect = function() {
|
||||
this._shouldConnect = false;
|
||||
this._setState('offline');
|
||||
if (this._ws) this._ws.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Reconnect to rippled WebSocket server
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.reconnect = function() {
|
||||
if (this._ws) {
|
||||
this.once('disconnect', this.connect.bind(this));
|
||||
this.disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to rippled WebSocket server and subscribe to events that are
|
||||
* internally requisite. Automatically retry connections with a gradual
|
||||
* back-off
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.connect = function() {
|
||||
var self = this;
|
||||
|
||||
// We don't connect if we believe we're already connected. This means we have
|
||||
// recently received a message from the server and the WebSocket has not
|
||||
// reported any issues either. If we do fail to ping or the connection drops,
|
||||
// we will automatically reconnect.
|
||||
if (this._connected === true) return;
|
||||
if (this._connected) return;
|
||||
|
||||
if (this._remote.trace) console.log('server: connect: %s', this._opts.url);
|
||||
this._remote.trace && log.info('connect:', this._opts.url);
|
||||
|
||||
// Ensure any existing socket is given the command to close first.
|
||||
if (this._ws) this._ws.close();
|
||||
|
||||
// We require this late, because websocket shims may be loaded after
|
||||
// ripple-lib.
|
||||
var WebSocket = require('ws');
|
||||
var WebSocket = Server.websocketConstructor();
|
||||
|
||||
if (!WebSocket) {
|
||||
throw new Error('No websocket support detected!');
|
||||
}
|
||||
|
||||
var ws = this._ws = new WebSocket(this._opts.url);
|
||||
|
||||
this._should_connect = true;
|
||||
this._shouldConnect = true;
|
||||
|
||||
self.emit('connecting');
|
||||
|
||||
ws.onopen = function () {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws !== self._ws) return;
|
||||
|
||||
self.emit('socket_open');
|
||||
|
||||
// Subscribe to events
|
||||
var request = self._remote._server_prepare_subscribe();
|
||||
self.request(request);
|
||||
ws.onmessage = function onMessage(msg) {
|
||||
self.emit('message', msg.data);
|
||||
};
|
||||
|
||||
ws.onerror = function (e) {
|
||||
ws.onopen = function onOpen() {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws !== self._ws) return;
|
||||
if (ws === self._ws) {
|
||||
self.emit('socket_open');
|
||||
// Subscribe to events
|
||||
self.request(self._remote._serverPrepareSubscribe());
|
||||
}
|
||||
};
|
||||
|
||||
if (self._remote.trace) console.log('server: onerror: %s', e.data || e);
|
||||
ws.onerror = function onError(e) {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws === self._ws) {
|
||||
self.emit('socket_error');
|
||||
self._remote.trace && log.info('onerror:', self._opts.url, e.data || e);
|
||||
|
||||
// Most connection errors for WebSockets are conveyed as 'close' events with
|
||||
// code 1006. This is done for security purposes and therefore unlikely to
|
||||
// ever change.
|
||||
// Most connection errors for WebSockets are conveyed as 'close' events with
|
||||
// code 1006. This is done for security purposes and therefore unlikely to
|
||||
// ever change.
|
||||
|
||||
// This means that this handler is hardly ever called in practice. If it is,
|
||||
// it probably means the server's WebSocket implementation is corrupt, or
|
||||
// the connection is somehow producing corrupt data.
|
||||
// This means that this handler is hardly ever called in practice. If it is,
|
||||
// it probably means the server's WebSocket implementation is corrupt, or
|
||||
// the connection is somehow producing corrupt data.
|
||||
|
||||
// Most WebSocket applications simply log and ignore this error. Once we
|
||||
// support for multiple servers, we may consider doing something like
|
||||
// lowering this server's quality score.
|
||||
// Most WebSocket applications simply log and ignore this error. Once we
|
||||
// support for multiple servers, we may consider doing something like
|
||||
// lowering this server's quality score.
|
||||
|
||||
// However, in Node.js this event may be triggered instead of the close
|
||||
// event, so we need to handle it.
|
||||
handleConnectionClose();
|
||||
// However, in Node.js this event may be triggered instead of the close
|
||||
// event, so we need to handle it.
|
||||
self._handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
// Failure to open.
|
||||
ws.onclose = function () {
|
||||
ws.onclose = function onClose() {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws !== self._ws) return;
|
||||
|
||||
if (self._remote.trace) console.log('server: onclose: %s', ws.readyState);
|
||||
|
||||
handleConnectionClose();
|
||||
if (ws === self._ws) {
|
||||
self._remote.trace && log.info('onclose:', self._opts.url, ws.readyState);
|
||||
self._handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
function handleConnectionClose() {
|
||||
self.emit('socket_close');
|
||||
self._set_state('offline');
|
||||
|
||||
// Prevent additional events from this socket
|
||||
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function () {};
|
||||
|
||||
// Should we be connected?
|
||||
if (!self._should_connect) return;
|
||||
|
||||
// Delay and retry.
|
||||
self._retry += 1;
|
||||
self._retry_timer = setTimeout(function () {
|
||||
if (self._remote.trace) console.log('server: retry');
|
||||
|
||||
if (!self._should_connect) return;
|
||||
self.connect();
|
||||
}, self._retry < 40
|
||||
? 1000/20 // First, for 2 seconds: 20 times per second
|
||||
: self._retry < 40+60
|
||||
? 1000 // Then, for 1 minute: once per second
|
||||
: self._retry < 40+60+60
|
||||
? 10*1000 // Then, for 10 minutes: once every 10 seconds
|
||||
: 30*1000); // Then: once every 30 seconds
|
||||
}
|
||||
|
||||
ws.onmessage = function (msg) {
|
||||
self.emit('message', msg.data);
|
||||
};
|
||||
};
|
||||
|
||||
Server.prototype.disconnect = function () {
|
||||
this._should_connect = false;
|
||||
this._set_state('offline');
|
||||
if (this._ws) {
|
||||
this._ws.close();
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype.send_message = function (message) {
|
||||
this._ws.send(JSON.stringify(message));
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a Request object to this server.
|
||||
* Retry connection to rippled server
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
Server.prototype.request = function (request) {
|
||||
var self = this;
|
||||
|
||||
// Only bother if we are still connected.
|
||||
if (self._ws) {
|
||||
request.message.id = self._id;
|
||||
Server.prototype._retryConnect = function() {
|
||||
var self = this;
|
||||
|
||||
self._requests[request.message.id] = request;
|
||||
this._retry += 1;
|
||||
|
||||
// Advance message ID
|
||||
self._id++;
|
||||
var retryTimeout = (this._retry < 40)
|
||||
? (1000 / 20) // First, for 2 seconds: 20 times per second
|
||||
: (this._retry < 40 + 60)
|
||||
? (1000) // Then, for 1 minute: once per second
|
||||
: (this._retry < 40 + 60 + 60)
|
||||
? (10 * 1000) // Then, for 10 minutes: once every 10 seconds
|
||||
: (30 * 1000); // Then: once every 30 seconds
|
||||
|
||||
if (self._connected || (request.message.command === 'subscribe' && self._ws.readyState === 1)) {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject('server: request: %s', request.message);
|
||||
}
|
||||
|
||||
self.send_message(request.message);
|
||||
} else {
|
||||
// XXX There are many ways to make self smarter.
|
||||
self.once('connect', function () {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject('server: request: %s', request.message);
|
||||
}
|
||||
self.send_message(request.message);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject('server: request: DROPPING: %s', request.message);
|
||||
function connectionRetry() {
|
||||
if (self._shouldConnect) {
|
||||
self._remote.trace && log.info('retry', self._opts.url);
|
||||
self.connect();
|
||||
}
|
||||
};
|
||||
|
||||
this._retryTimer = setTimeout(connectionRetry, retryTimeout);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle connection closes
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._handleClose = function() {
|
||||
var self = this;
|
||||
var ws = this._ws;
|
||||
|
||||
this.emit('socket_close');
|
||||
this._setState('offline');
|
||||
|
||||
// Prevent additional events from this socket
|
||||
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = function noOp() {};
|
||||
|
||||
if (self._shouldConnect) {
|
||||
this._retryConnect();
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._handle_message = function (json) {
|
||||
/**
|
||||
* Handle incoming messages from rippled WebSocket server
|
||||
*
|
||||
* @param {JSON-parseable} message
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._handleMessage = function(message) {
|
||||
var self = this;
|
||||
|
||||
var message;
|
||||
|
||||
try {
|
||||
message = JSON.parse(json);
|
||||
} catch(exception) { return; }
|
||||
try { message = JSON.parse(message); } catch(e) { }
|
||||
|
||||
if (!Server.isValidMessage(message)) return;
|
||||
|
||||
switch (message.type) {
|
||||
case 'ledgerClosed':
|
||||
this._lastLedgerClose = Date.now();
|
||||
this.emit('ledger_closed', message);
|
||||
break;
|
||||
|
||||
case 'serverStatus':
|
||||
// This message is only received when online.
|
||||
// As we are connected, it is the definitive final state.
|
||||
|
||||
this._setState(~(Server.onlineStates.indexOf(message.server_status)) ? 'online' : 'offline');
|
||||
|
||||
if (Server.isLoadStatus(message)) {
|
||||
self.emit('load', message, self);
|
||||
self._remote.emit('load', message, self);
|
||||
|
||||
var loadChanged = ((message.load_base !== self._load_base) ||
|
||||
(message.load_factor !== self._load_factor));
|
||||
|
||||
if (loadChanged) {
|
||||
self._load_base = message.load_base;
|
||||
self._load_factor = message.load_factor;
|
||||
self.emit('load_changed', message, self);
|
||||
self._remote.emit('load_changed', message, self);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
switch(message.type) {
|
||||
case 'response':
|
||||
// A response to a request.
|
||||
var request = self._requests[message.id];
|
||||
|
||||
delete self._requests[message.id];
|
||||
|
||||
if (!request) {
|
||||
if (self._remote.trace) utils.logObject('server: UNEXPECTED: %s', message);
|
||||
} else if ('success' === message.status) {
|
||||
if (self._remote.trace) utils.logObject('server: response: %s', message);
|
||||
this._remote.trace && log.info('UNEXPECTED:', self._opts.url, message);
|
||||
} else if (message.status === 'success') {
|
||||
this._remote.trace && log.info('response:', self._opts.url, message);
|
||||
|
||||
request.emit('success', message.result);
|
||||
|
||||
@@ -252,35 +383,200 @@ Server.prototype._handle_message = function (json) {
|
||||
emitter.emit('response_' + request.message.command, message.result, request, message);
|
||||
});
|
||||
} else if (message.error) {
|
||||
if (self._remote.trace) utils.logObject('server: error: %s', message);
|
||||
this._remote.trace && log.info('error:', self._opts.url, message);
|
||||
|
||||
request.emit('error', {
|
||||
'error' : 'remoteError',
|
||||
'error_message' : 'Remote reported an error.',
|
||||
'remote' : message
|
||||
error : 'remoteError',
|
||||
error_message : 'Remote reported an error.',
|
||||
remote : message
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'path_find':
|
||||
if (self._remote.trace) utils.logObject('server: path_find: %s', message);
|
||||
this._remote.trace && log.info('path_find:', self._opts.url, message);
|
||||
break;
|
||||
|
||||
case 'serverStatus':
|
||||
// This message is only received when online. As we are connected, it is the definative final state.
|
||||
self._set_state(self._is_online(message.server_status) ? 'online' : 'offline');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._handle_response_subscribe = function (message) {
|
||||
var self = this;
|
||||
/**
|
||||
* Check that received message from rippled is valid
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
self._server_status = message.server_status;
|
||||
Server.isValidMessage = function(message) {
|
||||
return (typeof message === 'object')
|
||||
&& (typeof message.type === 'string');
|
||||
};
|
||||
|
||||
if (self._is_online(message.server_status)) {
|
||||
self._set_state('online');
|
||||
/**
|
||||
* Check that received serverStatus message contains
|
||||
* load status information
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.isLoadStatus = function(message) {
|
||||
return (typeof message.load_base === 'number')
|
||||
&& (typeof message.load_factor === 'number');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle subscription response messages. Subscription response
|
||||
* messages indicate that a connection to the server is ready
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._handleResponseSubscribe = function(message) {
|
||||
if (~(Server.onlineStates.indexOf(message.server_status))) {
|
||||
this._setState('online');
|
||||
}
|
||||
if (Server.isLoadStatus(message)) {
|
||||
this._load_base = message.load_base || 256;
|
||||
this._load_factor = message.load_factor || 256;
|
||||
this._fee_ref = message.fee_ref;
|
||||
this._fee_base = message.fee_base;
|
||||
this._reserve_base = message.reserve_base;
|
||||
this._reserve_inc = message.reserve_inc;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send JSON message to rippled WebSocket server
|
||||
*
|
||||
* @param {JSON-Stringifiable} message
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.sendMessage = function(message) {
|
||||
if (this._ws) {
|
||||
this._remote.trace && log.info('request:', this._opts.url, message);
|
||||
this._ws.send(JSON.stringify(message));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a Request object.
|
||||
*
|
||||
* Requests are indexed by message ID, which is repeated
|
||||
* in the response from rippled WebSocket server
|
||||
*
|
||||
* @param {Request} request
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.request = function(request) {
|
||||
var self = this;
|
||||
|
||||
// Only bother if we are still connected.
|
||||
if (!this._ws) {
|
||||
this._remote.trace && log.info('request: DROPPING:', self._opts.url, request.message);
|
||||
return;
|
||||
}
|
||||
|
||||
request.server = this;
|
||||
request.message.id = this._id;
|
||||
|
||||
this._requests[request.message.id] = request;
|
||||
|
||||
// Advance message ID
|
||||
this._id++;
|
||||
|
||||
if (this._isConnected(request)) {
|
||||
this.sendMessage(request.message);
|
||||
} else {
|
||||
// XXX There are many ways to make this smarter.
|
||||
function serverReconnected() {
|
||||
self.sendMessage(request.message);
|
||||
}
|
||||
this.once('connect', serverReconnected);
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._isConnected = function(request) {
|
||||
var isSubscribeRequest = request
|
||||
&& request.message.command === 'subscribe'
|
||||
&& this._ws.readyState === 1;
|
||||
|
||||
return this._connected || (this._ws && isSubscribeRequest);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate transaction fee
|
||||
*
|
||||
* @param {Transaction|Number} Fee units for a provided transaction
|
||||
* @return {Number} Final fee in XRP for specified number of fee units
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype.computeFee = function(transaction) {
|
||||
var units;
|
||||
|
||||
if (transaction instanceof Transaction) {
|
||||
units = transaction.feeUnits();
|
||||
} else if (typeof transaction === 'number') {
|
||||
units = transaction;
|
||||
} else {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
return this.feeTx(units).to_json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate a transaction fee for a number of tx fee units.
|
||||
*
|
||||
* This takes into account the last known network and local load fees.
|
||||
*
|
||||
* @param {Number} Fee units for a provided transaction
|
||||
* @return {Amount} Final fee in XRP for specified number of fee units.
|
||||
*/
|
||||
|
||||
Server.prototype.feeTx = function(units) {
|
||||
var fee_unit = this.feeTxUnit();
|
||||
return Amount.from_json(String(Math.ceil(units * fee_unit)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current recommended transaction fee unit.
|
||||
*
|
||||
* Multiply this value with the number of fee units in order to calculate the
|
||||
* recommended fee for the transaction you are trying to submit.
|
||||
*
|
||||
* @return {Number} Recommended amount for one fee unit as float.
|
||||
*/
|
||||
|
||||
Server.prototype.feeTxUnit = function() {
|
||||
var fee_unit = this._fee_base / this._fee_ref;
|
||||
|
||||
// Apply load fees
|
||||
fee_unit *= this._load_factor / this._load_base;
|
||||
|
||||
// Apply fee cushion (a safety margin in case fees rise since we were last updated)
|
||||
fee_unit *= this._fee_cushion;
|
||||
|
||||
return fee_unit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current recommended reserve base.
|
||||
*
|
||||
* Returns the base reserve with load fees and safety margin applied.
|
||||
*/
|
||||
|
||||
Server.prototype.reserve = function(owner_count) {
|
||||
var reserve_base = Amount.from_json(String(this._reserve_base));
|
||||
var reserve_inc = Amount.from_json(String(this._reserve_inc));
|
||||
var owner_count = owner_count || 0;
|
||||
|
||||
if (owner_count < 0) {
|
||||
throw new Error('Owner count must not be negative.');
|
||||
}
|
||||
|
||||
return reserve_base.add(reserve_inc.product_human(owner_count));
|
||||
};
|
||||
|
||||
exports.Server = Server;
|
||||
|
||||
173
src/js/ripple/shamap.js
Normal file
173
src/js/ripple/shamap.js
Normal file
@@ -0,0 +1,173 @@
|
||||
var util = require('util');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var stypes = require('./serializedtypes');
|
||||
var hashprefixes = require('./hashprefixes');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
|
||||
function SHAMap() {
|
||||
this.root = new SHAMapTreeNodeInner();
|
||||
};
|
||||
|
||||
SHAMap.prototype.add_item = function (tag, node, type) {
|
||||
var node = new SHAMapTreeNodeLeaf(tag, node, type);
|
||||
this.root.add_item(tag, node);
|
||||
};
|
||||
|
||||
SHAMap.prototype.hash = function () {
|
||||
return this.root.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract class representing a node in a SHAMap tree.
|
||||
*
|
||||
* Can be either SHAMapTreeNodeInner or SHAMapTreeNodeLeaf.
|
||||
*/
|
||||
function SHAMapTreeNode() {
|
||||
|
||||
};
|
||||
|
||||
SHAMapTreeNode.TYPE_INNER = 1;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_NM = 2;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_MD = 3;
|
||||
SHAMapTreeNode.TYPE_ACCOUNT_STATE = 4;
|
||||
|
||||
SHAMapTreeNode.prototype.add_item = function (tag_segment, node) {
|
||||
throw new Error("Called unimplemented virtual method SHAMapTreeNode#add_item.");
|
||||
};
|
||||
|
||||
SHAMapTreeNode.prototype.hash = function () {
|
||||
throw new Error("Called unimplemented virtual method SHAMapTreeNode#hash.");
|
||||
};
|
||||
|
||||
/**
|
||||
* Inner (non-leaf) node in a SHAMap tree.
|
||||
*/
|
||||
function SHAMapTreeNodeInner() {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
this.leaves = {};
|
||||
|
||||
this.type = SHAMapTreeNode.INNER;
|
||||
|
||||
this.empty = true;
|
||||
}
|
||||
|
||||
util.inherits(SHAMapTreeNodeInner, SHAMapTreeNode);
|
||||
|
||||
SHAMapTreeNodeInner.prototype.add_item = function (tag_segment, node) {
|
||||
var current_node = this.get_node(tag_segment[0]);
|
||||
|
||||
if (current_node) {
|
||||
// A node already exists in this slot
|
||||
|
||||
if (current_node instanceof SHAMapTreeNodeInner) {
|
||||
// There is an inner node, so we need to go deeper
|
||||
current_node.add_item(tag_segment.slice(1), node);
|
||||
} else if (current_node.get_segment() === tag_segment) {
|
||||
// Collision
|
||||
throw new Error("Tried to add a node to a SHAMap that was already in there.");
|
||||
} else {
|
||||
// Turn it into an inner node
|
||||
var new_inner_node = new SHAMapTreeNodeInner();
|
||||
|
||||
// Move the existing leaf node down one level
|
||||
current_node.set_segment(current_node.get_segment().slice(1));
|
||||
new_inner_node.set_node(current_node.get_segment()[0], current_node);
|
||||
|
||||
// Add the new node next to it
|
||||
new_inner_node.add_item(tag_segment.slice(1), node);
|
||||
|
||||
// And place the newly created inner node in the slot
|
||||
this.set_node(tag_segment[0], new_inner_node);
|
||||
}
|
||||
} else {
|
||||
// Neat, we have a nice open spot for the new node
|
||||
node.set_segment(tag_segment);
|
||||
this.set_node(tag_segment[0], node);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Overwrite the node that is currently in a given slot.
|
||||
*/
|
||||
SHAMapTreeNodeInner.prototype.set_node = function (slot, node) {
|
||||
this.leaves[slot] = node;
|
||||
this.empty = false;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.get_node = function (slot) {
|
||||
return this.leaves[slot];
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.hash = function () {
|
||||
if (this.empty) {
|
||||
return UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
}
|
||||
|
||||
var hash_buffer = new SerializedObject();
|
||||
var buffer = [];
|
||||
for (var i = 0; i < 16; i++) {
|
||||
var leafHash = UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
var slot = i.toString(16).toUpperCase();
|
||||
if ("object" === typeof this.leaves[slot]) {
|
||||
leafHash = this.leaves[slot].hash();
|
||||
}
|
||||
hash_buffer.append(leafHash.to_bytes());
|
||||
}
|
||||
|
||||
var hash = hash_buffer.hash(hashprefixes.HASH_INNER_NODE);
|
||||
|
||||
return UInt256.from_bits(hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaf node in a SHAMap tree.
|
||||
*/
|
||||
function SHAMapTreeNodeLeaf(tag, node, type) {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
if ("string" === typeof tag) {
|
||||
tag = UInt256.from_hex(tag);
|
||||
} else if (tag instanceof UInt256) {
|
||||
// Type is already the right one
|
||||
} else {
|
||||
throw new Error("Tag is unexpected type.");
|
||||
}
|
||||
|
||||
this.tag = tag;
|
||||
this.tag_segment = null;
|
||||
|
||||
this.type = type;
|
||||
|
||||
this.node = node;
|
||||
}
|
||||
util.inherits(SHAMapTreeNodeLeaf, SHAMapTreeNode);
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.get_segment = function (segment) {
|
||||
return this.tag_segment;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.set_segment = function (segment) {
|
||||
this.tag_segment = segment;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.hash = function () {
|
||||
var buffer = new SerializedObject();
|
||||
switch (this.type) {
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_NM:
|
||||
return this.tag;
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_MD:
|
||||
buffer.append(this.node);
|
||||
buffer.append(this.tag.to_bytes());
|
||||
return buffer.hash(hashprefixes.HASH_TX_NODE);
|
||||
default:
|
||||
throw new Error("Tried to hash a SHAMap node of unknown type.");
|
||||
}
|
||||
};
|
||||
|
||||
exports.SHAMap = SHAMap;
|
||||
exports.SHAMapTreeNode = SHAMapTreeNode;
|
||||
exports.SHAMapTreeNodeInner = SHAMapTreeNodeInner;
|
||||
exports.SHAMapTreeNodeLeaf = SHAMapTreeNodeLeaf;
|
||||
File diff suppressed because it is too large
Load Diff
606
src/js/ripple/transactionmanager.js
Normal file
606
src/js/ripple/transactionmanager.js
Normal file
@@ -0,0 +1,606 @@
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var RippleError = require('./rippleerror').RippleError;
|
||||
var PendingQueue = require('./transactionqueue').TransactionQueue;
|
||||
|
||||
/**
|
||||
* @constructor TransactionManager
|
||||
* @param {Account} account
|
||||
*/
|
||||
|
||||
function TransactionManager(account) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this._account = account;
|
||||
this._accountID = account._account_id;
|
||||
this._remote = account._remote;
|
||||
this._nextSequence = void(0);
|
||||
this._maxFee = this._remote.max_fee;
|
||||
this._submissionTimeout = this._remote._submission_timeout;
|
||||
this._pending = new PendingQueue;
|
||||
|
||||
// Query remote server for next account sequence number
|
||||
this._loadSequence();
|
||||
|
||||
function transactionReceived(res) {
|
||||
var transaction = TransactionManager.normalizeTransaction(res);
|
||||
var sequence = transaction.transaction.Sequence;
|
||||
var hash = transaction.transaction.hash;
|
||||
|
||||
if (!transaction.validated) return;
|
||||
|
||||
self._pending.addReceivedSequence(sequence);
|
||||
|
||||
// ND: we need to check against all submissions IDs
|
||||
var submission = self._pending.getSubmission(hash);
|
||||
|
||||
self._remote._trace('transactionmanager: transaction_received:', transaction.transaction);
|
||||
|
||||
if (submission) {
|
||||
// ND: A `success` handler will `finalize` this later
|
||||
submission.emit('success', transaction);
|
||||
} else {
|
||||
self._pending.addReceivedId(hash, transaction);
|
||||
}
|
||||
};
|
||||
|
||||
this._account.on('transaction-outbound', transactionReceived);
|
||||
|
||||
function adjustFees(loadData, server) {
|
||||
// ND: note, that `Fee` is a component of a transactionID
|
||||
self._pending.forEach(function(pending) {
|
||||
var shouldAdjust = pending._server === server
|
||||
&& self._remote.local_fee && pending.tx_json.Fee;
|
||||
|
||||
if (!shouldAdjust) return;
|
||||
|
||||
var oldFee = pending.tx_json.Fee;
|
||||
var newFee = server.computeFee(pending);
|
||||
|
||||
if (Number(newFee) > self._maxFee) {
|
||||
return pending.once('presubmit', function() {
|
||||
pending.emit('error', 'tejMaxFeeExceeded');
|
||||
});
|
||||
}
|
||||
|
||||
pending.tx_json.Fee = newFee;
|
||||
pending.emit('fee_adjusted', oldFee, newFee);
|
||||
|
||||
self._remote._trace('transactionmanager: adjusting_fees:', pending.tx_json, oldFee, newFee);
|
||||
});
|
||||
};
|
||||
|
||||
this._remote.on('load_changed', adjustFees);
|
||||
|
||||
function updatePendingStatus(ledger) {
|
||||
self._pending.forEach(function(pending) {
|
||||
switch (ledger.ledger_index - pending.submitIndex) {
|
||||
case 8:
|
||||
pending.emit('lost', ledger);
|
||||
self._remote._trace('transactionmanager: update_pending:', pending.tx_json);
|
||||
break;
|
||||
case 4:
|
||||
pending.emit('missing', ledger);
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this._remote.on('ledger_closed', updatePendingStatus);
|
||||
|
||||
function remoteReconnected(callback) {
|
||||
var callback = (typeof callback === 'function') ? callback : function(){};
|
||||
|
||||
if (!self._pending.length) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
//Load account transaction history
|
||||
var options = {
|
||||
account: self._accountID,
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
binary: true,
|
||||
limit: 100,
|
||||
filter: 'outbound'
|
||||
}
|
||||
|
||||
function accountTx(err, transactions) {
|
||||
if (!err && Array.isArray(transactions.transactions)) {
|
||||
transactions.transactions.forEach(transactionReceived);
|
||||
}
|
||||
|
||||
self._remote.on('ledger_closed', updatePendingStatus);
|
||||
|
||||
//Load next transaction sequence
|
||||
self._loadSequence(function sequenceLoaded() {
|
||||
self._resubmit();
|
||||
});
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
self._remote.requestAccountTx(options, accountTx);
|
||||
|
||||
self.emit('reconnect');
|
||||
};
|
||||
|
||||
function remoteDisconnected() {
|
||||
self._remote.once('connect', remoteReconnected);
|
||||
self._remote.removeListener('ledger_closed', updatePendingStatus);
|
||||
};
|
||||
|
||||
this._remote.on('disconnect', remoteDisconnected);
|
||||
|
||||
function saveTransaction(transaction) {
|
||||
self._remote.storage.saveTransaction(transaction.summary());
|
||||
};
|
||||
|
||||
if (this._remote.storage) {
|
||||
this.on('save', saveTransaction);
|
||||
}
|
||||
};
|
||||
|
||||
util.inherits(TransactionManager, EventEmitter);
|
||||
|
||||
//Normalize transactions received from account
|
||||
//transaction stream and account_tx
|
||||
TransactionManager.normalizeTransaction = function(tx) {
|
||||
var transaction = tx;
|
||||
|
||||
if (!tx.engine_result) {
|
||||
// account_tx
|
||||
transaction = {
|
||||
engine_result: tx.meta.TransactionResult,
|
||||
transaction: tx.tx,
|
||||
hash: tx.tx.hash,
|
||||
ledger_index: tx.tx.ledger_index,
|
||||
meta: tx.meta,
|
||||
type: 'transaction',
|
||||
validated: true
|
||||
}
|
||||
transaction.result = transaction.engine_result;
|
||||
transaction.result_message = transaction.engine_result_message;
|
||||
}
|
||||
|
||||
transaction.metadata = transaction.meta;
|
||||
|
||||
if (!transaction.tx_json) {
|
||||
transaction.tx_json = transaction.transaction;
|
||||
}
|
||||
|
||||
return transaction;
|
||||
};
|
||||
|
||||
//Fill an account transaction sequence
|
||||
TransactionManager.prototype._fillSequence = function(tx, callback) {
|
||||
var self = this;
|
||||
|
||||
function submitFill(sequence, callback) {
|
||||
var fill = self._remote.transaction();
|
||||
fill.account_set(self._accountID);
|
||||
fill.tx_json.Sequence = sequence;
|
||||
fill.once('submitted', callback);
|
||||
fill.submit();
|
||||
};
|
||||
|
||||
function sequenceLoaded(err, sequence) {
|
||||
if (typeof sequence !== 'number') {
|
||||
callback(new Error('Failed to fetch account transaction sequence'));
|
||||
return;
|
||||
}
|
||||
|
||||
var sequenceDif = tx.tx_json.Sequence - sequence;
|
||||
var submitted = 0;
|
||||
|
||||
for (var i=sequence; i<tx.tx_json.Sequence; i++) {
|
||||
submitFill(i, function() {
|
||||
if (++submitted === sequenceDif) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this._loadSequence(sequenceLoaded);
|
||||
};
|
||||
|
||||
TransactionManager.prototype._loadSequence = function(callback) {
|
||||
var self = this;
|
||||
|
||||
function sequenceLoaded(err, sequence) {
|
||||
if (typeof sequence === 'number') {
|
||||
self._nextSequence = sequence;
|
||||
self.emit('sequence_loaded', sequence);
|
||||
if (typeof callback === 'function') {
|
||||
callback(err, sequence);
|
||||
}
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
self._loadSequence(callback);
|
||||
}, 1000 * 3);
|
||||
}
|
||||
};
|
||||
|
||||
this._account.getNextSequence(sequenceLoaded);
|
||||
};
|
||||
|
||||
TransactionManager.prototype._resubmit = function(ledgers, pending) {
|
||||
var self = this;
|
||||
var pending = pending ? [ pending ] : this._pending;
|
||||
var ledgers = Number(ledgers) || 0;
|
||||
|
||||
function resubmitTransaction(pending) {
|
||||
if (!pending || pending.finalized) {
|
||||
// Transaction has been finalized, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
var hashCached = pending.findId(self._pending._idCache);
|
||||
|
||||
self._remote._trace('transactionmanager: resubmit:', pending.tx_json);
|
||||
|
||||
if (hashCached) {
|
||||
return pending.emit('success', hashCached);
|
||||
}
|
||||
|
||||
while (self._pending.hasSequence(pending.tx_json.Sequence)) {
|
||||
//Sequence number has been consumed by another transaction
|
||||
pending.tx_json.Sequence += 1;
|
||||
self._remote._trace('transactionmanager: incrementing sequence:', pending.tx_json);
|
||||
}
|
||||
|
||||
self._request(pending);
|
||||
};
|
||||
|
||||
function resubmitTransactions() {
|
||||
;(function nextTransaction(i) {
|
||||
var transaction = pending[i];
|
||||
|
||||
if (!(transaction instanceof Transaction)) return;
|
||||
|
||||
transaction.once('submitted', function(m) {
|
||||
transaction.emit('resubmitted', m);
|
||||
|
||||
self._loadSequence();
|
||||
|
||||
if (++i < pending.length) nextTransaction(i);
|
||||
});
|
||||
|
||||
resubmitTransaction(transaction);
|
||||
})(0);
|
||||
};
|
||||
|
||||
this._waitLedgers(ledgers, resubmitTransactions);
|
||||
};
|
||||
|
||||
TransactionManager.prototype._waitLedgers = function(ledgers, callback) {
|
||||
if (ledgers < 1) return callback();
|
||||
|
||||
var self = this;
|
||||
var closes = 0;
|
||||
|
||||
function ledgerClosed() {
|
||||
if (++closes === ledgers) {
|
||||
self._remote.removeListener('ledger_closed', ledgerClosed);
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
this._remote.on('ledger_closed', ledgerClosed);
|
||||
};
|
||||
|
||||
TransactionManager.prototype._request = function(tx) {
|
||||
var self = this;
|
||||
var remote = this._remote;
|
||||
|
||||
if (tx.attempts > 10) {
|
||||
return tx.emit('error', new RippleError('tejAttemptsExceeded'));
|
||||
}
|
||||
|
||||
if (tx.attempts > 0 && !remote.local_signing) {
|
||||
// && tx.submittedTxnIDs.length != tx.attempts
|
||||
// ^^^ Above commented out intentionally
|
||||
// ^^^^ We might be a bit cleverer about allowing this in SOME cases, but
|
||||
// it's not really worth it, and would be prone to error. Use
|
||||
// `local_signing`
|
||||
|
||||
var message = ''
|
||||
+ 'It is not possible to resubmit transactions automatically safely without '
|
||||
+ 'synthesizing the transactionID locally. See `local_signing` config option';
|
||||
|
||||
return tx.emit('error', new RippleError('tejLocalSigningRequired', message));
|
||||
}
|
||||
|
||||
tx.emit('presubmit');
|
||||
|
||||
if (tx.finalized) return;
|
||||
|
||||
tx.submitIndex = this._remote._ledger_current_index;
|
||||
|
||||
if (tx.attempts === 0) {
|
||||
tx.initialSubmitIndex = tx.submitIndex;
|
||||
}
|
||||
|
||||
if (!tx._setLastLedger) {
|
||||
// Honor LastLedgerSequence set by user of API. If
|
||||
// left unset by API, bump LastLedgerSequence
|
||||
tx.tx_json.LastLedgerSequence = tx.submitIndex + 8;
|
||||
}
|
||||
|
||||
tx.lastLedgerSequence = tx.tx_json.LastLedgerSequence;
|
||||
|
||||
var submitRequest = remote.requestSubmit();
|
||||
|
||||
if (remote.local_signing) {
|
||||
tx.sign();
|
||||
// TODO: We are serializing twice, when we could/should be feeding the
|
||||
// tx_blob to `tx.hash()` which rebuilds it to sign it.
|
||||
submitRequest.tx_blob(tx.serialize().to_hex());
|
||||
|
||||
// ND: ecdsa produces a random `TxnSignature` field value, a component of
|
||||
// the hash. Attempting to identify a transaction via a hash synthesized
|
||||
// locally while using remote signing is inherently flawed.
|
||||
tx.addId(tx.hash());
|
||||
} else {
|
||||
// ND: `build_path` is completely ignored when doing local signing as
|
||||
// `Paths` is a component of the signed blob, the `tx_blob` is signed,
|
||||
// sealed and delivered, and the txn unmodified.
|
||||
// TODO: perhaps an exception should be raised if build_path is attempted
|
||||
// while local signing
|
||||
submitRequest.build_path(tx._build_path);
|
||||
submitRequest.secret(tx._secret);
|
||||
submitRequest.tx_json(tx.tx_json);
|
||||
}
|
||||
|
||||
remote._trace('transactionmanager: submit:', tx.tx_json);
|
||||
|
||||
function transactionProposed(message) {
|
||||
if (tx.finalized) return;
|
||||
// If server is honest, don't expect a final if rejected.
|
||||
message.rejected = tx.isRejected(message.engine_result_code);
|
||||
tx.emit('proposed', message);
|
||||
};
|
||||
|
||||
function transactionFailed(message) {
|
||||
if (tx.finalized) return;
|
||||
switch (message.engine_result) {
|
||||
case 'tefPAST_SEQ':
|
||||
self._resubmit(1, tx);
|
||||
break;
|
||||
default:
|
||||
tx.emit('error', message);
|
||||
}
|
||||
};
|
||||
|
||||
function transactionRetry(message) {
|
||||
if (tx.finalized) return;
|
||||
self._fillSequence(tx, function() {
|
||||
self._resubmit(1, tx);
|
||||
});
|
||||
};
|
||||
|
||||
function transactionFeeClaimed(message) {
|
||||
if (tx.finalized) return;
|
||||
tx.emit('error', message);
|
||||
};
|
||||
|
||||
function transactionFailedLocal(message) {
|
||||
if (tx.finalized) return;
|
||||
|
||||
var shouldAdjustFee = self._remote.local_fee
|
||||
&& (message.engine_result === 'telINSUF_FEE_P');
|
||||
|
||||
if (shouldAdjustFee) {
|
||||
self._resubmit(1, tx);
|
||||
} else {
|
||||
submissionError(message);
|
||||
}
|
||||
};
|
||||
|
||||
function submissionError(error) {
|
||||
// Finalized (e.g. aborted) transactions must stop all activity
|
||||
if (tx.finalized) return;
|
||||
|
||||
if (TransactionManager._isTooBusy(error)) {
|
||||
self._resubmit(1, tx);
|
||||
} else {
|
||||
self._nextSequence--;
|
||||
tx.emit('error', error);
|
||||
}
|
||||
};
|
||||
|
||||
function submitted(message) {
|
||||
// Finalized (e.g. aborted) transactions must stop all activity
|
||||
if (tx.finalized) return;
|
||||
|
||||
// ND: If for some unknown reason our hash wasn't computed correctly this is
|
||||
// an extra measure.
|
||||
if (message.tx_json && message.tx_json.hash) {
|
||||
tx.addId(message.tx_json.hash);
|
||||
}
|
||||
|
||||
message.result = message.engine_result || '';
|
||||
|
||||
tx.result = message;
|
||||
|
||||
remote._trace('transactionmanager: submit_response:', message);
|
||||
|
||||
tx.emit('submitted', message);
|
||||
|
||||
switch (message.result.slice(0, 3)) {
|
||||
case 'tes':
|
||||
transactionProposed(message);
|
||||
break;
|
||||
case 'tec':
|
||||
transactionFeeClaimed(message);
|
||||
break;
|
||||
case 'ter':
|
||||
transactionRetry(message);
|
||||
break;
|
||||
case 'tef':
|
||||
transactionFailed(message);
|
||||
break;
|
||||
case 'tel':
|
||||
transactionFailedLocal(message);
|
||||
break;
|
||||
default:
|
||||
// tem
|
||||
submissionError(message);
|
||||
}
|
||||
};
|
||||
|
||||
submitRequest.once('error', submitted);
|
||||
submitRequest.once('success', submitted);
|
||||
|
||||
if (tx._server) {
|
||||
submitRequest.server = tx._server;
|
||||
}
|
||||
|
||||
if (typeof tx._iff !== 'function') {
|
||||
submitTransaction();
|
||||
} else {
|
||||
return tx._iff(tx.summary(), function(err, proceed) {
|
||||
if (err || !proceed) {
|
||||
tx.emit('abort');
|
||||
} else {
|
||||
submitTransaction();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function requestTimeout() {
|
||||
// ND: What if the response is just slow and we get a response that
|
||||
// `submitted` above will cause to have concurrent resubmit logic streams?
|
||||
// It's simpler to just mute handlers and look out for finalized
|
||||
// `transaction` messages.
|
||||
|
||||
// ND: We should audit the code for other potential multiple resubmit
|
||||
// streams. Connection/reconnection could be one? That's why it's imperative
|
||||
// that ALL transactionIDs sent over network are tracked.
|
||||
|
||||
// Finalized (e.g. aborted) transactions must stop all activity
|
||||
if (tx.finalized) return;
|
||||
|
||||
tx.emit('timeout');
|
||||
|
||||
if (remote._connected) {
|
||||
remote._trace('transactionmanager: timeout:', tx.tx_json);
|
||||
self._resubmit(3, tx);
|
||||
}
|
||||
};
|
||||
|
||||
function submitTransaction() {
|
||||
if (tx.finalized) return;
|
||||
|
||||
submitRequest.timeout(self._submissionTimeout, requestTimeout);
|
||||
submitRequest.request();
|
||||
|
||||
tx.attempts++;
|
||||
tx.emit('postsubmit');
|
||||
};
|
||||
|
||||
return submitRequest;
|
||||
};
|
||||
|
||||
TransactionManager._isNoOp = function(transaction) {
|
||||
return (typeof transaction === 'object')
|
||||
&& (typeof transaction.tx_json === 'object')
|
||||
&& (transaction.tx_json.TransactionType === 'AccountSet')
|
||||
&& (transaction.tx_json.Flags === 0);
|
||||
};
|
||||
|
||||
TransactionManager._isRemoteError = function(error) {
|
||||
return (typeof error === 'object')
|
||||
&& (error.error === 'remoteError')
|
||||
&& (typeof error.remote === 'object');
|
||||
};
|
||||
|
||||
TransactionManager._isNotFound = function(error) {
|
||||
return TransactionManager._isRemoteError(error)
|
||||
&& /^(txnNotFound|transactionNotFound)$/.test(error.remote.error);
|
||||
};
|
||||
|
||||
TransactionManager._isTooBusy = function(error) {
|
||||
return TransactionManager._isRemoteError(error)
|
||||
&& (error.remote.error === 'tooBusy');
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point for TransactionManager submission
|
||||
*
|
||||
* @param {Object} tx
|
||||
*/
|
||||
|
||||
TransactionManager.prototype.submit = function(tx) {
|
||||
var self = this;
|
||||
var remote = this._remote;
|
||||
|
||||
// If sequence number is not yet known, defer until it is.
|
||||
if (typeof this._nextSequence === 'undefined') {
|
||||
function sequenceLoaded() {
|
||||
self.submit(tx);
|
||||
};
|
||||
this.once('sequence_loaded', sequenceLoaded);
|
||||
return;
|
||||
}
|
||||
|
||||
// Finalized (e.g. aborted) transactions must stop all activity
|
||||
if (tx.finalized) return;
|
||||
|
||||
function cleanup(message) {
|
||||
// ND: We can just remove this `tx` by identity
|
||||
self._pending.remove(tx);
|
||||
tx.emit('final', message);
|
||||
remote._trace('transactionmanager: finalize_transaction:', tx.tx_json);
|
||||
};
|
||||
|
||||
tx.once('cleanup', cleanup);
|
||||
|
||||
tx.on('save', function() {
|
||||
self.emit('save', tx);
|
||||
});
|
||||
|
||||
tx.once('error', function(message) {
|
||||
tx._errorHandler(message);
|
||||
});
|
||||
|
||||
tx.once('success', function(message) {
|
||||
tx._successHandler(message);
|
||||
});
|
||||
|
||||
tx.once('abort', function() {
|
||||
tx.emit('error', new RippleError('tejAbort', 'Transaction aborted'));
|
||||
});
|
||||
|
||||
if (typeof tx.tx_json.Sequence !== 'number') {
|
||||
tx.tx_json.Sequence = this._nextSequence++;
|
||||
}
|
||||
|
||||
tx.attempts = 0;
|
||||
|
||||
// Attach secret, associate transaction with a server, attach fee
|
||||
tx.complete();
|
||||
|
||||
var fee = Number(tx.tx_json.Fee);
|
||||
|
||||
if (!tx._secret && !tx.tx_json.TxnSignature) {
|
||||
tx.emit('error', new RippleError('tejSecretUnknown', 'Missing secret'));
|
||||
} else if (!remote.trusted && !remote.local_signing) {
|
||||
tx.emit('error', new RippleError('tejServerUntrusted', 'Attempt to give secret to untrusted server'));
|
||||
} else if (fee && fee > this._maxFee) {
|
||||
tx.emit('error', new RippleError('tejMaxFeeExceeded', 'Max fee exceeded'));
|
||||
} else {
|
||||
// ND: this is the ONLY place we put the tx into the queue. The
|
||||
// TransactionQueue queue is merely a list, so any mutations to tx._hash
|
||||
// will cause subsequent look ups (eg. inside 'transaction-outbound'
|
||||
// validated transaction clearing) to fail.
|
||||
this._pending.push(tx);
|
||||
this._request(tx);
|
||||
}
|
||||
};
|
||||
|
||||
exports.TransactionManager = TransactionManager;
|
||||
144
src/js/ripple/transactionqueue.js
Normal file
144
src/js/ripple/transactionqueue.js
Normal file
@@ -0,0 +1,144 @@
|
||||
|
||||
/**
|
||||
* Manager for pending transactions
|
||||
*/
|
||||
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
|
||||
function TransactionQueue() {
|
||||
var self = this;
|
||||
|
||||
this._queue = [ ];
|
||||
this._idCache = { };
|
||||
this._sequenceCache = { };
|
||||
this._save = void(0);
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.clearCache = function() {
|
||||
this._idCache = { };
|
||||
this._sequenceCache = { };
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.getMinLedger = function() {
|
||||
var minLedger = Infinity;
|
||||
|
||||
for (var i=0; i<this._queue.length; i++) {
|
||||
var submitIndex = this._queue[i].submitIndex;
|
||||
|
||||
if (typeof submitIndex !== 'number') {
|
||||
// If any pending transactions don't have a submit index,
|
||||
// return -1 for scanning all previous transactions
|
||||
minLedger = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (submitIndex < minLedger) {
|
||||
minLedger = submitIndex;
|
||||
}
|
||||
};
|
||||
|
||||
if (!isFinite(minLedger)) minLedger = -1;
|
||||
|
||||
if (minLedger !== -1) minLedger -= 1;
|
||||
|
||||
return minLedger;
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.save = function() {
|
||||
if (typeof this._save !== 'function') return;
|
||||
|
||||
this._save(this._queue.map(function(tx) {
|
||||
return {
|
||||
tx_json: tx.tx_json,
|
||||
submittedIDs: tx.submittedIDs
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Store received (validated) sequence
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.addReceivedSequence = function(sequence) {
|
||||
this._sequenceCache[sequence] = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Store received (validated) ID transaction
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.addReceivedId = function(id, transaction) {
|
||||
this._idCache[id] = transaction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get received (validated) transaction by ID
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.getReceived = function(id) {
|
||||
return this._idCache[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that sequence number has been consumed by a validated
|
||||
* transaction
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.hasSequence = function(sequence) {
|
||||
return this._sequenceCache[sequence] || false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a submitted transaction by ID. Transactions
|
||||
* may have multiple associated IDs.
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.getSubmission = function(id, callback) {
|
||||
var result = false;
|
||||
|
||||
for (var i=0, tx; tx=this._queue[i]; i++) {
|
||||
if (~tx.submittedIDs.indexOf(id)) {
|
||||
result = tx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a transaction from the queue
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.remove = function(tx) {
|
||||
// ND: We are just removing the Transaction by identity
|
||||
var i = this._queue.length;
|
||||
|
||||
while (i--) {
|
||||
if (this._queue[i] === tx) {
|
||||
this._queue.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._queue.length) {
|
||||
this.clearCache();
|
||||
}
|
||||
|
||||
this.save();
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.push = function(tx) {
|
||||
this._queue.push(tx);
|
||||
this.save();
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.forEach = function(fn) {
|
||||
this._queue.forEach(fn);
|
||||
};
|
||||
|
||||
TransactionQueue.prototype.length = function() {
|
||||
return this._queue.length;
|
||||
};
|
||||
|
||||
exports.TransactionQueue = TransactionQueue;
|
||||
@@ -1,11 +1,8 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var Base = require('./base').Base;
|
||||
|
||||
@@ -18,6 +15,8 @@ var Base = require('./base').Base;
|
||||
var UInt = function () {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._value = NaN;
|
||||
|
||||
this._update();
|
||||
};
|
||||
|
||||
UInt.json_rewrite = function (j, opts) {
|
||||
@@ -78,6 +77,15 @@ UInt.from_bn = function (j) {
|
||||
}
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_number = function (j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_number(j);
|
||||
}
|
||||
};
|
||||
|
||||
UInt.is_valid = function (j) {
|
||||
return this.from_json(j).is_valid();
|
||||
};
|
||||
@@ -90,6 +98,8 @@ UInt.prototype.clone = function () {
|
||||
UInt.prototype.copyTo = function (d) {
|
||||
d._value = this._value;
|
||||
|
||||
if ("function" === typeof d._update) d._update();
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
@@ -105,6 +115,20 @@ UInt.prototype.is_zero = function () {
|
||||
return this._value.equals(BigInteger.ZERO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update any derivative values.
|
||||
*
|
||||
* This allows subclasses to maintain caches of any data that they derive from
|
||||
* the main _value. For example, the Currency class keeps the currency type, the
|
||||
* currency code and other information about the currency cached.
|
||||
*
|
||||
* The reason for keeping this mechanism in this class is so every subclass can
|
||||
* call it whenever it modifies the internal state.
|
||||
*/
|
||||
UInt.prototype._update = function () {
|
||||
// Nothing to do by default. Subclasses will override this.
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt.prototype.parse_generic = function (j) {
|
||||
// Canonicalize and validate
|
||||
@@ -117,7 +141,7 @@ UInt.prototype.parse_generic = function (j) {
|
||||
case this.constructor.STR_ZERO:
|
||||
case this.constructor.ACCOUNT_ZERO:
|
||||
case this.constructor.HEX_ZERO:
|
||||
this._value = nbi();
|
||||
this._value = BigInteger.valueOf();
|
||||
break;
|
||||
|
||||
case "1":
|
||||
@@ -132,9 +156,6 @@ UInt.prototype.parse_generic = function (j) {
|
||||
if ('string' !== typeof j) {
|
||||
this._value = NaN;
|
||||
}
|
||||
else if (j[0] === "r") {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
}
|
||||
else if (this.constructor.width === j.length) {
|
||||
this._value = new BigInteger(utils.stringToArray(j), 256);
|
||||
}
|
||||
@@ -147,6 +168,8 @@ UInt.prototype.parse_generic = function (j) {
|
||||
}
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -158,6 +181,8 @@ UInt.prototype.parse_hex = function (j) {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -169,19 +194,25 @@ UInt.prototype.parse_bits = function (j) {
|
||||
this.parse_bytes(bytes);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
UInt.prototype.parse_bytes = function (j) {
|
||||
if (!Array.isArray(j) || j.length !== this.constructor.width) {
|
||||
this._value = NaN;
|
||||
this._value = NaN;
|
||||
} else {
|
||||
this._value = new BigInteger(j, 256);
|
||||
this._value = new BigInteger([0].concat(j), 256);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
UInt.prototype.parse_json = UInt.prototype.parse_hex;
|
||||
|
||||
UInt.prototype.parse_bn = function (j) {
|
||||
@@ -193,6 +224,23 @@ UInt.prototype.parse_bn = function (j) {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_number = function (j) {
|
||||
this._value = NaN;
|
||||
|
||||
if ("number" === typeof j &&
|
||||
j === +j &&
|
||||
j > 0) {
|
||||
// XXX Better, faster way to get BigInteger from JS int?
|
||||
this._value = new BigInteger(""+j);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -217,7 +265,6 @@ UInt.prototype.to_hex = function () {
|
||||
return null;
|
||||
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(bytes)).toUpperCase();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var utils = require('./utils');
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var UInt = require('./uint').UInt,
|
||||
Base = require('./base').Base;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var utils = require('./utils');
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var UInt = require('./uint').UInt,
|
||||
Base = require('./base').Base;
|
||||
var UInt = require('./uint').UInt;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
//
|
||||
// UInt160 support
|
||||
@@ -18,38 +15,70 @@ var UInt = require('./uint').UInt,
|
||||
var UInt160 = extend(function () {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._value = NaN;
|
||||
this._version_byte = void(0);
|
||||
|
||||
this._update();
|
||||
}, UInt);
|
||||
|
||||
UInt160.width = 20;
|
||||
UInt160.prototype = extend({}, UInt.prototype);
|
||||
UInt160.prototype.constructor = UInt160;
|
||||
|
||||
var ACCOUNT_ZERO = UInt160.ACCOUNT_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp";
|
||||
var ACCOUNT_ONE = UInt160.ACCOUNT_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji";
|
||||
var HEX_ZERO = UInt160.HEX_ZERO = "0000000000000000000000000000000000000000";
|
||||
var HEX_ONE = UInt160.HEX_ONE = "0000000000000000000000000000000000000001";
|
||||
var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
var ACCOUNT_ZERO = UInt160.ACCOUNT_ZERO = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp';
|
||||
var ACCOUNT_ONE = UInt160.ACCOUNT_ONE = 'rrrrrrrrrrrrrrrrrrrrBZbvji';
|
||||
var HEX_ZERO = UInt160.HEX_ZERO = '0000000000000000000000000000000000000000';
|
||||
var HEX_ONE = UInt160.HEX_ONE = '0000000000000000000000000000000000000001';
|
||||
var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
UInt160.prototype.set_version = function (j) {
|
||||
this._version_byte = j;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt160.prototype.get_version = function () {
|
||||
return this._version_byte;
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt160.prototype.parse_json = function (j) {
|
||||
// Canonicalize and validate
|
||||
if (config.accounts && j in config.accounts)
|
||||
if (config.accounts && j in config.accounts) {
|
||||
j = config.accounts[j].account;
|
||||
}
|
||||
|
||||
if ('number' === typeof j) {
|
||||
this._value = new BigInteger(String(j));
|
||||
if (typeof j === 'number' && !isNaN(j)) {
|
||||
// Allow raw numbers - DEPRECATED
|
||||
// This is used mostly by the test suite and is supported
|
||||
// as a legacy feature only. DO NOT RELY ON THIS BEHAVIOR.
|
||||
this._value = new BigInteger(String(j));
|
||||
this._version_byte = Base.VER_ACCOUNT_ID;
|
||||
} else if (typeof j !== 'string') {
|
||||
this._value = NaN;
|
||||
} else if (j[0] === 'r') {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
this._version_byte = Base.VER_ACCOUNT_ID;
|
||||
} else {
|
||||
this.parse_hex(j);
|
||||
}
|
||||
else if ('string' !== typeof j) {
|
||||
this._value = NaN;
|
||||
}
|
||||
else if (j[0] === "r") {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
}
|
||||
else {
|
||||
this._value = NaN;
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt160.prototype.parse_generic = function (j) {
|
||||
UInt.prototype.parse_generic.call(this, j);
|
||||
|
||||
if (isNaN(this._value)) {
|
||||
if ("string" === typeof j && j[0] === 'r') {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
}
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -57,15 +86,20 @@ UInt160.prototype.parse_json = function (j) {
|
||||
UInt160.prototype.to_json = function (opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (!(this._value instanceof BigInteger))
|
||||
return NaN;
|
||||
if (this._value instanceof BigInteger) {
|
||||
// If this value has a type, return a Base58 encoded string.
|
||||
if ("number" === typeof this._version_byte) {
|
||||
var output = Base.encode_check(this._version_byte, this.to_bytes());
|
||||
if (opts.gateways && output in opts.gateways) {
|
||||
output = opts.gateways[output];
|
||||
}
|
||||
|
||||
var output = Base.encode_check(Base.VER_ACCOUNT_ID, this.to_bytes());
|
||||
|
||||
if (opts.gateways && output in opts.gateways)
|
||||
output = opts.gateways[output];
|
||||
|
||||
return output;
|
||||
return output;
|
||||
} else {
|
||||
return this.to_hex();
|
||||
}
|
||||
}
|
||||
return NaN;
|
||||
};
|
||||
|
||||
exports.UInt160 = UInt160;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
|
||||
var sjcl = require('../../../build/sjcl');
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var utils = require('./utils');
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
var BigInteger = utils.jsbn.BigInteger;
|
||||
|
||||
var UInt = require('./uint').UInt,
|
||||
Base = require('./base').Base;
|
||||
|
||||
@@ -1,97 +1,98 @@
|
||||
Function.prototype.method = function(name,func) {
|
||||
Function.prototype.method = function(name, func) {
|
||||
this.prototype[name] = func;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
var filterErr = function(code, done) {
|
||||
function filterErr(code, done) {
|
||||
return function(e) {
|
||||
done(e.code !== code ? e : undefined);
|
||||
};
|
||||
done(e.code !== code ? e : void(0));
|
||||
};
|
||||
};
|
||||
|
||||
var throwErr = function(done) {
|
||||
function throwErr(done) {
|
||||
return function(e) {
|
||||
if (e)
|
||||
throw e;
|
||||
|
||||
done();
|
||||
};
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
done();
|
||||
};
|
||||
};
|
||||
|
||||
var trace = function(comment, func) {
|
||||
|
||||
function trace(comment, func) {
|
||||
return function() {
|
||||
console.log("%s: %s", trace, arguments.toString);
|
||||
func(arguments);
|
||||
};
|
||||
console.log("%s: %s", trace, arguments.toString);
|
||||
func(arguments);
|
||||
};
|
||||
};
|
||||
|
||||
var arraySet = function (count, value) {
|
||||
var i, a = new Array(count);
|
||||
function arraySet(count, value) {
|
||||
var a = new Array(count);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
for (var i=0; i<count; i++) {
|
||||
a[i] = value;
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
var hexToString = function (h) {
|
||||
var a = [];
|
||||
var i = 0;
|
||||
function hexToString(h) {
|
||||
var a = [];
|
||||
var i = 0;
|
||||
|
||||
if (h.length % 2) {
|
||||
a.push(String.fromCharCode(parseInt(h.substring(0, 1), 16)));
|
||||
i = 1;
|
||||
}
|
||||
|
||||
for (; i != h.length; i += 2) {
|
||||
for (; i<h.length; i+=2) {
|
||||
a.push(String.fromCharCode(parseInt(h.substring(i, i+2), 16)));
|
||||
}
|
||||
|
||||
return a.join("");
|
||||
|
||||
return a.join('');
|
||||
};
|
||||
|
||||
var stringToHex = function (s) {
|
||||
return Array.prototype.map.call(s, function (c) {
|
||||
var b = c.charCodeAt(0);
|
||||
|
||||
return b < 16 ? "0" + b.toString(16) : b.toString(16);
|
||||
}).join("");
|
||||
function stringToHex(s) {
|
||||
var result = '';
|
||||
for (var i=0; i<s.length; i++) {
|
||||
var b = s.charCodeAt(i);
|
||||
result += b < 16 ? '0' + b.toString(16) : b.toString(16);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
var stringToArray = function (s) {
|
||||
function stringToArray(s) {
|
||||
var a = new Array(s.length);
|
||||
var i;
|
||||
|
||||
for (i = 0; i != a.length; i += 1)
|
||||
for (var i=0; i<a.length; i+=1) {
|
||||
a[i] = s.charCodeAt(i);
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
var hexToArray = function (h) {
|
||||
function hexToArray(h) {
|
||||
return stringToArray(hexToString(h));
|
||||
}
|
||||
};
|
||||
|
||||
var chunkString = function (str, n, leftAlign) {
|
||||
function chunkString(str, n, leftAlign) {
|
||||
var ret = [];
|
||||
var i=0, len=str.length;
|
||||
|
||||
if (leftAlign) {
|
||||
i = str.length % n;
|
||||
if (i) ret.push(str.slice(0, i));
|
||||
if (i) {
|
||||
ret.push(str.slice(0, i));
|
||||
}
|
||||
}
|
||||
for(; i < len; i += n) {
|
||||
ret.push(str.slice(i, n+i));
|
||||
|
||||
for(; i<len; i+=n) {
|
||||
ret.push(str.slice(i, n + i));
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
var logObject = function (msg, obj) {
|
||||
console.log(msg, JSON.stringify(obj, undefined, 2));
|
||||
};
|
||||
|
||||
var assert = function (assertion, msg) {
|
||||
function assert(assertion, msg) {
|
||||
if (!assertion) {
|
||||
throw new Error("Assertion failed" + (msg ? ": "+msg : "."));
|
||||
}
|
||||
@@ -100,15 +101,18 @@ var assert = function (assertion, msg) {
|
||||
/**
|
||||
* Return unique values in array.
|
||||
*/
|
||||
var arrayUnique = function (arr) {
|
||||
function arrayUnique(arr) {
|
||||
var u = {}, a = [];
|
||||
for (var i = 0, l = arr.length; i < l; ++i){
|
||||
if (u.hasOwnProperty(arr[i])) {
|
||||
|
||||
for (var i=0, l=arr.length; i<l; i++){
|
||||
var k = arr[i];
|
||||
if (u[k]) {
|
||||
continue;
|
||||
}
|
||||
a.push(arr[i]);
|
||||
u[arr[i]] = 1;
|
||||
a.push(k);
|
||||
u[k] = true;
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
@@ -117,10 +121,23 @@ var arrayUnique = function (arr) {
|
||||
*
|
||||
* JavaScript timestamps are unix epoch in milliseconds.
|
||||
*/
|
||||
var toTimestamp = function (rpepoch) {
|
||||
function toTimestamp(rpepoch) {
|
||||
return (rpepoch + 0x386D4380) * 1000;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a JavaScript timestamp or Date to a Ripple epoch.
|
||||
*
|
||||
* JavaScript timestamps are unix epoch in milliseconds.
|
||||
*/
|
||||
function fromTimestamp(rpepoch) {
|
||||
if (rpepoch instanceof Date) {
|
||||
rpepoch = rpepoch.getTime();
|
||||
}
|
||||
|
||||
return Math.round(rpepoch/1000) - 0x386D4380;
|
||||
};
|
||||
|
||||
exports.trace = trace;
|
||||
exports.arraySet = arraySet;
|
||||
exports.hexToString = hexToString;
|
||||
@@ -128,9 +145,14 @@ exports.hexToArray = hexToArray;
|
||||
exports.stringToArray = stringToArray;
|
||||
exports.stringToHex = stringToHex;
|
||||
exports.chunkString = chunkString;
|
||||
exports.logObject = logObject;
|
||||
exports.assert = assert;
|
||||
exports.arrayUnique = arrayUnique;
|
||||
exports.toTimestamp = toTimestamp;
|
||||
exports.fromTimestamp = fromTimestamp;
|
||||
|
||||
// Going up three levels is needed to escape the src-cov folder used for the
|
||||
// test coverage stuff.
|
||||
exports.sjcl = require('../../../build/sjcl');
|
||||
exports.jsbn = require('../../../src/js/jsbn/jsbn');
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
var exports = module.exports = require('./utils.js');
|
||||
|
||||
// We override this function for browsers, because they print objects nicer
|
||||
// natively than JSON.stringify can.
|
||||
exports.logObject = function (msg, obj) {
|
||||
if (/MSIE/.test(navigator.userAgent)) {
|
||||
console.log(msg, JSON.stringify(obj));
|
||||
} else {
|
||||
console.log(msg, "", obj);
|
||||
}
|
||||
};
|
||||
@@ -6,11 +6,9 @@ sjcl.bn.prototype.divRem = function (that) {
|
||||
var thisa = this.abs(), thata = that.abs(), quot = new this._class(0),
|
||||
ci = 0;
|
||||
if (!thisa.greaterEquals(thata)) {
|
||||
this.initWith(0);
|
||||
return this;
|
||||
return [new sjcl.bn(0), this.copy()];
|
||||
} else if (thisa.equals(thata)) {
|
||||
this.initWith(1);
|
||||
return this;
|
||||
return [new sjcl.bn(1), new sjcl.bn(0)];
|
||||
}
|
||||
|
||||
for (; thisa.greaterEquals(thata); ci++) {
|
||||
@@ -45,8 +43,8 @@ sjcl.bn.prototype.div = function (that) {
|
||||
};
|
||||
|
||||
sjcl.bn.prototype.sign = function () {
|
||||
return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1;
|
||||
};
|
||||
return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1;
|
||||
};
|
||||
|
||||
/** -this */
|
||||
sjcl.bn.prototype.neg = function () {
|
||||
@@ -59,3 +57,89 @@ sjcl.bn.prototype.abs = function () {
|
||||
return this.neg();
|
||||
} else return this;
|
||||
};
|
||||
|
||||
/** this >> that */
|
||||
sjcl.bn.prototype.shiftRight = function (that) {
|
||||
if ("number" !== typeof that) {
|
||||
throw new Error("shiftRight expects a number");
|
||||
}
|
||||
|
||||
that = +that;
|
||||
|
||||
if (that < 0) {
|
||||
return this.shiftLeft(that);
|
||||
}
|
||||
|
||||
var a = new sjcl.bn(this);
|
||||
|
||||
while (that >= this.radix) {
|
||||
a.limbs.shift();
|
||||
that -= this.radix;
|
||||
}
|
||||
|
||||
while (that--) {
|
||||
a.halveM();
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
/** this >> that */
|
||||
sjcl.bn.prototype.shiftLeft = function (that) {
|
||||
if ("number" !== typeof that) {
|
||||
throw new Error("shiftLeft expects a number");
|
||||
}
|
||||
|
||||
that = +that;
|
||||
|
||||
if (that < 0) {
|
||||
return this.shiftRight(that);
|
||||
}
|
||||
|
||||
var a = new sjcl.bn(this);
|
||||
|
||||
while (that >= this.radix) {
|
||||
a.limbs.unshift(0);
|
||||
that -= this.radix;
|
||||
}
|
||||
|
||||
while (that--) {
|
||||
a.doubleM();
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
/** (int)this */
|
||||
// NOTE Truncates to 32-bit integer
|
||||
sjcl.bn.prototype.toNumber = function () {
|
||||
return this.limbs[0] | 0;
|
||||
};
|
||||
|
||||
/** find n-th bit, 0 = LSB */
|
||||
sjcl.bn.prototype.testBit = function (bitIndex) {
|
||||
var limbIndex = Math.floor(bitIndex / this.radix);
|
||||
var bitIndexInLimb = bitIndex % this.radix;
|
||||
|
||||
if (limbIndex >= this.limbs.length) return 0;
|
||||
|
||||
return (this.limbs[limbIndex] >>> bitIndexInLimb) & 1;
|
||||
};
|
||||
|
||||
/** set n-th bit, 0 = LSB */
|
||||
sjcl.bn.prototype.setBitM = function (bitIndex) {
|
||||
var limbIndex = Math.floor(bitIndex / this.radix);
|
||||
var bitIndexInLimb = bitIndex % this.radix;
|
||||
|
||||
while (limbIndex >= this.limbs.length) this.limbs.push(0);
|
||||
|
||||
this.limbs[limbIndex] |= 1 << bitIndexInLimb;
|
||||
|
||||
this.cnormalize();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
sjcl.bn.prototype.modInt = function (n) {
|
||||
return this.toNumber() % n;
|
||||
};
|
||||
|
||||
45
src/js/sjcl-custom/sjcl-jacobi.js
Normal file
45
src/js/sjcl-custom/sjcl-jacobi.js
Normal file
@@ -0,0 +1,45 @@
|
||||
sjcl.bn.prototype.jacobi = function (that) {
|
||||
var a = this;
|
||||
that = new sjcl.bn(that);
|
||||
|
||||
if (that.sign() === -1) return;
|
||||
|
||||
// 1. If a = 0 then return(0).
|
||||
if (a.equals(0)) { return 0; }
|
||||
|
||||
// 2. If a = 1 then return(1).
|
||||
if (a.equals(1)) { return 1; }
|
||||
|
||||
var s = 0;
|
||||
|
||||
// 3. Write a = 2^e * a1, where a1 is odd.
|
||||
var e = 0;
|
||||
while (!a.testBit(e)) e++;
|
||||
var a1 = a.shiftRight(e);
|
||||
|
||||
// 4. If e is even then set s ← 1.
|
||||
if ((e & 1) === 0) {
|
||||
s = 1;
|
||||
} else {
|
||||
var residue = that.modInt(8);
|
||||
|
||||
if (residue === 1 || residue === 7) {
|
||||
// Otherwise set s ← 1 if n ≡ 1 or 7 (mod 8)
|
||||
s = 1;
|
||||
} else if (residue === 3 || residue === 5) {
|
||||
// Or set s ← −1 if n ≡ 3 or 5 (mod 8).
|
||||
s = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If n ≡ 3 (mod 4) and a1 ≡ 3 (mod 4) then set s ← −s.
|
||||
if (that.modInt(4) === 3 && a1.modInt(4) === 3) {
|
||||
s = -s;
|
||||
}
|
||||
|
||||
if (a1.equals(1)) {
|
||||
return s;
|
||||
} else {
|
||||
return s * that.mod(a1).jacobi(a1);
|
||||
}
|
||||
};
|
||||
156
src/js/sjcl-custom/sjcl-montgomery.js
Normal file
156
src/js/sjcl-custom/sjcl-montgomery.js
Normal file
@@ -0,0 +1,156 @@
|
||||
sjcl.bn.prototype.invDigit = function ()
|
||||
{
|
||||
var radixMod = 1 + this.radixMask;
|
||||
|
||||
if (this.limbs.length < 1) return 0;
|
||||
var x = this.limbs[0];
|
||||
if ((x&1) == 0) return 0;
|
||||
var y = x&3; // y == 1/x mod 2^2
|
||||
y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
|
||||
y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
|
||||
y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
|
||||
// last step - calculate inverse mod DV directly;
|
||||
// assumes 16 < radixMod <= 32 and assumes ability to handle 48-bit ints
|
||||
y = (y*(2-x*y%radixMod))%radixMod; // y == 1/x mod 2^dbits
|
||||
// we really want the negative inverse, and -DV < y < DV
|
||||
return (y>0)?radixMod-y:-y;
|
||||
};
|
||||
|
||||
// returns bit length of the integer x
|
||||
function nbits(x) {
|
||||
var r = 1, t;
|
||||
if((t=x>>>16) != 0) { x = t; r += 16; }
|
||||
if((t=x>>8) != 0) { x = t; r += 8; }
|
||||
if((t=x>>4) != 0) { x = t; r += 4; }
|
||||
if((t=x>>2) != 0) { x = t; r += 2; }
|
||||
if((t=x>>1) != 0) { x = t; r += 1; }
|
||||
return r;
|
||||
}
|
||||
|
||||
// JSBN-style add and multiply for SJCL w/ 24 bit radix
|
||||
sjcl.bn.prototype.am = function (i,x,w,j,c,n) {
|
||||
var xl = x&0xfff, xh = x>>12;
|
||||
while (--n >= 0) {
|
||||
var l = this.limbs[i]&0xfff;
|
||||
var h = this.limbs[i++]>>12;
|
||||
var m = xh*l+h*xl;
|
||||
l = xl*l+((m&0xfff)<<12)+w.limbs[j]+c;
|
||||
c = (l>>24)+(m>>12)+xh*h;
|
||||
w.limbs[j++] = l&0xffffff;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
var Montgomery = function (m)
|
||||
{
|
||||
this.m = m;
|
||||
this.mt = m.limbs.length;
|
||||
this.mt2 = this.mt * 2;
|
||||
this.mp = m.invDigit();
|
||||
this.mpl = this.mp&0x7fff;
|
||||
this.mph = this.mp>>15;
|
||||
this.um = (1<<(m.radix-15))-1;
|
||||
};
|
||||
|
||||
Montgomery.prototype.reduce = function (x)
|
||||
{
|
||||
var radixMod = x.radixMask + 1;
|
||||
while (x.limbs.length <= this.mt2) // pad x so am has enough room later
|
||||
x.limbs[x.limbs.length] = 0;
|
||||
for (var i = 0; i < this.mt; ++i) {
|
||||
// faster way of calculating u0 = x[i]*mp mod 2^radix
|
||||
var j = x.limbs[i]&0x7fff;
|
||||
var u0 = (j*this.mpl+(((j*this.mph+(x.limbs[i]>>15)*this.mpl)&this.um)<<15))&x.radixMask;
|
||||
// use am to combine the multiply-shift-add into one call
|
||||
j = i+this.mt;
|
||||
x.limbs[j] += this.m.am(0,u0,x,i,0,this.mt);
|
||||
// propagate carry
|
||||
while (x.limbs[j] >= radixMod) { x.limbs[j] -= radixMod; x.limbs[++j]++; }
|
||||
}
|
||||
x.trim();
|
||||
x = x.shiftRight(this.mt * this.m.radix);
|
||||
if (x.greaterEquals(this.m)) x = x.sub(this.m);
|
||||
return x.trim().normalize().reduce();
|
||||
};
|
||||
|
||||
Montgomery.prototype.square = function (x)
|
||||
{
|
||||
return this.reduce(x.square());
|
||||
};
|
||||
|
||||
Montgomery.prototype.multiply = function (x, y)
|
||||
{
|
||||
return this.reduce(x.mul(y));
|
||||
};
|
||||
|
||||
Montgomery.prototype.convert = function (x)
|
||||
{
|
||||
return x.abs().shiftLeft(this.mt * this.m.radix).mod(this.m);
|
||||
};
|
||||
|
||||
Montgomery.prototype.revert = function (x)
|
||||
{
|
||||
return this.reduce(x.copy());
|
||||
};
|
||||
|
||||
sjcl.bn.prototype.powermodMontgomery = function (e, m)
|
||||
{
|
||||
var i = e.bitLength(), k, r = new this._class(1);
|
||||
|
||||
if (i <= 0) return r;
|
||||
else if (i < 18) k = 1;
|
||||
else if (i < 48) k = 3;
|
||||
else if (i < 144) k = 4;
|
||||
else if (i < 768) k = 5;
|
||||
else k = 6;
|
||||
|
||||
if (i < 8 || !m.testBit(0)) {
|
||||
// For small exponents and even moduli, use a simple square-and-multiply
|
||||
// algorithm.
|
||||
return this.powermod(e, m);
|
||||
}
|
||||
|
||||
var z = new Montgomery(m);
|
||||
|
||||
e.trim().normalize();
|
||||
|
||||
// precomputation
|
||||
var g = new Array(), n = 3, k1 = k-1, km = (1<<k)-1;
|
||||
g[1] = z.convert(this);
|
||||
if (k > 1) {
|
||||
var g2 = z.square(g[1]);
|
||||
|
||||
while (n <= km) {
|
||||
g[n] = z.multiply(g2, g[n-2]);
|
||||
n += 2;
|
||||
}
|
||||
}
|
||||
|
||||
var j = e.limbs.length-1, w, is1 = true, r2 = new this._class(), t;
|
||||
i = nbits(e.limbs[j])-1;
|
||||
while (j >= 0) {
|
||||
if (i >= k1) w = (e.limbs[j]>>(i-k1))&km;
|
||||
else {
|
||||
w = (e.limbs[j]&((1<<(i+1))-1))<<(k1-i);
|
||||
if (j > 0) w |= e.limbs[j-1]>>(this.radix+i-k1);
|
||||
}
|
||||
|
||||
n = k;
|
||||
while ((w&1) == 0) { w >>= 1; --n; }
|
||||
if ((i -= n) < 0) { i += this.radix; --j; }
|
||||
if (is1) { // ret == 1, don't bother squaring or multiplying it
|
||||
r = g[w].copy();
|
||||
is1 = false;
|
||||
} else {
|
||||
while (n > 1) { r2 = z.square(r); r = z.square(r2); n -= 2; }
|
||||
if (n > 0) r2 = z.square(r); else { t = r; r = r2; r2 = t; }
|
||||
r = z.multiply(r2,g[w]);
|
||||
}
|
||||
|
||||
while (j >= 0 && (e.limbs[j]&(1<<i)) == 0) {
|
||||
r2 = z.square(r); t = r; r = r2; r2 = t;
|
||||
if (--i < 0) { i = this.radix-1; --j; }
|
||||
}
|
||||
}
|
||||
return z.revert(r);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// ----- for secp256k1 ------
|
||||
|
||||
// Overwrite NIST-P256 with secp256k1 so we're on even footing
|
||||
// Overwrite NIST-P256 with secp256k1
|
||||
sjcl.ecc.curves.c256 = new sjcl.ecc.curve(
|
||||
sjcl.bn.pseudoMersennePrime(256, [[0,-1],[4,-1],[6,-1],[7,-1],[8,-1],[9,-1],[32,-1]]),
|
||||
"0x14551231950b75fc4402da1722fc9baee",
|
||||
|
||||
@@ -17,7 +17,7 @@ sjcl.ecc.ecdsa.publicKey.prototype = {
|
||||
l = R.bitLength(),
|
||||
r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)),
|
||||
s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)),
|
||||
sInv = s.modInverse(R),
|
||||
sInv = s.inverseMod(R),
|
||||
hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R),
|
||||
hA = r.mul(sInv).mod(R),
|
||||
r2 = this._curve.G.mult2(hG, hA, this._point).x;
|
||||
|
||||
@@ -1,373 +1,434 @@
|
||||
var buster = require("buster");
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var BigInteger = require('../src/js/jsbn/jsbn').BigInteger;
|
||||
var Amount = utils.load_module('amount').Amount;
|
||||
var UInt160 = utils.load_module('uint160').UInt160;
|
||||
var config = utils.get_config();
|
||||
|
||||
var jsbn = require('../src/js/ripple/jsbn');
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
|
||||
var Amount = require("../src/js/ripple/amount").Amount;
|
||||
var UInt160 = require("../src/js/ripple/uint160").UInt160;
|
||||
|
||||
try {
|
||||
var conf = require('./config');
|
||||
} catch(exception) {
|
||||
var conf = require('./config-example');
|
||||
}
|
||||
|
||||
var config = require('../src/js/ripple/config').load(conf);
|
||||
|
||||
// XXX Add test cases that push XRP vs non-XRP behavior.
|
||||
|
||||
buster.testCase("Amount", {
|
||||
"negatives" : {
|
||||
"Number 1" : function () {
|
||||
buster.assert.equals("-1", Amount.from_human('0').add(Amount.from_human('-1')).to_human());
|
||||
},
|
||||
},
|
||||
|
||||
"from_number" : {
|
||||
"Number 1" : function () {
|
||||
buster.assert.equals("1/1/rrrrrrrrrrrrrrrrrrrrBZbvji", Amount.from_number(1).to_text_full());
|
||||
},
|
||||
},
|
||||
|
||||
"UInt160" : {
|
||||
"Parse 0" : function () {
|
||||
buster.assert.equals(nbi(), UInt160.from_generic("0")._value);
|
||||
},
|
||||
"Parse 0 export" : function () {
|
||||
buster.assert.equals(UInt160.ACCOUNT_ZERO, UInt160.from_generic("0").to_json());
|
||||
},
|
||||
"Parse 1" : function () {
|
||||
buster.assert.equals(new BigInteger([1]), UInt160.from_generic("1")._value);
|
||||
},
|
||||
"Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export" : function () {
|
||||
buster.assert.equals(UInt160.ACCOUNT_ZERO, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json());
|
||||
},
|
||||
"Parse rrrrrrrrrrrrrrrrrrrrBZbvji export" : function () {
|
||||
buster.assert.equals(UInt160.ACCOUNT_ONE, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json());
|
||||
},
|
||||
"Parse mtgox export" : function () {
|
||||
buster.assert.equals(config.accounts["mtgox"].account, UInt160.from_json("mtgox").to_json());
|
||||
},
|
||||
|
||||
"is_valid('rrrrrrrrrrrrrrrrrrrrrhoLvTp')" : function () {
|
||||
buster.assert(UInt160.is_valid("rrrrrrrrrrrrrrrrrrrrrhoLvTp"));
|
||||
},
|
||||
|
||||
"!is_valid('rrrrrrrrrrrrrrrrrrrrrhoLvT')" : function () {
|
||||
buster.refute(UInt160.is_valid("rrrrrrrrrrrrrrrrrrrrrhoLvT"));
|
||||
},
|
||||
},
|
||||
|
||||
"Amount parsing" : {
|
||||
"Parse 800/USD/mtgox" : function () {
|
||||
buster.assert.equals("800/USD/"+config.accounts["mtgox"].account, Amount.from_json("800/USD/mtgox").to_text_full());
|
||||
},
|
||||
"Parse native 0" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("0").to_text_full());
|
||||
},
|
||||
"Parse native 0.0" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("0.0").to_text_full());
|
||||
},
|
||||
"Parse native -0" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("-0").to_text_full());
|
||||
},
|
||||
"Parse native -0.0" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("-0.0").to_text_full());
|
||||
},
|
||||
"Parse native 1000" : function () {
|
||||
buster.assert.equals("0.001/XRP", Amount.from_json("1000").to_text_full());
|
||||
},
|
||||
"Parse native 12.3" : function () {
|
||||
buster.assert.equals("12.3/XRP", Amount.from_json("12.3").to_text_full());
|
||||
},
|
||||
"Parse native -12.3" : function () {
|
||||
buster.assert.equals("-12.3/XRP", Amount.from_json("-12.3").to_text_full());
|
||||
},
|
||||
"Parse 123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () {
|
||||
buster.assert.equals("123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").to_text_full());
|
||||
},
|
||||
"Parse 12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () {
|
||||
buster.assert.equals("12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").to_text_full());
|
||||
},
|
||||
"Parse 12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () {
|
||||
buster.assert.equals("12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").to_text_full());
|
||||
},
|
||||
"Parse 1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () {
|
||||
buster.assert.equals("1.23/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").to_text_full());
|
||||
},
|
||||
"Parse -0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () {
|
||||
buster.assert.equals("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").to_text_full());
|
||||
},
|
||||
"Parse -0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh" : function () {
|
||||
buster.assert.equals("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").to_text_full());
|
||||
},
|
||||
},
|
||||
"Amount operations" : {
|
||||
"Negate native 123" : function () {
|
||||
buster.assert.equals("-0.000123/XRP", Amount.from_json("123").negate().to_text_full());
|
||||
},
|
||||
"Negate native -123" : function () {
|
||||
buster.assert.equals("0.000123/XRP", Amount.from_json("-123").negate().to_text_full());
|
||||
},
|
||||
"Negate non-native 123" : function () {
|
||||
buster.assert.equals("-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").negate().to_text_full());
|
||||
},
|
||||
"Negate non-native -123" : function () {
|
||||
buster.assert.equals("123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").negate().to_text_full());
|
||||
},
|
||||
"Clone non-native -123" : function () {
|
||||
buster.assert.equals("-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").clone().to_text_full());
|
||||
},
|
||||
"Add XRP to XRP" : function () {
|
||||
buster.assert.equals("0.0002/XRP", Amount.from_json("150").add(Amount.from_json("50")).to_text_full());
|
||||
},
|
||||
"Add USD to USD" : function () {
|
||||
buster.assert.equals("200.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").add(Amount.from_json("50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply 0 XRP with 0 XRP" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("0").multiply(Amount.from_json("0")).to_text_full());
|
||||
},
|
||||
"Multiply 0 USD with 0 XRP" : function () {
|
||||
buster.assert.equals("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("0")).to_text_full());
|
||||
},
|
||||
"Multiply 0 XRP with 0 USD" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("0").multiply(Amount.from_json("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply 1 XRP with 0 XRP" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("1").multiply(Amount.from_json("0")).to_text_full());
|
||||
},
|
||||
"Multiply 1 USD with 0 XRP" : function () {
|
||||
buster.assert.equals("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("0")).to_text_full());
|
||||
},
|
||||
"Multiply 1 XRP with 0 USD" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("1").multiply(Amount.from_json("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply 0 XRP with 1 XRP" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("0").multiply(Amount.from_json("1")).to_text_full());
|
||||
},
|
||||
"Multiply 0 USD with 1 XRP" : function () {
|
||||
buster.assert.equals("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("1")).to_text_full());
|
||||
},
|
||||
"Multiply 0 XRP with 1 USD" : function () {
|
||||
buster.assert.equals("0/XRP", Amount.from_json("0").multiply(Amount.from_json("1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply XRP with USD" : function () {
|
||||
buster.assert.equals("2000/XRP", Amount.from_json("200").multiply(Amount.from_json("10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply XRP with USD" : function () {
|
||||
buster.assert.equals("0.2/XRP", Amount.from_json("20000").multiply(Amount.from_json("10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply XRP with USD" : function () {
|
||||
buster.assert.equals("20/XRP", Amount.from_json("2000000").multiply(Amount.from_json("10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply XRP with USD, neg" : function () {
|
||||
buster.assert.equals("-0.002/XRP", Amount.from_json("200").multiply(Amount.from_json("-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply XRP with USD, neg, frac" : function () {
|
||||
buster.assert.equals("-0.222/XRP", Amount.from_json("-6000").multiply(Amount.from_json("37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply USD with USD" : function () {
|
||||
buster.assert.equals("0.020000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply USD with USD" : function () {
|
||||
buster.assert.equals("200000000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply EUR with USD, result < 1" : function () {
|
||||
buster.assert.equals("100000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply EUR with USD, neg" : function () {
|
||||
buster.assert.equals("-48000000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply EUR with USD, neg, <1" : function () {
|
||||
buster.assert.equals("-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Multiply EUR with XRP, factor < 1" : function () {
|
||||
buster.assert.equals("100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("2000")).to_text_full());
|
||||
},
|
||||
"Multiply EUR with XRP, neg" : function () {
|
||||
buster.assert.equals("-500/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("5")).to_text_full());
|
||||
},
|
||||
"Multiply EUR with XRP, neg, <1" : function () {
|
||||
buster.assert.equals("-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").multiply(Amount.from_json("2000")).to_text_full());
|
||||
},
|
||||
"Multiply XRP with XRP" : function () {
|
||||
buster.assert.equals("0.0001/XRP", Amount.from_json("10").multiply(Amount.from_json("10")).to_text_full());
|
||||
},
|
||||
"Divide XRP by USD" : function () {
|
||||
buster.assert.equals("0.00002/XRP", Amount.from_json("200").divide(Amount.from_json("10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide XRP by USD" : function () {
|
||||
buster.assert.equals("0.002/XRP", Amount.from_json("20000").divide(Amount.from_json("10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide XRP by USD" : function () {
|
||||
buster.assert.equals("0.2/XRP", Amount.from_json("2000000").divide(Amount.from_json("10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide XRP by USD, neg" : function () {
|
||||
buster.assert.equals("-0.00002/XRP", Amount.from_json("200").divide(Amount.from_json("-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide XRP by USD, neg, frac" : function () {
|
||||
buster.assert.equals("-0.000162/XRP", Amount.from_json("-6000").divide(Amount.from_json("37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide USD by USD" : function () {
|
||||
buster.assert.equals("200/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide USD by USD, fractional" : function () {
|
||||
buster.assert.equals("57142.85714285714/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("35/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide USD by USD" : function () {
|
||||
buster.assert.equals("20/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide EUR by USD, factor < 1" : function () {
|
||||
buster.assert.equals("0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide EUR by USD, neg" : function () {
|
||||
buster.assert.equals("-12/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide EUR by USD, neg, <1" : function () {
|
||||
buster.assert.equals("-0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh")).to_text_full());
|
||||
},
|
||||
"Divide EUR by XRP, result < 1" : function () {
|
||||
buster.assert.equals("0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("2000")).to_text_full());
|
||||
},
|
||||
"Divide EUR by XRP, neg" : function () {
|
||||
buster.assert.equals("-20/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("5")).to_text_full());
|
||||
},
|
||||
"Divide EUR by XRP, neg, <1" : function () {
|
||||
buster.assert.equals("-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", Amount.from_json("-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh").divide(Amount.from_json("2000")).to_text_full());
|
||||
}
|
||||
},
|
||||
"Amount comparisons" : {
|
||||
"0 USD == 0 USD" : function () {
|
||||
var a = Amount.from_json("0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.assert(a.equals(b));
|
||||
buster.refute(a.not_equals_why(b));
|
||||
},
|
||||
"0 USD == -0 USD" : function () {
|
||||
var a = Amount.from_json("0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("-0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.assert(a.equals(b));
|
||||
buster.refute(a.not_equals_why(b));
|
||||
},
|
||||
"0 XRP == 0 XRP" : function () {
|
||||
var a = Amount.from_json("0");
|
||||
var b = Amount.from_json("0.0");
|
||||
buster.assert(a.equals(b));
|
||||
buster.refute(a.not_equals_why(b));
|
||||
},
|
||||
"0 XRP == -0 XRP" : function () {
|
||||
var a = Amount.from_json("0");
|
||||
var b = Amount.from_json("-0");
|
||||
buster.assert(a.equals(b));
|
||||
buster.refute(a.not_equals_why(b));
|
||||
},
|
||||
"10 USD == 10 USD" : function () {
|
||||
var a = Amount.from_json("10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.assert(a.equals(b));
|
||||
buster.refute(a.not_equals_why(b));
|
||||
},
|
||||
"123.4567 USD == 123.4567 USD" : function () {
|
||||
var a = Amount.from_json("123.4567/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("123.4567/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.assert(a.equals(b));
|
||||
buster.refute(a.not_equals_why(b));
|
||||
},
|
||||
"10 XRP == 10 XRP" : function () {
|
||||
var a = Amount.from_json("10");
|
||||
var b = Amount.from_json("10");
|
||||
buster.assert(a.equals(b));
|
||||
buster.refute(a.not_equals_why(b));
|
||||
},
|
||||
"1.1 XRP == 1.1 XRP" : function () {
|
||||
var a = Amount.from_json("1.1");
|
||||
var b = Amount.from_json("11.0").ratio_human(10);
|
||||
buster.assert(a.equals(b));
|
||||
buster.refute(a.not_equals_why(b));
|
||||
},
|
||||
"0 USD == 0 USD (ignore issuer)" : function () {
|
||||
var a = Amount.from_json("0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("0/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP");
|
||||
buster.assert(a.equals(b, true));
|
||||
buster.refute(a.not_equals_why(b, true));
|
||||
},
|
||||
"1.1 USD == 1.10 USD (ignore issuer)" : function () {
|
||||
var a = Amount.from_json("1.1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("1.10/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP");
|
||||
buster.assert(a.equals(b, true));
|
||||
buster.refute(a.not_equals_why(b, true));
|
||||
},
|
||||
describe('Amount', function() {
|
||||
describe('Negatives', function() {
|
||||
it('Number 1', function () {
|
||||
assert.strictEqual(Amount.from_human('0').add(Amount.from_human('-1')).to_human(), '-1');
|
||||
});
|
||||
});
|
||||
describe('Positives', function() {
|
||||
it('Number 1', function() {
|
||||
assert(Amount.from_json('1').is_positive());
|
||||
});
|
||||
});
|
||||
describe('from_number', function() {
|
||||
it('Number 1', function() {
|
||||
assert.strictEqual(Amount.from_number(1).to_text_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
});
|
||||
describe('text_full_rewrite', function() {
|
||||
it('Number 1', function() {
|
||||
assert.strictEqual('0.000001/XRP', Amount.text_full_rewrite(1));
|
||||
});
|
||||
});
|
||||
describe('json_rewrite', function() {
|
||||
it('Number 1', function() {
|
||||
assert.strictEqual('1', Amount.json_rewrite(1));
|
||||
});
|
||||
});
|
||||
describe('UInt160', function() {
|
||||
it('Parse 0', function () {
|
||||
assert.deepEqual(new BigInteger(), UInt160.from_generic('0')._value);
|
||||
});
|
||||
it('Parse 0 export', function () {
|
||||
assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_generic('0').set_version(0).to_json());
|
||||
});
|
||||
it('Parse 1', function () {
|
||||
assert.deepEqual(new BigInteger([1]), UInt160.from_generic('1')._value);
|
||||
});
|
||||
it('Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export', function () {
|
||||
assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_json('rrrrrrrrrrrrrrrrrrrrrhoLvTp').to_json());
|
||||
});
|
||||
it('Parse rrrrrrrrrrrrrrrrrrrrBZbvji export', function () {
|
||||
assert.strictEqual(UInt160.ACCOUNT_ONE, UInt160.from_json('rrrrrrrrrrrrrrrrrrrrBZbvji').to_json());
|
||||
});
|
||||
it('Parse mtgox export', function () {
|
||||
assert.strictEqual(config.accounts['mtgox'].account, UInt160.from_json('mtgox').to_json());
|
||||
});
|
||||
it('is_valid rrrrrrrrrrrrrrrrrrrrrhoLvTp', function () {
|
||||
assert(UInt160.is_valid('rrrrrrrrrrrrrrrrrrrrrhoLvTp'));
|
||||
});
|
||||
it('!is_valid rrrrrrrrrrrrrrrrrrrrrhoLvT', function () {
|
||||
assert(!UInt160.is_valid('rrrrrrrrrrrrrrrrrrrrrhoLvT'));
|
||||
});
|
||||
});
|
||||
describe('Amount validity', function() {
|
||||
it('is_valid 1', function() {
|
||||
assert(Amount.is_valid(1));
|
||||
});
|
||||
it('is_valid "1"', function() {
|
||||
assert(Amount.is_valid('1'));
|
||||
});
|
||||
it('is_valid "1/XRP"', function() {
|
||||
assert(Amount.is_valid('1/XRP'));
|
||||
});
|
||||
it('!is_valid NaN', function() {
|
||||
assert(!Amount.is_valid(NaN));
|
||||
});
|
||||
it('!is_valid "xx"', function() {
|
||||
assert(!Amount.is_valid('xx'));
|
||||
});
|
||||
it('!is_valid_full 1', function() {
|
||||
assert(!Amount.is_valid_full(1));
|
||||
});
|
||||
it('is_valid_full "1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL"', function() {
|
||||
assert(Amount.is_valid_full('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL'));
|
||||
});
|
||||
});
|
||||
describe('Amount parsing', function() {
|
||||
it('Parse invalid string', function() {
|
||||
assert.strictEqual(Amount.from_json('x').to_text(), '0');
|
||||
assert.strictEqual(typeof Amount.from_json('x').to_text(true), 'number');
|
||||
assert(isNaN(Amount.from_json('x').to_text(true)));
|
||||
});
|
||||
it('Parse 800/USD/mtgox', function () {
|
||||
assert.strictEqual('800/USD/'+config.accounts['mtgox'].account, Amount.from_json('800/USD/mtgox').to_text_full());
|
||||
});
|
||||
it('Parse native 0', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('0').to_text_full());
|
||||
});
|
||||
it('Parse native 0.0', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('0.0').to_text_full());
|
||||
});
|
||||
it('Parse native -0', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('-0').to_text_full());
|
||||
});
|
||||
it('Parse native -0.0', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('-0.0').to_text_full());
|
||||
});
|
||||
it('Parse native 1000', function () {
|
||||
assert.strictEqual('0.001/XRP', Amount.from_json('1000').to_text_full());
|
||||
});
|
||||
it('Parse native 12.3', function () {
|
||||
assert.strictEqual('12.3/XRP', Amount.from_json('12.3').to_text_full());
|
||||
});
|
||||
it('Parse native -12.3', function () {
|
||||
assert.strictEqual('-12.3/XRP', Amount.from_json('-12.3').to_text_full());
|
||||
});
|
||||
it('Parse 123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
|
||||
assert.strictEqual('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('123./USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full());
|
||||
});
|
||||
it('Parse 12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
|
||||
assert.strictEqual('12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('12300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full());
|
||||
});
|
||||
it('Parse 12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
|
||||
assert.strictEqual('12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('12.3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full());
|
||||
});
|
||||
it('Parse 1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
|
||||
assert.strictEqual('1.23/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1.2300/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full());
|
||||
});
|
||||
it('Parse -0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
|
||||
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full());
|
||||
});
|
||||
it('Parse -0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () {
|
||||
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full());
|
||||
});
|
||||
});
|
||||
describe('Amount operations', function() {
|
||||
it('Negate native 123', function () {
|
||||
assert.strictEqual('-0.000123/XRP', Amount.from_json('123').negate().to_text_full());
|
||||
});
|
||||
it('Negate native -123', function () {
|
||||
assert.strictEqual('0.000123/XRP', Amount.from_json('-123').negate().to_text_full());
|
||||
});
|
||||
it('Negate non-native 123', function () {
|
||||
assert.strictEqual('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').negate().to_text_full());
|
||||
});
|
||||
it('Negate non-native -123', function () {
|
||||
assert.strictEqual('123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').negate().to_text_full());
|
||||
});
|
||||
it('Clone non-native -123', function () {
|
||||
assert.strictEqual('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-123/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').clone().to_text_full());
|
||||
});
|
||||
it('Add XRP to XRP', function () {
|
||||
assert.strictEqual('0.0002/XRP', Amount.from_json('150').add(Amount.from_json('50')).to_text_full());
|
||||
});
|
||||
it('Add USD to USD', function () {
|
||||
assert.strictEqual('200.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').add(Amount.from_json('50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Add 0 USD to 1 USD', function() {
|
||||
assert.strictEqual('1' , Amount.from_json('1/USD').add('0/USD').to_text());
|
||||
});
|
||||
it('Subtract USD from USD', function() {
|
||||
assert.strictEqual('99.52/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('150.02/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').subtract(Amount.from_json('50.5/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply 0 XRP with 0 XRP', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('0')).to_text_full());
|
||||
});
|
||||
it('Multiply 0 USD with 0 XRP', function () {
|
||||
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('0')).to_text_full());
|
||||
});
|
||||
it('Multiply 0 XRP with 0 USD', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply 1 XRP with 0 XRP', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('1').multiply(Amount.from_json('0')).to_text_full());
|
||||
});
|
||||
it('Multiply 1 USD with 0 XRP', function () {
|
||||
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('0')).to_text_full());
|
||||
});
|
||||
it('Multiply 1 XRP with 0 USD', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('1').multiply(Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply 0 XRP with 1 XRP', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('1')).to_text_full());
|
||||
});
|
||||
it('Multiply 0 USD with 1 XRP', function () {
|
||||
assert.strictEqual('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('1')).to_text_full());
|
||||
});
|
||||
it('Multiply 0 XRP with 1 USD', function () {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('0').multiply(Amount.from_json('1/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply XRP with USD', function () {
|
||||
assert.equal('0.002/XRP', Amount.from_json('200').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply XRP with USD', function () {
|
||||
assert.strictEqual('0.2/XRP', Amount.from_json('20000').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply XRP with USD', function () {
|
||||
assert.strictEqual('20/XRP', Amount.from_json('2000000').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply XRP with USD, neg', function () {
|
||||
assert.strictEqual('-0.002/XRP', Amount.from_json('200').multiply(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply XRP with USD, neg, frac', function () {
|
||||
assert.strictEqual('-0.222/XRP', Amount.from_json('-6000').multiply(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply USD with USD', function () {
|
||||
assert.strictEqual('20000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply USD with USD', function () {
|
||||
assert.strictEqual('200000000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply EUR with USD, result < 1', function () {
|
||||
assert.strictEqual('100000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply EUR with USD, neg', function () {
|
||||
assert.strictEqual('-48000000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply EUR with USD, neg, <1', function () {
|
||||
assert.strictEqual('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Multiply EUR with XRP, factor < 1', function () {
|
||||
assert.strictEqual('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000')).to_text_full());
|
||||
});
|
||||
it('Multiply EUR with XRP, neg', function () {
|
||||
assert.strictEqual('-500/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('5')).to_text_full());
|
||||
});
|
||||
it('Multiply EUR with XRP, neg, <1', function () {
|
||||
assert.strictEqual('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_json('2000')).to_text_full());
|
||||
});
|
||||
it('Multiply XRP with XRP', function () {
|
||||
assert.strictEqual('0.0001/XRP', Amount.from_json('10').multiply(Amount.from_json('10')).to_text_full());
|
||||
});
|
||||
it('Divide XRP by USD', function () {
|
||||
assert.strictEqual('0.00002/XRP', Amount.from_json('200').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide XRP by USD', function () {
|
||||
assert.strictEqual('0.002/XRP', Amount.from_json('20000').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide XRP by USD', function () {
|
||||
assert.strictEqual('0.2/XRP', Amount.from_json('2000000').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide XRP by USD, neg', function () {
|
||||
assert.strictEqual('-0.00002/XRP', Amount.from_json('200').divide(Amount.from_json('-10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide XRP by USD, neg, frac', function () {
|
||||
assert.strictEqual('-0.000162/XRP', Amount.from_json('-6000').divide(Amount.from_json('37/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide USD by USD', function () {
|
||||
assert.strictEqual('200/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('10/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide USD by USD, fractional', function () {
|
||||
assert.strictEqual('57142.85714285714/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('35/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide USD by USD', function () {
|
||||
assert.strictEqual('20/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('2000000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('100000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide EUR by USD, factor < 1', function () {
|
||||
assert.strictEqual('0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide EUR by USD, neg', function () {
|
||||
assert.strictEqual('-12/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-24000/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide EUR by USD, neg, <1', function () {
|
||||
assert.strictEqual('-0.1/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('-1000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full());
|
||||
});
|
||||
it('Divide EUR by XRP, result < 1', function () {
|
||||
assert.strictEqual('0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_text_full());
|
||||
});
|
||||
it('Divide EUR by XRP, neg', function () {
|
||||
assert.strictEqual('-20/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('5')).to_text_full());
|
||||
});
|
||||
it('Divide EUR by XRP, neg, <1', function () {
|
||||
assert.strictEqual('-0.05/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', Amount.from_json('-100/EUR/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').divide(Amount.from_json('2000')).to_text_full());
|
||||
});
|
||||
it('Divide by zero should throw', function() {
|
||||
assert.throws(function() {
|
||||
Amount.from_json(1).divide(Amount.from_json(0));
|
||||
});
|
||||
});
|
||||
it('Divide zero by number', function() {
|
||||
assert.strictEqual('0', Amount.from_json(0).divide(Amount.from_json(1)).to_text());
|
||||
});
|
||||
it('Divide invalid by number', function() {
|
||||
assert.throws(function() {
|
||||
Amount.from_json('x').divide(Amount.from_json('1'));
|
||||
});
|
||||
});
|
||||
it('Divide number by invalid', function() {
|
||||
assert.throws(function() {
|
||||
Amount.from_json('1').divide(Amount.from_json('x'));
|
||||
});
|
||||
});
|
||||
it('amount.abs -1 == 1', function() {
|
||||
assert.strictEqual('1', Amount.from_json(-1).abs().to_text());
|
||||
});
|
||||
it('amount.copyTo native', function() {
|
||||
assert(isNaN(Amount.from_json('x').copyTo(new Amount())._value));
|
||||
});
|
||||
it('amount.copyTo zero', function() {
|
||||
assert(!(Amount.from_json(0).copyTo(new Amount())._is_negative))
|
||||
});
|
||||
});
|
||||
describe('Amount comparisons', function() {
|
||||
it('0 USD == 0 USD amount.equals string argument', function() {
|
||||
var a = '0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL';
|
||||
assert(Amount.from_json(a).equals(a));
|
||||
});
|
||||
it('0 USD == 0 USD', function () {
|
||||
var a = Amount.from_json('0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(a.equals(b));
|
||||
assert(!a.not_equals_why(b));
|
||||
});
|
||||
it('0 USD == -0 USD', function () {
|
||||
var a = Amount.from_json('0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('-0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(a.equals(b));
|
||||
assert(!a.not_equals_why(b));
|
||||
});
|
||||
it('0 XRP == 0 XRP', function () {
|
||||
var a = Amount.from_json('0');
|
||||
var b = Amount.from_json('0.0');
|
||||
assert(a.equals(b));
|
||||
assert(!a.not_equals_why(b));
|
||||
});
|
||||
it('0 XRP == -0 XRP', function () {
|
||||
var a = Amount.from_json('0');
|
||||
var b = Amount.from_json('-0');
|
||||
assert(a.equals(b));
|
||||
assert(!a.not_equals_why(b));
|
||||
});
|
||||
it('10 USD == 10 USD', function () {
|
||||
var a = Amount.from_json('10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(a.equals(b));
|
||||
assert(!a.not_equals_why(b));
|
||||
});
|
||||
it('123.4567 USD == 123.4567 USD', function () {
|
||||
var a = Amount.from_json('123.4567/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('123.4567/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(a.equals(b));
|
||||
assert(!a.not_equals_why(b));
|
||||
});
|
||||
it('10 XRP == 10 XRP', function () {
|
||||
var a = Amount.from_json('10');
|
||||
var b = Amount.from_json('10');
|
||||
assert(a.equals(b));
|
||||
assert(!a.not_equals_why(b));
|
||||
});
|
||||
it('1.1 XRP == 1.1 XRP', function () {
|
||||
var a = Amount.from_json('1.1');
|
||||
var b = Amount.from_json('11.0').ratio_human(10);
|
||||
assert(a.equals(b));
|
||||
assert(!a.not_equals_why(b));
|
||||
});
|
||||
it('0 USD == 0 USD (ignore issuer)', function () {
|
||||
var a = Amount.from_json('0/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('0/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP');
|
||||
assert(a.equals(b, true));
|
||||
assert(!a.not_equals_why(b, true));
|
||||
});
|
||||
it('1.1 USD == 1.10 USD (ignore issuer)', function () {
|
||||
var a = Amount.from_json('1.1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('1.10/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP');
|
||||
assert(a.equals(b, true));
|
||||
assert(!a.not_equals_why(b, true));
|
||||
});
|
||||
// Exponent mismatch
|
||||
"10 USD != 100 USD" : function () {
|
||||
var a = Amount.from_json("10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("100/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "Non-XRP value differs.");
|
||||
},
|
||||
"10 XRP != 100 XRP" : function () {
|
||||
var a = Amount.from_json("10");
|
||||
var b = Amount.from_json("100");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "XRP value differs.");
|
||||
},
|
||||
it('10 USD != 100 USD', function () {
|
||||
var a = Amount.from_json('10/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('100/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'Non-XRP value differs.');
|
||||
});
|
||||
it('10 XRP != 100 XRP', function () {
|
||||
var a = Amount.from_json('10');
|
||||
var b = Amount.from_json('100');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'XRP value differs.');
|
||||
});
|
||||
// Mantissa mismatch
|
||||
"1 USD != 2 USD" : function () {
|
||||
var a = Amount.from_json("1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("2/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "Non-XRP value differs.");
|
||||
},
|
||||
"1 XRP != 2 XRP" : function () {
|
||||
var a = Amount.from_json("1");
|
||||
var b = Amount.from_json("2");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "XRP value differs.");
|
||||
},
|
||||
"0.1 USD != 0.2 USD" : function () {
|
||||
var a = Amount.from_json("0.1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("0.2/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "Non-XRP value differs.");
|
||||
},
|
||||
it('1 USD != 2 USD', function () {
|
||||
var a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('2/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'Non-XRP value differs.');
|
||||
});
|
||||
it('1 XRP != 2 XRP', function () {
|
||||
var a = Amount.from_json('1');
|
||||
var b = Amount.from_json('2');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'XRP value differs.');
|
||||
});
|
||||
it('0.1 USD != 0.2 USD', function () {
|
||||
var a = Amount.from_json('0.1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('0.2/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'Non-XRP value differs.');
|
||||
});
|
||||
// Sign mismatch
|
||||
"1 USD != -1 USD" : function () {
|
||||
var a = Amount.from_json("1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("-1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "Non-XRP sign differs.");
|
||||
},
|
||||
"1 XRP != -1 XRP" : function () {
|
||||
var a = Amount.from_json("1");
|
||||
var b = Amount.from_json("-1");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "XRP sign differs.");
|
||||
},
|
||||
"1 USD != 1 USD (issuer mismatch)" : function () {
|
||||
var a = Amount.from_json("1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("1/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "Non-XRP issuer differs: rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
},
|
||||
"1 USD != 1 EUR" : function () {
|
||||
var a = Amount.from_json("1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("1/EUR/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "Non-XRP currency differs.");
|
||||
},
|
||||
"1 USD != 1 XRP" : function () {
|
||||
var a = Amount.from_json("1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
var b = Amount.from_json("1");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "Native mismatch.");
|
||||
},
|
||||
"1 XRP != 1 USD" : function () {
|
||||
var a = Amount.from_json("1");
|
||||
var b = Amount.from_json("1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL");
|
||||
buster.refute(a.equals(b));
|
||||
buster.assert.equals(a.not_equals_why(b), "Native mismatch.");
|
||||
}
|
||||
}
|
||||
it('1 USD != -1 USD', function () {
|
||||
var a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('-1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'Non-XRP sign differs.');
|
||||
});
|
||||
it('1 XRP != -1 XRP', function () {
|
||||
var a = Amount.from_json('1');
|
||||
var b = Amount.from_json('-1');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'XRP sign differs.');
|
||||
});
|
||||
it('1 USD != 1 USD (issuer mismatch)', function () {
|
||||
var a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('1/USD/rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'Non-XRP issuer differs: rH5aWQJ4R7v4Mpyf4kDBUvDFT5cbpFq3XP/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
});
|
||||
it('1 USD != 1 EUR', function () {
|
||||
var a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('1/EUR/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'Non-XRP currency differs.');
|
||||
});
|
||||
it('1 USD != 1 XRP', function () {
|
||||
var a = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
var b = Amount.from_json('1');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'Native mismatch.');
|
||||
});
|
||||
it('1 XRP != 1 USD', function () {
|
||||
var a = Amount.from_json('1');
|
||||
var b = Amount.from_json('1/USD/rNDKeo9RrCiRdfsMG8AdoZvNZxHASGzbZL');
|
||||
assert(!a.equals(b));
|
||||
assert.strictEqual(a.not_equals_why(b), 'Native mismatch.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
var buster = require("buster");
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Seed = utils.load_module('seed').Seed;
|
||||
var config = require('./testutils').get_config();
|
||||
|
||||
var Seed = require("../src/js/ripple/seed").Seed;
|
||||
|
||||
try {
|
||||
var conf = require('./config');
|
||||
} catch(exception) {
|
||||
var conf = require('./config-example');
|
||||
}
|
||||
|
||||
var config = require('../src/js/ripple/config').load(conf);
|
||||
|
||||
buster.testCase("Base58", {
|
||||
"Seed" : {
|
||||
"saESc82Vun7Ta5EJRzGJbrXb5HNYk" : function () {
|
||||
var seed = Seed.from_json("saESc82Vun7Ta5EJRzGJbrXb5HNYk");
|
||||
buster.assert.equals(seed.to_hex(), "FF1CF838D02B2CF7B45BAC27F5F24F4F");
|
||||
},
|
||||
"sp6iDHnmiPN7tQFHm5sCW59ax3hfE" : function () {
|
||||
var seed = Seed.from_json("sp6iDHnmiPN7tQFHm5sCW59ax3hfE");
|
||||
buster.assert.equals(seed.to_hex(), "00AD8DA764C3C8AF5F9B8D51C94B9E49");
|
||||
}
|
||||
}
|
||||
describe('Base58', function() {
|
||||
describe('Seed', function() {
|
||||
it('saESc82Vun7Ta5EJRzGJbrXb5HNYk', function () {
|
||||
var seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
|
||||
assert.strictEqual(seed.to_hex(), 'FF1CF838D02B2CF7B45BAC27F5F24F4F');
|
||||
});
|
||||
it('sp6iDHnmiPN7tQFHm5sCW59ax3hfE', function () {
|
||||
var seed = Seed.from_json('sp6iDHnmiPN7tQFHm5sCW59ax3hfE');
|
||||
assert.strictEqual(seed.to_hex(), '00AD8DA764C3C8AF5F9B8D51C94B9E49');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
var config = module.exports;
|
||||
|
||||
config["ripple-lib"] = {
|
||||
rootPath: "../",
|
||||
environment: "node",
|
||||
tests: [
|
||||
"test/*-test.js"
|
||||
]
|
||||
}
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
104
test/currency-test.js
Normal file
104
test/currency-test.js
Normal file
@@ -0,0 +1,104 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var currency = utils.load_module('currency').Currency;
|
||||
|
||||
describe('Currency', function() {
|
||||
describe('json_rewrite', function() {
|
||||
it('json_rewrite("USD") == "USD"', function() {
|
||||
assert.strictEqual('USD', currency.json_rewrite('USD'));
|
||||
});
|
||||
it('json_rewrite("NaN") == "XRP"', function() {
|
||||
assert.strictEqual('XRP', currency.json_rewrite(NaN));
|
||||
});
|
||||
it('json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
|
||||
assert.strictEqual(currency.json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000"),
|
||||
'015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
});
|
||||
});
|
||||
describe('from_json', function() {
|
||||
it('from_json(NaN).to_json() == "XRP"', function() {
|
||||
var r = currency.from_json(NaN);
|
||||
assert(!r.is_valid());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json("XRP").to_json() == "XRP"', function() {
|
||||
var r = currency.from_json('XRP');
|
||||
assert(r.is_valid());
|
||||
assert(r.is_native());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
});
|
||||
describe('to_human', function() {
|
||||
it('"USD".to_human() == "USD"', function() {
|
||||
assert.strictEqual('USD', currency.from_json('USD').to_human());
|
||||
});
|
||||
it('"NaN".to_human() == "XRP"', function() {
|
||||
assert.strictEqual('XRP', currency.from_json(NaN).to_human());
|
||||
});
|
||||
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human(),
|
||||
'XAU');
|
||||
});
|
||||
});
|
||||
describe('parse_json(currency obj)', function() {
|
||||
assert.strictEqual('USD', new currency().parse_json(currency.from_json('USD')).to_json());
|
||||
});
|
||||
describe('is_valid', function() {
|
||||
it('Currency.is_valid("XRP")', function() {
|
||||
assert(currency.is_valid('XRP'));
|
||||
});
|
||||
it('!Currency.is_valid(NaN)', function() {
|
||||
assert(!currency.is_valid(NaN));
|
||||
});
|
||||
it('from_json("XRP").is_valid()', function() {
|
||||
assert(currency.from_json('XRP').is_valid());
|
||||
});
|
||||
it('!from_json(NaN).is_valid()', function() {
|
||||
assert(!currency.from_json(NaN).is_valid());
|
||||
});
|
||||
});
|
||||
describe('clone', function() {
|
||||
it('should clone currency object', function() {
|
||||
var c = currency.from_json('XRP');
|
||||
assert.strictEqual('XRP', c.clone().to_json());
|
||||
});
|
||||
});
|
||||
describe('to_human', function() {
|
||||
it('should generate human string', function() {
|
||||
assert.strictEqual('XRP', currency.from_json('XRP').to_human());
|
||||
});
|
||||
});
|
||||
describe('has_interest', function() {
|
||||
it('should be true for type 1 currency codes', function() {
|
||||
assert(currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000').has_interest());
|
||||
assert(currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000').has_interest());
|
||||
});
|
||||
it('should be false for type 0 currency codes', function() {
|
||||
assert(!currency.from_hex('0000000000000000000000005553440000000000').has_interest());
|
||||
assert(!currency.from_json('USD').has_interest());
|
||||
});
|
||||
});
|
||||
function precision(num, precision) {
|
||||
return +(Math.round(num + "e+"+precision) + "e-"+precision);
|
||||
}
|
||||
describe('get_interest_at', function() {
|
||||
it('returns demurred value for demurrage currency', function() {
|
||||
var cur = currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
|
||||
// At start, no demurrage should occur
|
||||
assert.equal(1, cur.get_interest_at(443845330));
|
||||
|
||||
// After one year, 0.5% should have occurred
|
||||
assert.equal(0.995, precision(cur.get_interest_at(443845330 + 31536000), 14));
|
||||
|
||||
// After one demurrage period, 1/e should have occurred
|
||||
assert.equal(1/Math.E, cur.get_interest_at(443845330 + 6291418827.05));
|
||||
|
||||
// One year before start, it should be (roughly) 0.5% higher.
|
||||
assert.equal(1.005, precision(cur.get_interest_at(443845330 - 31536000), 4));
|
||||
|
||||
// One demurrage period before start, rate should be e
|
||||
assert.equal(Math.E, cur.get_interest_at(443845330 - 6291418827.05));
|
||||
});
|
||||
});
|
||||
});
|
||||
14
test/keypair-test.js
Normal file
14
test/keypair-test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Seed = utils.load_module('seed').Seed;
|
||||
var config = require('./testutils').get_config();
|
||||
|
||||
describe('KeyPair', function() {
|
||||
it('can generate an address', function () {
|
||||
var seed = Seed.from_json("masterpassphrase");
|
||||
var address = seed.get_key().get_address();
|
||||
assert.strictEqual(address.to_json(), 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
104
test/remote-test.js
Normal file
104
test/remote-test.js
Normal file
@@ -0,0 +1,104 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sinon = require('sinon');
|
||||
|
||||
var Remote = utils.load_module('remote').Remote;
|
||||
var Server = utils.load_module('server').Server;
|
||||
var Request = utils.load_module('request').Request;
|
||||
|
||||
var options, spy, mock, stub, remote, callback;
|
||||
|
||||
describe('Remote', function () {
|
||||
describe('initialing a remote with options', function () {
|
||||
beforeEach(function () {
|
||||
options = {
|
||||
trace : true,
|
||||
trusted: true,
|
||||
local_signing: true,
|
||||
servers: [
|
||||
{ host: 's-west.ripple.com', port: 443, secure: true },
|
||||
{ host: 's-east.ripple.com', port: 443, secure: true }
|
||||
],
|
||||
|
||||
blobvault : 'https://blobvault.payward.com',
|
||||
persistent_auth : false,
|
||||
transactions_per_page: 50,
|
||||
|
||||
bridge: {
|
||||
out: {
|
||||
// 'bitcoin': 'localhost:3000'
|
||||
// 'bitcoin': 'https://www.bitstamp.net/ripple/bridge/out/bitcoin/'
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
})
|
||||
it('should add a server for each specified', function (done) {
|
||||
var remote = new Remote(options);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
describe('functions that return request objects', function () {
|
||||
beforeEach(function () {
|
||||
callback = function () {}
|
||||
remote = new Remote(options);
|
||||
});
|
||||
|
||||
describe('requesting a ledger', function () {
|
||||
it('should return a request', function (done) {
|
||||
var request = remote.request_ledger(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
describe('requesting server info', function () {
|
||||
it('should return a request object', function (done) {
|
||||
var request = remote.request_server_info(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
describe('requesting peers', function () {
|
||||
it('should return a request object', function (done) {
|
||||
var request = remote.request_peers(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('requesting a connection', function () {
|
||||
it('should return a request object', function (done) {
|
||||
var request = remote.request_connect(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('making a unique node list add request', function () {
|
||||
it('should return a request object', function (done) {
|
||||
var request = remote.request_unl_add(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('making a unique node list request', function () {
|
||||
it('should return a request object', function (done) {
|
||||
var request = remote.request_unl_list(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('making a unique node list delete request', function () {
|
||||
it('should return a request object', function (done) {
|
||||
var request = remote.request_unl_delete(null, {}, callback);
|
||||
assert(request instanceof Request);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
61
test/serializedobject-test.js
Normal file
61
test/serializedobject-test.js
Normal file
@@ -0,0 +1,61 @@
|
||||
var utils = require('./testutils');
|
||||
var assert = require('assert');
|
||||
var SerializedObject = utils.load_module('serializedobject').SerializedObject;
|
||||
|
||||
describe('Serialized object', function() {
|
||||
describe('Serialized object', function() {
|
||||
it('From json and back', function() {
|
||||
var input_json = {
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Fee: '15',
|
||||
Flags: 0,
|
||||
Paths: [[
|
||||
{
|
||||
account: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
|
||||
currency: 'USD',
|
||||
issuer: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV'
|
||||
},
|
||||
{
|
||||
currency: 'XRP'
|
||||
}
|
||||
]],
|
||||
SendMax: {
|
||||
currency: 'USD',
|
||||
issuer: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
value: '2.74579388'
|
||||
},
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A',
|
||||
TransactionType: 'Payment',
|
||||
TxnSignature: '30450221009DA3A42DD25E3B22EC45AD8BA8FC7A954264264A816D300B2DF69F814D7D4DD2022072C9627F97EEC6DA13DE841E06E2CD985EF06A0FBB15DDBF0800D0730C8986BF'
|
||||
};
|
||||
var output_json = SerializedObject.from_json(input_json).to_json();
|
||||
assert.deepEqual(input_json, output_json);
|
||||
});
|
||||
});
|
||||
describe('Format validation', function() {
|
||||
// Peercover actually had a problem submitting transactions without a `Fee`
|
||||
// and rippled was only informing of "transaction is invalid"
|
||||
it('should throw an Error when there is a missing field', function() {
|
||||
var input_json = {
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A',
|
||||
TransactionType: 'Payment',
|
||||
TxnSignature: '30450221009DA3A42DD25E3B22EC45AD8BA8FC7A954264264A816D300B2DF69F814D7D4DD2022072C9627F97EEC6DA13DE841E06E2CD985EF06A0FBB15DDBF0800D0730C8986BF'
|
||||
};
|
||||
assert.throws (
|
||||
function() {
|
||||
var output_json = SerializedObject.from_json(input_json);
|
||||
},
|
||||
/Payment is missing fields: \["Fee"\]/
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,56 @@
|
||||
var buster = require("buster");
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var Seed = utils.load_module('seed').Seed;
|
||||
|
||||
var Seed = require("../src/js/ripple/seed").Seed;
|
||||
|
||||
try {
|
||||
var conf = require('./config');
|
||||
} catch(exception) {
|
||||
var conf = require('./config-example');
|
||||
function _isNaN(n) {
|
||||
return typeof n === 'number' && isNaN(n);
|
||||
}
|
||||
|
||||
var config = require('../src/js/ripple/config').load(conf);
|
||||
|
||||
buster.testCase("Signing", {
|
||||
"Keys" : {
|
||||
"SigningPubKey 1 (ripple-client issue #245)" : function () {
|
||||
var seed = Seed.from_json("saESc82Vun7Ta5EJRzGJbrXb5HNYk");
|
||||
var key = seed.get_key("rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ");
|
||||
describe('Signing', function() {
|
||||
describe('Keys', function() {
|
||||
it('SigningPubKey 1 (ripple-client issue #245)', function () {
|
||||
var seed = Seed.from_json('saESc82Vun7Ta5EJRzGJbrXb5HNYk');
|
||||
var key = seed.get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
|
||||
var pub = key.to_hex_pub();
|
||||
buster.assert.equals(pub, "0396941B22791A448E5877A44CE98434DB217D6FB97D63F0DAD23BE49ED45173C9");
|
||||
},
|
||||
"SigningPubKey 2 (master seed)" : function () {
|
||||
var seed = Seed.from_json("snoPBrXtMeMyMHUVTgbuqAfg1SUTb");
|
||||
var key = seed.get_key("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
|
||||
assert.strictEqual(pub, '0396941B22791A448E5877A44CE98434DB217D6FB97D63F0DAD23BE49ED45173C9');
|
||||
});
|
||||
it('SigningPubKey 2 (master seed)', function () {
|
||||
var seed = Seed.from_json('snoPBrXtMeMyMHUVTgbuqAfg1SUTb');
|
||||
var key = seed.get_key('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
var pub = key.to_hex_pub();
|
||||
buster.assert.equals(pub, "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020");
|
||||
}
|
||||
}
|
||||
assert.strictEqual(pub, '0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020');
|
||||
});
|
||||
});
|
||||
describe('parse_json', function() {
|
||||
it('empty string', function() {
|
||||
assert(_isNaN(new Seed().parse_json('').to_json()));
|
||||
});
|
||||
it('hex string', function() {
|
||||
var str = new Array(33).join('0');
|
||||
assert(_isNaN(new Seed().parse_json(str).to_json()));
|
||||
});
|
||||
it('passphrase', function() {
|
||||
var str = new Array(60).join('0');
|
||||
assert.strictEqual('snFRPnVL3secohdpwSie8ANXdFQvG', new Seed().parse_json(str).to_json());
|
||||
});
|
||||
it('null', function() {
|
||||
assert(_isNaN(new Seed().parse_json(null).to_json()));
|
||||
});
|
||||
});
|
||||
describe('parse_passphrase', function() {
|
||||
it('invalid passphrase', function() {
|
||||
assert.throws(function() {
|
||||
new Seed().parse_passphrase(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('get_key', function() {
|
||||
it('get key from invalid seed', function() {
|
||||
assert.throws(function() {
|
||||
new Seed().get_key('rBZ4j6MsoctipM6GEyHSjQKzXG3yambDnZ');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
52
test/sjcl-extramath-test.js
Normal file
52
test/sjcl-extramath-test.js
Normal file
@@ -0,0 +1,52 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sjcl = require('../build/sjcl');
|
||||
|
||||
describe('SJCL Extramath', function() {
|
||||
describe('setBitM', function() {
|
||||
it('0x0f set bit 4 => 0x1f', function () {
|
||||
var val = new sjcl.bn("0f");
|
||||
val.setBitM(4);
|
||||
assert.strictEqual(val.toString(), '0x1f');
|
||||
});
|
||||
it('0x0f set bit 23 => 0x80000f', function () {
|
||||
var val = new sjcl.bn("0f");
|
||||
val.setBitM(23);
|
||||
assert.strictEqual(val.toString(), '0x80000f');
|
||||
});
|
||||
it('0x0f set bit 24 => 0x100000f', function () {
|
||||
var val = new sjcl.bn("0f");
|
||||
val.setBitM(24);
|
||||
assert.strictEqual(val.toString(), '0x100000f');
|
||||
});
|
||||
});
|
||||
describe('testBit', function() {
|
||||
it('0x03', function () {
|
||||
var val = new sjcl.bn("03");
|
||||
assert.strictEqual(val.testBit(0), 1);
|
||||
assert.strictEqual(val.testBit(1), 1);
|
||||
assert.strictEqual(val.testBit(2), 0);
|
||||
});
|
||||
it('0x1000000', function () {
|
||||
var val = new sjcl.bn("1000000");
|
||||
assert.strictEqual(val.testBit(25), 0);
|
||||
assert.strictEqual(val.testBit(24), 1);
|
||||
assert.strictEqual(val.testBit(23), 0);
|
||||
assert.strictEqual(val.testBit( 1), 0);
|
||||
assert.strictEqual(val.testBit( 0), 0);
|
||||
});
|
||||
it('0xff7fffffff', function () {
|
||||
var val = new sjcl.bn("ff7fffffff");
|
||||
assert.strictEqual(val.testBit(32), 1);
|
||||
assert.strictEqual(val.testBit(31), 0);
|
||||
assert.strictEqual(val.testBit(30), 1);
|
||||
assert.strictEqual(val.testBit(24), 1);
|
||||
assert.strictEqual(val.testBit(23), 1);
|
||||
assert.strictEqual(val.testBit(22), 1);
|
||||
assert.strictEqual(val.testBit( 1), 1);
|
||||
assert.strictEqual(val.testBit( 0), 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
48
test/sjcl-jacobi-test.js
Normal file
48
test/sjcl-jacobi-test.js
Normal file
@@ -0,0 +1,48 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sjcl = require('../build/sjcl');
|
||||
|
||||
describe('SJCL Jacobi', function() {
|
||||
it('(15/13) = -1', function () {
|
||||
var jac = new sjcl.bn(15).jacobi(13);
|
||||
assert.strictEqual(jac, -1);
|
||||
});
|
||||
it('(4/97) = 1', function () {
|
||||
var jac = new sjcl.bn(4).jacobi(97);
|
||||
assert.strictEqual(jac, 1);
|
||||
});
|
||||
it('(17/17) = 0', function () {
|
||||
var jac = new sjcl.bn(17).jacobi(17);
|
||||
assert.strictEqual(jac, 0);
|
||||
});
|
||||
it('(34/17) = 0', function () {
|
||||
var jac = new sjcl.bn(34).jacobi(17);
|
||||
assert.strictEqual(jac, 0);
|
||||
});
|
||||
it('(19/45) = 1', function () {
|
||||
var jac = new sjcl.bn(19).jacobi(45);
|
||||
assert.strictEqual(jac, 1);
|
||||
});
|
||||
it('(8/21) = -1', function () {
|
||||
var jac = new sjcl.bn(8).jacobi(21);
|
||||
assert.strictEqual(jac, -1);
|
||||
});
|
||||
it('(5/21) = 1', function () {
|
||||
var jac = new sjcl.bn(5).jacobi(21);
|
||||
assert.strictEqual(jac, 1);
|
||||
});
|
||||
it('(1001/9907) = -1', function () {
|
||||
var jac = new sjcl.bn(1001).jacobi(9907);
|
||||
assert.strictEqual(jac, -1);
|
||||
});
|
||||
it('(1236/20003) = 1', function () {
|
||||
var jac = new sjcl.bn(1236).jacobi(20003);
|
||||
assert.strictEqual(jac, 1);
|
||||
});
|
||||
it('With huge numbers', function () {
|
||||
var jac = new sjcl.bn("217033ffbc5a462201407027104916c5e7bf09f2b0c926f7c5cb20858be29d92e7fe67080eeb268fcbc2bc44d9cecfe1d3acbb302111eba355a8b769ed4bdbf773d37dca47e2293c173ff4f84b38f4e84bfad7cc1a913d70e11cf664a95575b80ec9d10123289b402ad2c71c70f2dc28360262d3d703faa964c741a711e4eebd324d659601dd14564fcd8c5908bbf8c97cf3ff82f083da3005848b48cb545b31be2039cca1d67714f32d32b3228c1a659415ee6c138ca274f789006a90a9a41bbc3934b84c78948eae8351f45696fa716b1328561f4c3bbb44ac73112c291b6b4587365e44fa09d583fb8074eb35bf947231e500c0d2c79c5fb957e50e84f6c9").jacobi("c7f1bc1dfb1be82d244aef01228c1409c198894eca9e21430f1669b4aa3864c9f37f3d51b2b4ba1ab9e80f59d267fda1521e88b05117993175e004543c6e3611242f24432ce8efa3b81f0ff660b4f91c5d52f2511a6f38181a7bf9abeef72db056508bbb4eeb5f65f161dd2d5b439655d2ae7081fcc62fdcb281520911d96700c85cdaf12e7d1f15b55ade867240722425198d4ce39019550c4c8a921fc231d3e94297688c2d77cd68ee8fdeda38b7f9a274701fef23b4eaa6c1a9c15b2d77f37634930386fc20ec291be95aed9956801e1c76601b09c413ad915ff03bfdc0b6b233686ae59e8caf11750b509ab4e57ee09202239baee3d6e392d1640185e1cd");
|
||||
assert.strictEqual(jac, -1);
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
88
test/sjcl-montgomery.js
Normal file
88
test/sjcl-montgomery.js
Normal file
@@ -0,0 +1,88 @@
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils');
|
||||
var sjcl = require('../build/sjcl');
|
||||
|
||||
function testExp(vec) {
|
||||
var actual = new sjcl.bn(vec.g).powermodMontgomery(new sjcl.bn(vec.e),
|
||||
new sjcl.bn(vec.m));
|
||||
assert.strictEqual(actual.toString(), new sjcl.bn(vec.r).toString());
|
||||
}
|
||||
|
||||
describe('SJCL Montgomery Exponentiation', function() {
|
||||
describe('powermod', function() {
|
||||
it('test 1', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: 3,
|
||||
m: 3,
|
||||
r: 2
|
||||
});
|
||||
});
|
||||
it('test 2', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "10000000000000000000000000",
|
||||
m: 1337,
|
||||
r: 1206
|
||||
});
|
||||
});
|
||||
it('test 3', function () {
|
||||
testExp({
|
||||
g: 17,
|
||||
e: 90,
|
||||
m: 34717861147,
|
||||
r: 28445204336
|
||||
});
|
||||
});
|
||||
it('test 4', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "0x844A000000000000000000000",
|
||||
m: 13,
|
||||
r: 9
|
||||
});
|
||||
});
|
||||
it('test 5', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: 0x1010,
|
||||
m: 131,
|
||||
r: 59
|
||||
});
|
||||
});
|
||||
it('test 6', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "43207437777777877617151",
|
||||
m: 13,
|
||||
r: 2
|
||||
});
|
||||
});
|
||||
it('test 7', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "389274238947216444871600001871964319565192765874149",
|
||||
m: 117,
|
||||
r: 44
|
||||
});
|
||||
});
|
||||
it('test 8', function () {
|
||||
testExp({
|
||||
g: 2,
|
||||
e: "89457115510016156219817846189181057618965150496979174671534084187",
|
||||
m: "1897166415676096761",
|
||||
r: "16840615e646a4c5c8d"
|
||||
});
|
||||
});
|
||||
it('test 9', function () {
|
||||
testExp({
|
||||
g: "4c3399bebab284bc7f9056efe17ea39db324ffa1d52dc1542eb16a749570789359f192535b7bcb514b36be9cdb4fb2a6ba3ad6b4248034e9a1d2a8612cd9d885242b4f679524121b74f79d7db14859fccde1c0dfe6ef002dcbc777ab5fcf4432",
|
||||
e: "000322e6b6dafe138ccd6a991977d19",
|
||||
m: "a5091daa41997943e3c98469e93377f668d05d8059bc53f72aaacdac3729a3070dc7439a5171160bf9ec2826b7191b03b0e84b28e14dd376de35d29a96f686666e053ab62a41ebc2b5f52e8cf06254100fd153a1cda4485f170c39c54689e52d",
|
||||
r: "554336ea044782d29f091117cfeaeee2334b4242bd7428d0bba3ce5325781dc219e891e54698cb0193ffe7c6fc07f1808f6685e64b6a082815f6afd2e16c7a61316b5e3e59cd8b3984d5a76c8e173f8615f7dceac0c99e27e4abfb1278dfa67f"
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
31
test/testutils.js
Normal file
31
test/testutils.js
Normal file
@@ -0,0 +1,31 @@
|
||||
var ripple = require('../src/js/ripple');
|
||||
|
||||
exports.get_config = get_config;
|
||||
|
||||
function get_config() {
|
||||
var config = { };
|
||||
try {
|
||||
config = require('./config');
|
||||
} catch(exception) {
|
||||
config = require('./config-example');
|
||||
}
|
||||
return load_config(config);
|
||||
};
|
||||
|
||||
exports.load_config = load_config;
|
||||
|
||||
function load_config(config) {
|
||||
return load_module('config').load(config);
|
||||
};
|
||||
|
||||
exports.load_module = load_module;
|
||||
|
||||
function load_module(name) {
|
||||
if (process.env.RIPPLE_LIB_COV) {
|
||||
return require('../src-cov/js/ripple/' + name)
|
||||
} else if (!ripple.hasOwnProperty(name)) {
|
||||
return require('../src/js/ripple/' + name);
|
||||
} else {
|
||||
return require('../src/js/ripple')[name];
|
||||
}
|
||||
};
|
||||
109
test/transaction-test.js
Normal file
109
test/transaction-test.js
Normal file
@@ -0,0 +1,109 @@
|
||||
var utils = require('./testutils');
|
||||
var assert = require('assert');
|
||||
var Transaction = utils.load_module('transaction').Transaction;
|
||||
|
||||
describe('Transaction', function() {
|
||||
it('Serialization', function() {
|
||||
var input_json = {
|
||||
Account : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
Amount : {
|
||||
currency : "LTC",
|
||||
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
value : "9.985"
|
||||
},
|
||||
Destination : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
Fee : "15",
|
||||
Flags : 0,
|
||||
Paths : [
|
||||
[
|
||||
{
|
||||
account : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
currency : "USD",
|
||||
issuer : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
type : 49,
|
||||
type_hex : "0000000000000031"
|
||||
},
|
||||
{
|
||||
currency : "LTC",
|
||||
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
type : 48,
|
||||
type_hex : "0000000000000030"
|
||||
},
|
||||
{
|
||||
account : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
currency : "LTC",
|
||||
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
type : 49,
|
||||
type_hex : "0000000000000031"
|
||||
}
|
||||
]
|
||||
],
|
||||
SendMax : {
|
||||
currency : "USD",
|
||||
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
value : "30.30993068"
|
||||
},
|
||||
Sequence : 415,
|
||||
SigningPubKey : "02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A",
|
||||
TransactionType : "Payment",
|
||||
TxnSignature : "304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E"
|
||||
};
|
||||
var expected_hex = "1200002200000000240000019F61D4A3794DFA1510000000000000000000000000004C54430000000000EF7ED76B77750D79EC92A59389952E0E8054407668400000000000000F69D4CAC4AC112283000000000000000000000000005553440000000000EF7ED76B77750D79EC92A59389952E0E80544076732102854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A7448304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E8114EF7ED76B77750D79EC92A59389952E0E805440768314EF7ED76B77750D79EC92A59389952E0E80544076011231DD39C650A96EDA48334E70CC4A85B8B2E8502CD30000000000000000000000005553440000000000DD39C650A96EDA48334E70CC4A85B8B2E8502CD3300000000000000000000000004C5443000000000047DA9E2E00ECF224A52329793F1BB20FB1B5EA643147DA9E2E00ECF224A52329793F1BB20FB1B5EA640000000000000000000000004C5443000000000047DA9E2E00ECF224A52329793F1BB20FB1B5EA6400";
|
||||
var transaction = Transaction.from_json(input_json);
|
||||
|
||||
assert.deepEqual(transaction.serialize().to_hex(), expected_hex);
|
||||
});
|
||||
|
||||
it('Hashing', function() {
|
||||
var input_json = {
|
||||
Account : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
Amount : {
|
||||
currency : "LTC",
|
||||
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
value : "9.985"
|
||||
},
|
||||
Destination : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
Fee : "15",
|
||||
Flags : 0,
|
||||
Paths : [
|
||||
[
|
||||
{
|
||||
account : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
currency : "USD",
|
||||
issuer : "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
type : 49,
|
||||
type_hex : "0000000000000031"
|
||||
},
|
||||
{
|
||||
currency : "LTC",
|
||||
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
type : 48,
|
||||
type_hex : "0000000000000030"
|
||||
},
|
||||
{
|
||||
account : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
currency : "LTC",
|
||||
issuer : "rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX",
|
||||
type : 49,
|
||||
type_hex : "0000000000000031"
|
||||
}
|
||||
]
|
||||
],
|
||||
SendMax : {
|
||||
currency : "USD",
|
||||
issuer : "r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS",
|
||||
value : "30.30993068"
|
||||
},
|
||||
Sequence : 415,
|
||||
SigningPubKey : "02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A",
|
||||
TransactionType : "Payment",
|
||||
TxnSignature : "304602210096C2F385530587DE573936CA51CB86B801A28F777C944E268212BE7341440B7F022100EBF0508A9145A56CDA7FAF314DF3BBE51C6EE450BA7E74D88516891A3608644E"
|
||||
};
|
||||
var expected_hash = "87366146D381AD971B97DD41CFAC1AE4670B0E996AB574B0CE18CE6467811868";
|
||||
var transaction = Transaction.from_json(input_json);
|
||||
|
||||
assert.deepEqual(transaction.hash(), expected_hash);
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,26 +1,25 @@
|
||||
var fs = require("fs");
|
||||
var buster = require("buster");
|
||||
var fs = require('fs');
|
||||
var assert = require('assert');
|
||||
var utils = require('./testutils').load_module('utils');
|
||||
|
||||
var utils = require("../src/js/ripple/utils.js");
|
||||
|
||||
buster.testCase("Utils", {
|
||||
"hexToString and stringToHex" : {
|
||||
"Even: 123456" : function () {
|
||||
buster.assert.equals("123456", utils.stringToHex(utils.hexToString("123456")));
|
||||
},
|
||||
"Odd: 12345" : function () {
|
||||
buster.assert.equals("012345", utils.stringToHex(utils.hexToString("12345")));
|
||||
},
|
||||
"Under 10: 0" : function () {
|
||||
buster.assert.equals("00", utils.stringToHex(utils.hexToString("0")));
|
||||
},
|
||||
"Under 10: 1" : function () {
|
||||
buster.assert.equals("01", utils.stringToHex(utils.hexToString("1")));
|
||||
},
|
||||
"Empty" : function () {
|
||||
buster.assert.equals("", utils.stringToHex(utils.hexToString("")));
|
||||
}
|
||||
}
|
||||
describe('Utils', function() {
|
||||
describe('hexToString and stringToHex', function() {
|
||||
it('Even: 123456', function () {
|
||||
assert.strictEqual('123456', utils.stringToHex(utils.hexToString('123456')));
|
||||
});
|
||||
it('Odd: 12345', function () {
|
||||
assert.strictEqual('012345', utils.stringToHex(utils.hexToString('12345')));
|
||||
});
|
||||
it('Under 10: 0', function () {
|
||||
assert.strictEqual('00', utils.stringToHex(utils.hexToString('0')));
|
||||
});
|
||||
it('Under 10: 1', function () {
|
||||
assert.strictEqual('01', utils.stringToHex(utils.hexToString('1')));
|
||||
});
|
||||
it('Empty', function () {
|
||||
assert.strictEqual('', utils.stringToHex(utils.hexToString('')));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,6 +1,40 @@
|
||||
// If there is no WebSocket, try MozWebSocket (support for some old browsers)
|
||||
try {
|
||||
module.exports = WebSocket
|
||||
module.exports = WebSocket;
|
||||
} catch(err) {
|
||||
module.exports = MozWebSocket
|
||||
}
|
||||
module.exports = MozWebSocket;
|
||||
}
|
||||
|
||||
// Some versions of Safari Mac 5 and Safari iOS 4 seem to support websockets,
|
||||
// but can't communicate with websocketpp, which is what rippled uses.
|
||||
//
|
||||
// Note that we check for both the WebSocket protocol version the browser seems
|
||||
// to implement as well as the user agent etc. The reason is that we want to err
|
||||
// on the side of trying to connect since we don't want to accidentally disable
|
||||
// a browser that would normally work fine.
|
||||
var match, versionRegexp = /Version\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//;
|
||||
if (
|
||||
// Is browser
|
||||
"object" === typeof navigator &&
|
||||
"string" === typeof navigator.userAgent &&
|
||||
// Is Safari
|
||||
(match = versionRegexp.exec(navigator.userAgent)) &&
|
||||
// And uses the old websocket protocol
|
||||
2 === window.WebSocket.CLOSED
|
||||
) {
|
||||
// Is iOS
|
||||
if (/iP(hone|od|ad)/.test(navigator.platform)) {
|
||||
// Below version 5 is broken
|
||||
if (+match[1] < 5) {
|
||||
module.exports = void(0);
|
||||
}
|
||||
// Is any other Mac OS
|
||||
// If you want to refactor this code, be careful, iOS user agents contain the
|
||||
// string "like Mac OS X".
|
||||
} else if (navigator.appVersion.indexOf("Mac") !== -1) {
|
||||
// Below version 6 is broken
|
||||
if (+match[1] < 6) {
|
||||
module.exports = void(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user