mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-12 00:35:47 +00:00
Compare commits
1468 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f282585c3f | ||
|
|
ae5ff31c96 | ||
|
|
0bdd37090e | ||
|
|
c745faaaf0 | ||
|
|
0c62fa2112 | ||
|
|
806547dd15 | ||
|
|
572c945274 | ||
|
|
c605efab61 | ||
|
|
a17011243e | ||
|
|
b1b47d7d91 | ||
|
|
a93c580c92 | ||
|
|
030e2786d1 | ||
|
|
56bbe1d437 | ||
|
|
1fff5ea6dc | ||
|
|
ad9956375f | ||
|
|
7d7970d318 | ||
|
|
f4fa10b9c0 | ||
|
|
e68096bd27 | ||
|
|
50cda426eb | ||
|
|
1038421428 | ||
|
|
a5046ab086 | ||
|
|
ebbec1954e | ||
|
|
4021018931 | ||
|
|
5824c3cb7c | ||
|
|
45a1b9471e | ||
|
|
70bc819665 | ||
|
|
e05f3e9b9c | ||
|
|
e93f1ab6f4 | ||
|
|
0878a8ecf0 | ||
|
|
6ea07139dc | ||
|
|
55fca2d7d5 | ||
|
|
8596dcef21 | ||
|
|
bca84d5508 | ||
|
|
65f7485497 | ||
|
|
457b02c781 | ||
|
|
c8e0fa85f3 | ||
|
|
5e714f6143 | ||
|
|
4ecbf31898 | ||
|
|
ea24bf0415 | ||
|
|
171f8349cb | ||
|
|
398f8d001f | ||
|
|
e66978fb48 | ||
|
|
c57d528db7 | ||
|
|
0c47310063 | ||
|
|
cb4f6e37a8 | ||
|
|
ab943f36c3 | ||
|
|
de7fc78ef0 | ||
|
|
9a502580fd | ||
|
|
d56e70b995 | ||
|
|
e9aaf50d59 | ||
|
|
ac0a4f521a | ||
|
|
a3380c5cdd | ||
|
|
6a6d2a0787 | ||
|
|
64809d9ae2 | ||
|
|
d14b38bc91 | ||
|
|
440dfb5785 | ||
|
|
d4a4b5f4fb | ||
|
|
0c000a7fee | ||
|
|
c655c2a20e | ||
|
|
3ba5a18b91 | ||
|
|
bdb3415855 | ||
|
|
5ef5bdd9d9 | ||
|
|
c7bbce8371 | ||
|
|
5e2c26a4a2 | ||
|
|
631faa20ec | ||
|
|
2db17ba67c | ||
|
|
50eca42e35 | ||
|
|
f327487157 | ||
|
|
85b64b7ac3 | ||
|
|
cf17a9e8d6 | ||
|
|
71a1282b89 | ||
|
|
85e1f2f47d | ||
|
|
1f68eba146 | ||
|
|
d71873442f | ||
|
|
10ca2da2d6 | ||
|
|
c7ba822320 | ||
|
|
ef3ce46d00 | ||
|
|
c4595e03ce | ||
|
|
26a7eb456b | ||
|
|
142a85d6a7 | ||
|
|
ef51490a1a | ||
|
|
c40d643238 | ||
|
|
6f23c88567 | ||
|
|
56958a6242 | ||
|
|
74dac97b36 | ||
|
|
2f2e41c781 | ||
|
|
8c872f71c6 | ||
|
|
b40b496866 | ||
|
|
569fec296e | ||
|
|
56d8aa797a | ||
|
|
fe7e30b737 | ||
|
|
a114281c60 | ||
|
|
d09548d04d | ||
|
|
a02b8e3e5c | ||
|
|
2c3f9ca202 | ||
|
|
587782820d | ||
|
|
8fad048569 | ||
|
|
f7c35b118e | ||
|
|
65a669bbb2 | ||
|
|
9985acc539 | ||
|
|
f1f0a43f21 | ||
|
|
6b856c3cc5 | ||
|
|
d92888ed73 | ||
|
|
0357840654 | ||
|
|
53cae3a66d | ||
|
|
949a1ca4ae | ||
|
|
e667536a5b | ||
|
|
dde000a4bb | ||
|
|
aa1f5a8e7d | ||
|
|
bfbfcc2894 | ||
|
|
6abfa759aa | ||
|
|
7cbac2e757 | ||
|
|
1012381d3d | ||
|
|
6de96f62df | ||
|
|
e2ed2bdbf6 | ||
|
|
e248c54aa5 | ||
|
|
1c9635edad | ||
|
|
25cf6c52e4 | ||
|
|
7859ef6145 | ||
|
|
6efaa4ac7e | ||
|
|
19e17a8431 | ||
|
|
c865ae9734 | ||
|
|
6959f74073 | ||
|
|
9f4d21e976 | ||
|
|
719f39c01c | ||
|
|
25bb9c7320 | ||
|
|
a160e16abd | ||
|
|
ec31841aa5 | ||
|
|
3e249902c4 | ||
|
|
21bb766f06 | ||
|
|
a883151400 | ||
|
|
3c7fe82cbd | ||
|
|
899fc09704 | ||
|
|
daa45a44b9 | ||
|
|
52494628c3 | ||
|
|
dbf5d21b72 | ||
|
|
441bd4dfbf | ||
|
|
8452f05dda | ||
|
|
0d2325e646 | ||
|
|
90329d3d73 | ||
|
|
ca83a142f8 | ||
|
|
d3b2d3d5c5 | ||
|
|
255177487c | ||
|
|
ed0b75bcde | ||
|
|
06500a7909 | ||
|
|
6e16bf68ae | ||
|
|
ad22480117 | ||
|
|
2fcd09072f | ||
|
|
f0c785b196 | ||
|
|
84fe76bada | ||
|
|
b5ed8f59a7 | ||
|
|
52526f90d7 | ||
|
|
99e6e81e65 | ||
|
|
5af824f5cf | ||
|
|
2166bb2e88 | ||
|
|
ae884c0200 | ||
|
|
423ec7d08a | ||
|
|
914cd6ecb2 | ||
|
|
f221c82859 | ||
|
|
d57be723e6 | ||
|
|
777554809a | ||
|
|
f2b63fa4a8 | ||
|
|
4d06ce7454 | ||
|
|
8da6ec5fa3 | ||
|
|
2a5a8b498d | ||
|
|
a9b7d7d793 | ||
|
|
6578cf5dd7 | ||
|
|
2e21e8a43c | ||
|
|
176e1fd9d4 | ||
|
|
c3b274b18f | ||
|
|
8e134918fb | ||
|
|
2b531d2a1f | ||
|
|
87317dd54a | ||
|
|
618548c88d | ||
|
|
b62f42006c | ||
|
|
c275174f27 | ||
|
|
af4ed295e0 | ||
|
|
7614a03ea8 | ||
|
|
d9527726b6 | ||
|
|
05f4099709 | ||
|
|
a20a649013 | ||
|
|
0e3e64105c | ||
|
|
b2cdb1a6ae | ||
|
|
812432db96 | ||
|
|
5b2c4aef2d | ||
|
|
b7ccf424f4 | ||
|
|
77d5db168b | ||
|
|
e80cd1ff55 | ||
|
|
4ff25a21f6 | ||
|
|
f184a71360 | ||
|
|
fc38a9853d | ||
|
|
6023efed41 | ||
|
|
2abac6ce5c | ||
|
|
53c7705c36 | ||
|
|
7059ab65d6 | ||
|
|
e133988b36 | ||
|
|
9af27e7964 | ||
|
|
a57b3835fb | ||
|
|
451cbb809e | ||
|
|
fd1b64393d | ||
|
|
ed875a35b4 | ||
|
|
e85b0c2122 | ||
|
|
f5b192f55f | ||
|
|
ff86d5381d | ||
|
|
b63ac4addb | ||
|
|
3e1a66d617 | ||
|
|
93ed5a8cae | ||
|
|
2e6e8807be | ||
|
|
1ed36fabdb | ||
|
|
8dc40ee379 | ||
|
|
db4c7c89e3 | ||
|
|
f9bc7cc746 | ||
|
|
8f87ed65f9 | ||
|
|
39c37631f3 | ||
|
|
d0fb291c4e | ||
|
|
793523cbe9 | ||
|
|
6da4dd9ecc | ||
|
|
79892af8f8 | ||
|
|
b86790c854 | ||
|
|
c8f18c8c85 | ||
|
|
b19ecb4482 | ||
|
|
ba9af55aca | ||
|
|
35d76b3520 | ||
|
|
a5a0326092 | ||
|
|
a05833f845 | ||
|
|
c5deb60510 | ||
|
|
ff2ff89e3e | ||
|
|
35a346a674 | ||
|
|
1217a95c52 | ||
|
|
d025b4a0c3 | ||
|
|
2cab50f920 | ||
|
|
b049278dde | ||
|
|
93335e74cb | ||
|
|
2833a7b66e | ||
|
|
0d05b960f7 | ||
|
|
9fd64a9209 | ||
|
|
1637d26de3 | ||
|
|
525ff9b75e | ||
|
|
42e7932f59 | ||
|
|
98f40abfc3 | ||
|
|
55cd13ed4e | ||
|
|
7cb113fcbc | ||
|
|
ddbb999194 | ||
|
|
1db96829ed | ||
|
|
3498dea18c | ||
|
|
97a8c87490 | ||
|
|
fa72e09840 | ||
|
|
d8cad710a5 | ||
|
|
f91dcc33d3 | ||
|
|
e5f524ec56 | ||
|
|
f9b13cbc7f | ||
|
|
c7e0ba68f6 | ||
|
|
220262d192 | ||
|
|
1f860ecba6 | ||
|
|
69a13b71ea | ||
|
|
0a27afe6ee | ||
|
|
a3de021cd2 | ||
|
|
4dcbe78e83 | ||
|
|
84a8e8cbf6 | ||
|
|
e4b2b3d06b | ||
|
|
000a2ea00c | ||
|
|
89de91301e | ||
|
|
6be84bfa73 | ||
|
|
6cd79e7237 | ||
|
|
11d73173b8 | ||
|
|
9b3d62b765 | ||
|
|
2bdff53e68 | ||
|
|
8af5f9c28e | ||
|
|
9f71abf978 | ||
|
|
3fc2d3c1d9 | ||
|
|
c0c8db6dcc | ||
|
|
27249c0bb4 | ||
|
|
62e9684542 | ||
|
|
74b006cb0b | ||
|
|
77b33f11ab | ||
|
|
0aba638e6e | ||
|
|
e82522349f | ||
|
|
e520700260 | ||
|
|
3ec335f3a6 | ||
|
|
0f212e4dd1 | ||
|
|
c263654c88 | ||
|
|
874e3f24a6 | ||
|
|
b14343f3cf | ||
|
|
732b50dea7 | ||
|
|
be3bbe9b61 | ||
|
|
51211bbba0 | ||
|
|
5d1ff1c912 | ||
|
|
ea1be4fc50 | ||
|
|
7cc05f0d92 | ||
|
|
54606f3c21 | ||
|
|
a5d1705930 | ||
|
|
bfc0fb6c88 | ||
|
|
d1d4452217 | ||
|
|
2166a434a3 | ||
|
|
1053fa18e1 | ||
|
|
fa147d467e | ||
|
|
3f61598d6c | ||
|
|
9bf3724ce6 | ||
|
|
c2f27a4deb | ||
|
|
b6b99dde02 | ||
|
|
1fd0f4a8fe | ||
|
|
67d39737a4 | ||
|
|
aef4fe29a3 | ||
|
|
34c0677c45 | ||
|
|
3cb4a64b47 | ||
|
|
0db0375a5e | ||
|
|
47e6bdc644 | ||
|
|
66c2e27711 | ||
|
|
72387873b4 | ||
|
|
59017bc0bd | ||
|
|
2dde114d3d | ||
|
|
9e89904f03 | ||
|
|
56d0aca254 | ||
|
|
239710cebf | ||
|
|
1eaad617cb | ||
|
|
3c21994adc | ||
|
|
d15d14e197 | ||
|
|
e32694dc79 | ||
|
|
6ec8124287 | ||
|
|
2222adfc10 | ||
|
|
fcc2377657 | ||
|
|
1704ac4ae1 | ||
|
|
666e4348e0 | ||
|
|
9b22f279bc | ||
|
|
0835de983b | ||
|
|
1a892d58fc | ||
|
|
73a3cce4a4 | ||
|
|
d5ef4774fa | ||
|
|
c5bd4239a4 | ||
|
|
634e811888 | ||
|
|
3204998fcb | ||
|
|
12e428733a | ||
|
|
9cc6ad09a9 | ||
|
|
84abb5962e | ||
|
|
4bba55d2dc | ||
|
|
b4cabad44e | ||
|
|
28cc0f9e3b | ||
|
|
95a2cc18fe | ||
|
|
8e315a9859 | ||
|
|
89adcf4f4e | ||
|
|
3a6c5e41c9 | ||
|
|
86ed24b94c | ||
|
|
c792c471c3 | ||
|
|
e371cc2c3c | ||
|
|
ccf218c8f0 | ||
|
|
0d7fc0a573 | ||
|
|
74cacd5209 | ||
|
|
bb79cf2a87 | ||
|
|
28451df1a8 | ||
|
|
38e288f62a | ||
|
|
905f908450 | ||
|
|
672171fd0c | ||
|
|
520660ecbc | ||
|
|
06acb5faf2 | ||
|
|
d43fa03f05 | ||
|
|
baed1aaf92 | ||
|
|
cc229e803c | ||
|
|
d6b1728c23 | ||
|
|
bc5dcc359c | ||
|
|
ced07e1d6b | ||
|
|
cffffd9591 | ||
|
|
b8766e263f | ||
|
|
fc426d5764 | ||
|
|
056d2381cd | ||
|
|
2932a0ec5f | ||
|
|
d3d85a3fcf | ||
|
|
7a1feaa897 | ||
|
|
5f3cf72cc6 | ||
|
|
cae980788e | ||
|
|
df763b8765 | ||
|
|
365085809e | ||
|
|
3ee7998261 | ||
|
|
6fb9ed8312 | ||
|
|
89f79c35f5 | ||
|
|
6bdd4b2670 | ||
|
|
acd79d19e2 | ||
|
|
674d4a957d | ||
|
|
bdbf264771 | ||
|
|
8f17873da2 | ||
|
|
b0cac776ee | ||
|
|
625dba4d85 | ||
|
|
261b72d0fc | ||
|
|
b5b167ef6d | ||
|
|
66d21b24cd | ||
|
|
5a084ea3cc | ||
|
|
486944fa4c | ||
|
|
b63a76d298 | ||
|
|
31045039c0 | ||
|
|
6f5d1104aa | ||
|
|
3c9660203b | ||
|
|
29e1423f84 | ||
|
|
e42e67e259 | ||
|
|
ed018282c4 | ||
|
|
fbe015758c | ||
|
|
7e24a81764 | ||
|
|
9ab77e90fe | ||
|
|
ae3ed699db | ||
|
|
0c22a9753e | ||
|
|
a447f6b723 | ||
|
|
a8ef614b81 | ||
|
|
9025e8bfa8 | ||
|
|
722f4e175d | ||
|
|
1ad6e5a15f | ||
|
|
3554572db7 | ||
|
|
f1abff962f | ||
|
|
f05941fbc4 | ||
|
|
237c46d5a0 | ||
|
|
76cfb69d9f | ||
|
|
7610df0fbb | ||
|
|
8bc935aa62 | ||
|
|
24587fab9c | ||
|
|
0248475473 | ||
|
|
d2fa5c4b12 | ||
|
|
c60c0cb6e0 | ||
|
|
cdf1112666 | ||
|
|
d861bb2e34 | ||
|
|
006849a3d5 | ||
|
|
a3c1d06eba | ||
|
|
4bd1e7a2bc | ||
|
|
68643f3118 | ||
|
|
560dfc8ae6 | ||
|
|
b0459e096b | ||
|
|
2a0dfc4587 | ||
|
|
2dcd5f94fb | ||
|
|
13685d03e1 | ||
|
|
278df9025a | ||
|
|
cb608406f8 | ||
|
|
f4a55d03d3 | ||
|
|
d3b6b8127c | ||
|
|
bc1f9f8a28 | ||
|
|
9a5c9aea75 | ||
|
|
f1004c6db2 | ||
|
|
7708c64576 | ||
|
|
0527b8c981 | ||
|
|
13f89e2fcc | ||
|
|
69a0a473a6 | ||
|
|
4ab82d7e01 | ||
|
|
4be209e286 | ||
|
|
8b10325895 | ||
|
|
70bf600247 | ||
|
|
d42e06d48b | ||
|
|
9c080b6790 | ||
|
|
033257b03b | ||
|
|
39d8bcdfc2 | ||
|
|
2ddcb4e2b7 | ||
|
|
d972718a53 | ||
|
|
6abed8dd53 | ||
|
|
e74e697b45 | ||
|
|
26c59e8565 | ||
|
|
a5e83c4f23 | ||
|
|
900c4bbd2e | ||
|
|
947ec3edc2 | ||
|
|
957f10d9f1 | ||
|
|
89aa54dff8 | ||
|
|
bb76530e4b | ||
|
|
011e2cc1e3 | ||
|
|
4c594f8964 | ||
|
|
1fcfcf2392 | ||
|
|
6311abff81 | ||
|
|
ed2da57475 | ||
|
|
778ccd4805 | ||
|
|
327c35252f | ||
|
|
5e7af2fba4 | ||
|
|
dce15bc579 | ||
|
|
d5e32db954 | ||
|
|
bdfa83592b | ||
|
|
23e473b688 | ||
|
|
0dfd3a0ae0 | ||
|
|
d107092540 | ||
|
|
c2f379d3b3 | ||
|
|
57b70300f5 | ||
|
|
eeba86f9c5 | ||
|
|
e0d68e60ec | ||
|
|
254248486b | ||
|
|
1b57cc6d35 | ||
|
|
77234f256d | ||
|
|
795d31d2db | ||
|
|
f3f10fd9bd | ||
|
|
7100b4be8d | ||
|
|
b1a7200d1b | ||
|
|
5d8bb541c6 | ||
|
|
b51c59b23a | ||
|
|
2cd434e861 | ||
|
|
1599eb9629 | ||
|
|
8ef7481858 | ||
|
|
344d478b3f | ||
|
|
39b7e27aa6 | ||
|
|
b1876b4f77 | ||
|
|
db3b41d1ba | ||
|
|
02b5d14d0f | ||
|
|
0120044c96 | ||
|
|
ad6304e857 | ||
|
|
7cba84b8cf | ||
|
|
5a9a4be163 | ||
|
|
4d1a31d3c9 | ||
|
|
6e3ceec4e5 | ||
|
|
bc7d3c0af8 | ||
|
|
519ddee092 | ||
|
|
3e0fcc5b8b | ||
|
|
b1972985c4 | ||
|
|
51c42e9257 | ||
|
|
86dcbcc671 | ||
|
|
3b7cd9d84f | ||
|
|
1073ec6214 | ||
|
|
14a5e42a63 | ||
|
|
b4564a86b4 | ||
|
|
03386a61e9 | ||
|
|
8bb2623360 | ||
|
|
ab0e4188b3 | ||
|
|
42c853dbf4 | ||
|
|
ce48a1793b | ||
|
|
6177543d98 | ||
|
|
9697bfa817 | ||
|
|
70425ab5c8 | ||
|
|
7cccb451d2 | ||
|
|
a39fb9d551 | ||
|
|
8f7cdc6e4f | ||
|
|
8f7e365b03 | ||
|
|
64735e523f | ||
|
|
f126610219 | ||
|
|
2caef539ce | ||
|
|
468fb87749 | ||
|
|
4f4808ff15 | ||
|
|
e6bbca7df1 | ||
|
|
e7d1095be2 | ||
|
|
a08d5ce6e5 | ||
|
|
fec2f5578d | ||
|
|
4869e30914 | ||
|
|
e1f31765e7 | ||
|
|
a3668defa8 | ||
|
|
765ff9fa32 | ||
|
|
dd04177f83 | ||
|
|
2e2ab6bffc | ||
|
|
934cacfc1b | ||
|
|
9800fd8f11 | ||
|
|
3e84996788 | ||
|
|
5a3f55d774 | ||
|
|
dbddc314a6 | ||
|
|
c98f875811 | ||
|
|
29a1ffb3b8 | ||
|
|
17770ad4c9 | ||
|
|
cc9ed435eb | ||
|
|
27a723b453 | ||
|
|
af6c9b6bd2 | ||
|
|
2d3bbecb05 | ||
|
|
51e4cb15b4 | ||
|
|
5ce91a027c | ||
|
|
3cb337e7ec | ||
|
|
c29f92f05b | ||
|
|
01903cc6d2 | ||
|
|
fff7a6bc9e | ||
|
|
678c67622d | ||
|
|
2a6aec94fb | ||
|
|
bc52f33e9c | ||
|
|
006beeb5f9 | ||
|
|
ff85b3c4c9 | ||
|
|
6c7b2b17dc | ||
|
|
131de6661c | ||
|
|
d416f31801 | ||
|
|
8885a9e3e5 | ||
|
|
27e100f4ee | ||
|
|
40dc49bd63 | ||
|
|
989509dc07 | ||
|
|
9c3f5fbcd2 | ||
|
|
0917f66cb2 | ||
|
|
66c56df7dc | ||
|
|
b5fdfa2604 | ||
|
|
a0d4a3c84d | ||
|
|
d8374b2f49 | ||
|
|
a2a2162f48 | ||
|
|
d845d094db | ||
|
|
81e805fcb9 | ||
|
|
81283eeb84 | ||
|
|
60069d0a28 | ||
|
|
7c0561d17f | ||
|
|
45ac10b215 | ||
|
|
47f5943cf7 | ||
|
|
73d30242c9 | ||
|
|
5a4e33a02d | ||
|
|
edbbbec8f3 | ||
|
|
f8811f27a0 | ||
|
|
ad6138a14e | ||
|
|
e6fec67ce9 | ||
|
|
08a09fab9c | ||
|
|
1bf06bc656 | ||
|
|
cf46808557 | ||
|
|
df5a8656f6 | ||
|
|
b41f00515b | ||
|
|
2b22b49f83 | ||
|
|
4c0eda95c6 | ||
|
|
92e4644d60 | ||
|
|
11b67b15e4 | ||
|
|
565cd59f13 | ||
|
|
f4643c7b52 | ||
|
|
a292c2841c | ||
|
|
edd57a89c1 | ||
|
|
0d3bc96672 | ||
|
|
cc96d21da6 | ||
|
|
ed4e07907f | ||
|
|
da6e4be815 | ||
|
|
391cba18b6 | ||
|
|
49f55cea48 | ||
|
|
fc361841b0 | ||
|
|
5db493df1c | ||
|
|
ca317f01db | ||
|
|
2687830623 | ||
|
|
4d9603ae7b | ||
|
|
61087c9406 | ||
|
|
4d6251be37 | ||
|
|
18b817c284 | ||
|
|
61649b1428 | ||
|
|
c9f82be54c | ||
|
|
416dc44d05 | ||
|
|
97712bfe96 | ||
|
|
f923a62f54 | ||
|
|
a41c5ddc62 | ||
|
|
564f248fe0 | ||
|
|
e712034c0f | ||
|
|
ca88298b76 | ||
|
|
5ac21f993e | ||
|
|
5eb12c9d28 | ||
|
|
e12bea4b27 | ||
|
|
cad9521049 | ||
|
|
0847002c96 | ||
|
|
c3783533e9 | ||
|
|
85a7e935b2 | ||
|
|
43658264a8 | ||
|
|
b00f5c5a1c | ||
|
|
7af758bf88 | ||
|
|
c1a0be2402 | ||
|
|
1d4bcd4e0f | ||
|
|
4450ef822e | ||
|
|
ef74c7ca11 | ||
|
|
27eadc5587 | ||
|
|
0b03de66e7 | ||
|
|
cfcea4affb | ||
|
|
4e13170123 | ||
|
|
fe4cf94b62 | ||
|
|
f4233d7615 | ||
|
|
6e5bebfe81 | ||
|
|
21b0e09837 | ||
|
|
7b243dff03 | ||
|
|
fbe67df069 | ||
|
|
ea82c8cce3 | ||
|
|
e5322fb8e4 | ||
|
|
c5e6c5819b | ||
|
|
c3975dfc68 | ||
|
|
ddf7ca78ee | ||
|
|
71d8b6c9bf | ||
|
|
f11cd65494 | ||
|
|
59f7d49b80 | ||
|
|
447ae3f38f | ||
|
|
b2c4f935e7 | ||
|
|
5a85385db6 | ||
|
|
6dcd9e9014 | ||
|
|
eebfe02163 | ||
|
|
19294f5435 | ||
|
|
ef17cd86a8 | ||
|
|
ac62a336ea | ||
|
|
bdb7454737 | ||
|
|
4e9082f4d9 | ||
|
|
7daa4b4c3b | ||
|
|
7be13bebfc | ||
|
|
aea75f2beb | ||
|
|
cb59f86d4c | ||
|
|
8f340c1cde | ||
|
|
76659b613b | ||
|
|
79c5428da2 | ||
|
|
ca2d137d52 | ||
|
|
9789f76f64 | ||
|
|
df6eee1084 | ||
|
|
09461fb3c8 | ||
|
|
5607f2d379 | ||
|
|
3b7f556887 | ||
|
|
e84633de13 | ||
|
|
2b2267c46e | ||
|
|
37090716d3 | ||
|
|
3535ce1b04 | ||
|
|
dda865b6f2 | ||
|
|
9115a7193d | ||
|
|
6d347bcec0 | ||
|
|
b96d26acc2 | ||
|
|
5dbfe04a9a | ||
|
|
7e11b4e03d | ||
|
|
e2eaf9718c | ||
|
|
2576abe06e | ||
|
|
07a15d7b91 | ||
|
|
c5fdb3e2f6 | ||
|
|
0b068dab6a | ||
|
|
b4497bcb08 | ||
|
|
3baea7752f | ||
|
|
05cce3dbab | ||
|
|
d23dca2ef1 | ||
|
|
bcf93e230e | ||
|
|
4bd39b9bb1 | ||
|
|
8797cdb27e | ||
|
|
ab259fa519 | ||
|
|
585ca4160d | ||
|
|
5fe1ebdd45 | ||
|
|
a0ba289848 | ||
|
|
b6f0aa3914 | ||
|
|
a47eef3283 | ||
|
|
e0bcf19340 | ||
|
|
fa9305626b | ||
|
|
a573465e41 | ||
|
|
9527d6ed22 | ||
|
|
3ef60e0391 | ||
|
|
6028115e52 | ||
|
|
fd0dc3b330 | ||
|
|
3ebcadfad4 | ||
|
|
301b34a923 | ||
|
|
f963d266a2 | ||
|
|
309957a6cf | ||
|
|
47094f84d7 | ||
|
|
68c86bf672 | ||
|
|
e6782f4563 | ||
|
|
1b2b19381b | ||
|
|
d495f397b0 | ||
|
|
f1a58de348 | ||
|
|
01c6417425 | ||
|
|
6ae186951b | ||
|
|
8d77ff9af7 | ||
|
|
eeb5e22e9a | ||
|
|
fc5284cc88 | ||
|
|
6a7eb132bd | ||
|
|
f8519584d2 | ||
|
|
58b307411e | ||
|
|
c7dd3cc70e | ||
|
|
dc62dbe022 | ||
|
|
e50ddd6237 | ||
|
|
6a55dbc55e | ||
|
|
4e7cf460ec | ||
|
|
87dd8d535a | ||
|
|
ce6010ecd3 | ||
|
|
ac5f146187 | ||
|
|
14b2697a52 | ||
|
|
a4078e10e9 | ||
|
|
06796aa7c0 | ||
|
|
62d4be0185 | ||
|
|
2e0536ea2d | ||
|
|
94c7408b5b | ||
|
|
1186b4a314 | ||
|
|
1cccf01bf2 | ||
|
|
8f7cda3c2c | ||
|
|
c95ac13946 | ||
|
|
ee1ce36045 | ||
|
|
a4f22d8b42 | ||
|
|
2afce7cf54 | ||
|
|
6150721951 | ||
|
|
61b3ef0205 | ||
|
|
06108ffee3 | ||
|
|
726b309085 | ||
|
|
ff14b55ea5 | ||
|
|
bb82eb9219 | ||
|
|
abc43f80e0 | ||
|
|
f6343dc1d1 | ||
|
|
182e1863f4 | ||
|
|
0a8d4ad587 | ||
|
|
48e49ac42b | ||
|
|
1579d58edf | ||
|
|
61cb21188d | ||
|
|
49d50c02d4 | ||
|
|
b7f7e6dc60 | ||
|
|
e49bb4e527 | ||
|
|
44a9724b2a | ||
|
|
7af4a376a2 | ||
|
|
77e69efe19 | ||
|
|
da8061ed52 | ||
|
|
90d65573b5 | ||
|
|
838180a498 | ||
|
|
5ebab2fe28 | ||
|
|
0ff0004d42 | ||
|
|
b931b92773 | ||
|
|
ad08e20085 | ||
|
|
c1c18c465d | ||
|
|
a71fc07ba5 | ||
|
|
89c4839fa5 | ||
|
|
41ee43740e | ||
|
|
179c215b15 | ||
|
|
9100e8ecc0 | ||
|
|
fb213e5818 | ||
|
|
ac12e3fb5c | ||
|
|
caa78b11dd | ||
|
|
61586a4185 | ||
|
|
a7df5248c9 | ||
|
|
cf53ec9da8 | ||
|
|
46e966fb7f | ||
|
|
01e38ed4ca | ||
|
|
01459061ee | ||
|
|
3ef105e077 | ||
|
|
decebe3d2e | ||
|
|
2a832777a7 | ||
|
|
6a718c4384 | ||
|
|
a1face76b7 | ||
|
|
8104f71162 | ||
|
|
1ce2a517aa | ||
|
|
53c7836a7a | ||
|
|
160b6e8a51 | ||
|
|
b1f6284813 | ||
|
|
d104ebb6f5 | ||
|
|
017713c435 | ||
|
|
644ca2b472 | ||
|
|
453ff91065 | ||
|
|
8b79ec0e5a | ||
|
|
95b7858c8f | ||
|
|
5ef7e5462e | ||
|
|
54a2655bc2 | ||
|
|
785a066ebb | ||
|
|
fa60b182a3 | ||
|
|
dbc965de3c | ||
|
|
77814b791d | ||
|
|
ddf12d43c3 | ||
|
|
f5b59b4268 | ||
|
|
58e14f3bb3 | ||
|
|
1c02166662 | ||
|
|
0e5c29269f | ||
|
|
9a349a3d55 | ||
|
|
d5e01adbf9 | ||
|
|
61bc01ae12 | ||
|
|
c3568de8b3 | ||
|
|
eca5ac7611 | ||
|
|
a8e0cb2e0b | ||
|
|
799bb5faeb | ||
|
|
0b5c0722e0 | ||
|
|
3964e4522e | ||
|
|
bdb299e085 | ||
|
|
349ca81cc9 | ||
|
|
77a323aaae | ||
|
|
8be08b5e73 | ||
|
|
b53b05496d | ||
|
|
bd2dba7f15 | ||
|
|
81e0e2672d | ||
|
|
bd65c6e6d0 | ||
|
|
396d7b07d8 | ||
|
|
55184162d1 | ||
|
|
beffd0864a | ||
|
|
5ba7c31e4c | ||
|
|
b26129db72 | ||
|
|
1693a57845 | ||
|
|
7ea1ba168d | ||
|
|
7fe530e82c | ||
|
|
7e4ae26b8f | ||
|
|
963e1d58cb | ||
|
|
cc6f683590 | ||
|
|
da3af124ba | ||
|
|
b342cf1edf | ||
|
|
a1989b3931 | ||
|
|
80bdce970a | ||
|
|
7cf80a468d | ||
|
|
bee632e1e2 | ||
|
|
7901e12b0a | ||
|
|
8979a3cf02 | ||
|
|
9025119f8d | ||
|
|
fdaa63c132 | ||
|
|
9e825e927c | ||
|
|
4929d63073 | ||
|
|
5b0dd33fa8 | ||
|
|
5203a1e868 | ||
|
|
27645c234a | ||
|
|
5280d994a2 | ||
|
|
12f43a5334 | ||
|
|
4f0399180c | ||
|
|
ea11d34254 | ||
|
|
a222f2be98 | ||
|
|
a48a25e236 | ||
|
|
e4f9be5af8 | ||
|
|
473d8a8d8c | ||
|
|
41ea820ae0 | ||
|
|
0558ad689a | ||
|
|
3199aa438a | ||
|
|
c3f630c27f | ||
|
|
cf3a21a712 | ||
|
|
d8504a3001 | ||
|
|
a2b07d5edd | ||
|
|
13a6a2c335 | ||
|
|
e19be192bd | ||
|
|
c32216c9e5 | ||
|
|
904082a86c | ||
|
|
f56a20d697 | ||
|
|
8275e036c9 | ||
|
|
903e480130 | ||
|
|
30fd0e7bff | ||
|
|
fbdef6eea0 | ||
|
|
5a04ce9629 | ||
|
|
693e2aaae7 | ||
|
|
43deeaf5fb | ||
|
|
cbba7727f2 | ||
|
|
52e1665e72 | ||
|
|
66ea770287 | ||
|
|
18efa5d742 | ||
|
|
802212bbdc | ||
|
|
7f59fb917c | ||
|
|
6ebaec31a5 | ||
|
|
14f409ff56 | ||
|
|
8ffd0b13a3 | ||
|
|
969873441e | ||
|
|
282ac6d8ab | ||
|
|
1e3c96b14f | ||
|
|
b14fab8aa7 | ||
|
|
be33b1be60 | ||
|
|
06288e798e | ||
|
|
0de7d84862 | ||
|
|
58afce517a | ||
|
|
250e987fd9 | ||
|
|
87ba2abc9a | ||
|
|
716fd0b938 | ||
|
|
893fc4c168 | ||
|
|
6f5cf8506f | ||
|
|
c808cb0a1c | ||
|
|
5f677a86a7 | ||
|
|
11540f8cd9 | ||
|
|
9d6ccdcab1 | ||
|
|
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 | ||
|
|
1305acb849 | ||
|
|
1e1e70d9a6 | ||
|
|
942bfe88c1 | ||
|
|
63038d3603 | ||
|
|
0f5fdbc772 | ||
|
|
8640176a07 | ||
|
|
e49838437e | ||
|
|
7c5e9bae2a | ||
|
|
08a2d771cd | ||
|
|
36f8ca1073 | ||
|
|
1e288c31af | ||
|
|
e8144128d0 | ||
|
|
e5421effd5 | ||
|
|
48a2bb9fe4 | ||
|
|
67d100796d | ||
|
|
0f91bf0e98 | ||
|
|
1aaee7526b | ||
|
|
fd0ce52cf6 | ||
|
|
0fd973f58b | ||
|
|
a99856bec0 | ||
|
|
eac195ec18 | ||
|
|
ab5d64ff12 | ||
|
|
5f017b2b85 | ||
|
|
bb6ec3b5c9 | ||
|
|
d870264230 | ||
|
|
aad97c936d | ||
|
|
b78835f195 | ||
|
|
a480e76353 | ||
|
|
8294c78db4 | ||
|
|
c06c74e460 | ||
|
|
3ea426d45f | ||
|
|
bc74543ca2 | ||
|
|
c69966035b | ||
|
|
78b1e4f570 | ||
|
|
815e2af3cf | ||
|
|
5bc1603f43 | ||
|
|
c48bf4089c | ||
|
|
fd67ea3036 | ||
|
|
006d1fadce | ||
|
|
a1206f6e3f | ||
|
|
be2d647d48 | ||
|
|
9d863c4dfa | ||
|
|
a1bef6248a | ||
|
|
6bb9f9e5e4 | ||
|
|
f69ad8b172 | ||
|
|
2f4a9c2c26 | ||
|
|
342d5a1aa1 | ||
|
|
6e2170156d | ||
|
|
a8a760fc41 | ||
|
|
8e54231a6c | ||
|
|
78fdd6bae7 | ||
|
|
89a8e6c362 | ||
|
|
03a6583032 | ||
|
|
a5206d2959 | ||
|
|
4e876511a3 | ||
|
|
09e190c822 | ||
|
|
d08e06ad53 | ||
|
|
1f40bb7ea8 | ||
|
|
3e1e62c03a | ||
|
|
6bc2493d8d | ||
|
|
0d00a30d19 | ||
|
|
c70e8dcf01 | ||
|
|
06c3a87929 | ||
|
|
5b11795dc3 | ||
|
|
9a6d4adfbe | ||
|
|
913df130e8 | ||
|
|
1222599c6c | ||
|
|
b51d677145 | ||
|
|
4eb516e072 | ||
|
|
451e6f9b86 | ||
|
|
151a4fa1c0 | ||
|
|
a025c187cb | ||
|
|
9550edab9b | ||
|
|
9ec72ee8c5 | ||
|
|
d93145fc1e | ||
|
|
087f357714 | ||
|
|
0fcce464bb | ||
|
|
44bb8658f0 | ||
|
|
be6bd8cc5d | ||
|
|
3812ac7d53 | ||
|
|
6e36ded34f | ||
|
|
07d33d817b | ||
|
|
28f7b483a8 | ||
|
|
722ff4dc5c | ||
|
|
13b7c9bfbb | ||
|
|
daaac98bef | ||
|
|
84e79abbaa | ||
|
|
2a340d09a4 | ||
|
|
db5c3f1bf2 | ||
|
|
705a9ad087 | ||
|
|
fa1a4a0307 | ||
|
|
ce1338cf20 | ||
|
|
28408bfa24 | ||
|
|
dc290f69ec | ||
|
|
dde93f5e8d | ||
|
|
d862d17770 | ||
|
|
58ca70ee1c | ||
|
|
ed1bc7fb25 | ||
|
|
7cb9787424 | ||
|
|
be78db1563 | ||
|
|
9dd337a16c | ||
|
|
c2b03e9d93 | ||
|
|
75d3a4f827 | ||
|
|
c2ad23996d | ||
|
|
348615671a | ||
|
|
6704120105 | ||
|
|
45a3ac3f66 | ||
|
|
5a1b9d1f90 | ||
|
|
355a2b076b | ||
|
|
82224de494 | ||
|
|
8a1c5a7b66 | ||
|
|
8feaae0e2d | ||
|
|
a0e1c07425 | ||
|
|
514dff0e2c | ||
|
|
10c883e432 | ||
|
|
b0c8d67d27 | ||
|
|
6601edadb2 | ||
|
|
9de7b285f0 | ||
|
|
c8471e5d11 | ||
|
|
8ff5f6022a | ||
|
|
3a4cd93629 | ||
|
|
ba2f290100 | ||
|
|
337bb03a00 | ||
|
|
b651bc80f9 | ||
|
|
b3c96419d4 | ||
|
|
a813b0cc9d | ||
|
|
a22de5e84a | ||
|
|
44d1f1457b | ||
|
|
22a954deed | ||
|
|
bad52779e3 | ||
|
|
8073fc7979 | ||
|
|
c7a464b0e2 | ||
|
|
34d8a77bc0 | ||
|
|
498e7df622 | ||
|
|
60587b59ee | ||
|
|
d1c1ac7fbb | ||
|
|
0622cc9766 | ||
|
|
20032e073f |
7
.flowconfig
Normal file
7
.flowconfig
Normal file
@@ -0,0 +1,7 @@
|
||||
[ignore]
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
||||
[options]
|
||||
26
.gitignore
vendored
26
.gitignore
vendored
@@ -17,7 +17,7 @@
|
||||
|
||||
# Ignore object files.
|
||||
*.o
|
||||
build
|
||||
build/
|
||||
tags
|
||||
bin/rippled
|
||||
Debug/*.*
|
||||
@@ -25,6 +25,7 @@ Release/*.*
|
||||
|
||||
# Ignore locally installed node_modules
|
||||
node_modules
|
||||
!test/node_modules
|
||||
|
||||
# Ignore tmp directory.
|
||||
tmp
|
||||
@@ -37,3 +38,26 @@ db/*.db-*
|
||||
rippled.cfg
|
||||
validators.txt
|
||||
test/config.js
|
||||
|
||||
# Ignore coverage files
|
||||
/lib-cov
|
||||
/src-cov
|
||||
/coverage.html
|
||||
/coverage
|
||||
|
||||
# Ignore IntelliJ files
|
||||
.idea
|
||||
|
||||
# Ignore npm-debug
|
||||
npm-debug.log
|
||||
|
||||
# Ignore dist folder, build for bower
|
||||
dist/
|
||||
|
||||
# Ignore flow output directory
|
||||
out/
|
||||
|
||||
# Ignore perf test cache
|
||||
scripts/cache
|
||||
|
||||
eslintrc
|
||||
|
||||
10
.gitmodules
vendored
10
.gitmodules
vendored
@@ -1,10 +0,0 @@
|
||||
[submodule "src/js/cryptojs"]
|
||||
path = src/js/cryptojs
|
||||
url = git://github.com/gwjjeff/cryptojs.git
|
||||
[submodule "src/js/sjcl"]
|
||||
path = src/js/sjcl
|
||||
url = git://github.com/bitwiseshiftleft/sjcl.git
|
||||
ignore = dirty
|
||||
[submodule "src/cpp/leveldb"]
|
||||
path = src/cpp/leveldb
|
||||
url = https://code.google.com/p/leveldb/
|
||||
4
.npmignore
Normal file
4
.npmignore
Normal file
@@ -0,0 +1,4 @@
|
||||
lib-cov
|
||||
coverage.html
|
||||
src
|
||||
dist/bower
|
||||
18
.travis.yml
Normal file
18
.travis.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.12"
|
||||
before_script:
|
||||
- npm install -g eslint
|
||||
- curl 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc' > ./eslintrc
|
||||
- eslint --reset -c ./eslintrc $(git --no-pager diff --name-only -M100% --diff-filter=AM --relative $(git merge-base FETCH_HEAD origin/HEAD) FETCH_HEAD | grep "\.js$")
|
||||
script: MOCHA_REPORTER=tap npm test --coverage
|
||||
after_success:
|
||||
- npm run coveralls
|
||||
notifications:
|
||||
email: false
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/d1ec4245f90231619d30
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
156
Gulpfile.js
Normal file
156
Gulpfile.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/* eslint-disable no-var, no-param-reassign */
|
||||
/* these eslint rules are disabled because gulp does not support babel yet */
|
||||
'use strict';
|
||||
var _ = require('lodash');
|
||||
var gulp = require('gulp');
|
||||
var gutil = require('gulp-util');
|
||||
var watch = require('gulp-watch');
|
||||
var plumber = require('gulp-plumber');
|
||||
var filelog = require('gulp-filelog');
|
||||
var cleanDest = require('gulp-clean-dest');
|
||||
var uglify = require('gulp-uglify');
|
||||
var rename = require('gulp-rename');
|
||||
var webpack = require('webpack');
|
||||
var bump = require('gulp-bump');
|
||||
var react = require('gulp-react');
|
||||
var flow = require('gulp-flowtype');
|
||||
var argv = require('yargs').argv;
|
||||
|
||||
var pkg = require('./package.json');
|
||||
|
||||
function webpackConfig(extension, overrides) {
|
||||
overrides = overrides || {};
|
||||
var defaults = {
|
||||
cache: true,
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: './build/',
|
||||
filename: ['ripple-', extension].join(pkg.version)
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader?optional=runtime'
|
||||
}]
|
||||
}
|
||||
};
|
||||
return _.assign({}, defaults, overrides);
|
||||
}
|
||||
|
||||
function logPluginError(error) {
|
||||
gutil.log(error.toString());
|
||||
}
|
||||
|
||||
gulp.task('build', function(callback) {
|
||||
webpack(webpackConfig('.js'), callback);
|
||||
});
|
||||
|
||||
gulp.task('build-min', ['build'], function() {
|
||||
return gulp.src(['./build/ripple-', '.js'].join(pkg.version))
|
||||
.pipe(uglify())
|
||||
.pipe(rename(['ripple-', '-min.js'].join(pkg.version)))
|
||||
.pipe(gulp.dest('./build/'));
|
||||
});
|
||||
|
||||
gulp.task('build-debug', function(callback) {
|
||||
var configOverrides = {debug: true, devtool: 'eval'};
|
||||
webpack(webpackConfig('-debug.js', configOverrides), callback);
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate a WebPack external for a given unavailable module which replaces
|
||||
* that module's constructor with an error-thrower
|
||||
*/
|
||||
|
||||
function buildUseError(cons) {
|
||||
return ('var {<CONS>:function(){throw new Error('
|
||||
+ '"Class is unavailable in this build: <CONS>")}}')
|
||||
.replace(new RegExp('<CONS>', 'g'), cons);
|
||||
}
|
||||
|
||||
gulp.task('build-core', function(callback) {
|
||||
var configOverrides = {
|
||||
cache: false,
|
||||
entry: './src/remote.js',
|
||||
externals: [{
|
||||
'./transaction': buildUseError('Transaction'),
|
||||
'./orderbook': buildUseError('OrderBook'),
|
||||
'./account': buildUseError('Account'),
|
||||
'./serializedobject': buildUseError('SerializedObject')
|
||||
}],
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin()
|
||||
]
|
||||
};
|
||||
webpack(webpackConfig('-core.js', configOverrides), callback);
|
||||
});
|
||||
|
||||
gulp.task('bower-build', ['build'], function() {
|
||||
return gulp.src(['./build/ripple-', '.js'].join(pkg.version))
|
||||
.pipe(rename('ripple.js'))
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-min', ['build-min'], function() {
|
||||
return gulp.src(['./build/ripple-', '-min.js'].join(pkg.version))
|
||||
.pipe(rename('ripple-min.js'))
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-debug', ['build-debug'], function() {
|
||||
return gulp.src(['./build/ripple-', '-debug.js'].join(pkg.version))
|
||||
.pipe(rename('ripple-debug.js'))
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-version', function() {
|
||||
gulp.src('./dist/bower/bower.json')
|
||||
.pipe(bump({version: pkg.version}))
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug',
|
||||
'bower-version']);
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('src/*', ['build-debug']);
|
||||
});
|
||||
|
||||
// To use this, each javascript file must have /* @flow */ on the first line
|
||||
gulp.task('typecheck', function() {
|
||||
return gulp.src('src/*.js')
|
||||
.pipe(flow({ // note: do not set the 'all' option, it is broken
|
||||
weak: true, // remove this after all errors are addressed
|
||||
killFlow: true
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task('strip', function() {
|
||||
return gulp.src('src/*.js')
|
||||
.pipe(watch('src/*.js'))
|
||||
.pipe(cleanDest('out')) // delete outdated output file before stripping
|
||||
.pipe(plumber()) // prevent an error in one file from ending build
|
||||
.pipe(react({stripTypes: true}).on('error', logPluginError))
|
||||
.pipe(filelog())
|
||||
.pipe(gulp.dest('out'));
|
||||
});
|
||||
|
||||
gulp.task('version-bump', function() {
|
||||
if (!argv.type) {
|
||||
throw new Error('No type found, pass it in using the --type argument');
|
||||
}
|
||||
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({type: argv.type}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('version-beta', function() {
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({version: pkg.version + '-beta'}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('default', ['build', 'build-debug', 'build-min']);
|
||||
418
HISTORY.md
Normal file
418
HISTORY.md
Normal file
@@ -0,0 +1,418 @@
|
||||
##0.12.7
|
||||
|
||||
+ [Improve performance of orderbook](https://github.com/ripple/ripple-lib/commit/c745faaaf0956ca98448a754b4fe97fb50574fc7)
|
||||
|
||||
+ [Remove Firefox warning about prototype overwrite](https://github.com/ripple/ripple-lib/commit/0c62fa21123b220b066871e1c41a3b4fe6f51885)
|
||||
|
||||
+ [Fix compare bug in Amount class](https://github.com/ripple/ripple-lib/commit/806547dd154e1b0bf252e8a74ad3ac6aa8a97660)
|
||||
|
||||
##0.12.6
|
||||
|
||||
+ [Fix webpack require failure due to "./" notation](https://github.com/ripple/ripple-lib/commit/8d9746d7b10be203ee613df523c2522012ff1baf)
|
||||
|
||||
##0.12.15
|
||||
|
||||
+ [Add offer autobridging](https://github.com/ripple/ripple-lib/commit/c7bbce83719c1e8c6a4fae5ca850e7515db1a4a5)
|
||||
|
||||
+ [Prevent crash when listening for "model" events on the OrderBook class](https://github.com/ripple/ripple-lib/commit/5824c3cb7cb6bd834d6e037f69943aebf3d83351)
|
||||
|
||||
+ [Fix empty order edgecase](https://github.com/ripple/ripple-lib/commit/64809d9ae23dc24f47accd4b4788b48f49880d3e)
|
||||
|
||||
+ [Fix AutobridgeCalculator (RT-3445)](https://github.com/ripple/ripple-lib/commit/1fff5ea6dcbcee856536df26f3b9cf1aec3c3b55)
|
||||
|
||||
+ [Update sjcl and delete custom ripemd160, montgomery, and jacobi](https://github.com/ripple/ripple-lib/commit/50cda426eb83599c38c0b725e1524a01fc415da2)
|
||||
|
||||
+ [Fix transaction summary for transactions that fail with remoteError](https://github.com/ripple/ripple-lib/commit/5e714f6143464d7912f42537acaa553b88eaf6dc)
|
||||
|
||||
+ [Fix serializedobject append for excessively large bytes length](https://github.com/ripple/ripple-lib/commit/e93f1ab6f4aaad347450aee75a169af0faa2121c)
|
||||
|
||||
+ [Switch to sjcl npm module](https://github.com/ripple/ripple-lib/commit/9a502580fd89ec6a9aa55f4e5847f6a4a2cb5bba)
|
||||
|
||||
+ [Add babel transpiler](https://github.com/ripple/ripple-lib/commit/398f8d001f758bf575b959537a17e79e4042d17b)
|
||||
|
||||
+ [Remove unused float.js and wallet.js](https://github.com/ripple/ripple-lib/commit/d4a4b5f4fbbf09677a59ce81bace35c6426a2fda)
|
||||
|
||||
+ [Remove config singleton to reduce global state](https://github.com/ripple/ripple-lib/commit/c655c2a20ee5d150a4b5a1b6717b9fb81f636025)
|
||||
|
||||
##0.12.4
|
||||
|
||||
+ [Improve entropy security](https://github.com/ripple/ripple-lib/commit/c7ba822320880037796f57876d1abb4e525648ed)
|
||||
|
||||
+ [Remove unused crypt.js file](https://github.com/ripple/ripple-lib/commit/1f68eba1461bca03a4d22872450d15ae5a185334)
|
||||
|
||||
##0.12.3
|
||||
|
||||
+ [Add getLedgerSequence to Remote](https://github.com/ripple/ripple-lib/commit/d09548d04d3238fca653d482ec1d5faa7254559a)
|
||||
|
||||
+ [Improve randomness when generating ECDSA signatures](https://github.com/ripple/ripple-lib/commit/fe7e30b737ead6e71adfa466f5835ba546feab31)
|
||||
|
||||
+ [Improve SerializedObject.append performance](https://github.com/ripple/ripple-lib/commit/f7c35b118ebba549a64bcaa1a0629385ec6dbf6f)
|
||||
|
||||
+ [Add `Amount.scale`. Multiply an amount’s value by a scale factor](https://github.com/ripple/ripple-lib/commit/74dac97b368493056474468520f05671f458a69f)
|
||||
|
||||
|
||||
##0.12.2
|
||||
|
||||
+ [Check that stack trace is available, fixes logging in browser](https://github.com/ripple/ripple-lib/commit/53cae3a66d48e88e8a6bbb96d6489ce7b9e22975)
|
||||
|
||||
|
||||
##0.12.1
|
||||
|
||||
**Breaking Changes**
|
||||
|
||||
+ [Removed support for parsing native amounts in floating point format](https://github.com/ripple/ripple-lib/commit/e80cd1ff55deae9cd5b0ae85be957f86856b887e)
|
||||
|
||||
|
||||
**Changes**
|
||||
|
||||
+ [Fix taker pays funded calculation](https://github.com/ripple/ripple-lib/commit/5af824f5cf46c7b9caa58ee0a757bf854d26c8dc)
|
||||
|
||||
+ [Fix order funded amount calculation](https://github.com/ripple/ripple-lib/commit/b2cdb1a6aed968b1f306e8dadbd4b7ca37e5aa03)
|
||||
|
||||
+ [Fix handling of quality in order book](https://github.com/ripple/ripple-lib/commit/2a5a8b498da60df738ba18d5c265f34771e8a1af)
|
||||
|
||||
+ [Fix currency parsing of non-alphanumeric and no-currency currencies](https://github.com/ripple/ripple-lib/commit/2166bb2e88eae8d5f1aba77338f69e8a9edf6a6f)
|
||||
|
||||
+ [Add Amount.strict_mode for toggling range validation](https://github.com/ripple/ripple-lib/commit/b5ed8f59a7dab1a17491618b8d9193646c314fb4)
|
||||
|
||||
+ [Add filename and line number to log, use log.warn() for deprecations](https://github.com/ripple/ripple-lib/commit/90329d3d73f1a76675063655b407513e32dc048b)
|
||||
|
||||
+ [Add GlobalFreeze and NoFreeze flags](https://github.com/ripple/ripple-lib/commit/e2ed2bdbf6f01c7d4d690c2cf0b83fba94558dd7)
|
||||
|
||||
+ [Fix handling of falsy parameters in requestLedger](https://github.com/ripple/ripple-lib/commit/6023efed41b7812b3bab660a1c0dc9f0a21000b9)
|
||||
|
||||
+ [Fix Base:decode](https://github.com/ripple/ripple-lib/commit/719f39c01c6941d9a650aa94f95617793dd53ea0)
|
||||
|
||||
+ [Fix Amount: clone in ratio_human, product_human](https://github.com/ripple/ripple-lib/commit/19e17a8431550cf156b1ad669a19dedfe4e28e4a)
|
||||
|
||||
+ [Fix Amount.to_human for very small numbers](https://github.com/ripple/ripple-lib/commit/6abfa759aa09d68074ac558d96c4b126a7cd1719)
|
||||
|
||||
+ [Refactor base conversion](https://github.com/ripple/ripple-lib/commit/f2b63fa4a80663eb29472bc6bb1aea8159f1f205)
|
||||
|
||||
+ [Update binary transaction format](https://github.com/ripple/ripple-lib/commit/8e134918fb4c22983320a3102f955e4568bb1dfb)
|
||||
|
||||
+ [Add DefaultRipple account flag](https://github.com/ripple/ripple-lib/commit/3e249902c4cf25b4da5e75048c84ae391be83b10)
|
||||
|
||||
+ [Remove `Features` field requirement in `SetFee` transaction format](https://github.com/ripple/ripple-lib/commit/a20a649013646710c078d4ce1e210f87c7fe74fe)
|
||||
|
||||
+ [Remove `RegularKey` field requirement in `SetRegularKey` transaction format](https://github.com/ripple/ripple-lib/commit/c275174f27877ba8f389eb4efe969feb514d6e46)
|
||||
|
||||
|
||||
##0.12.0
|
||||
|
||||
**Breaking Changes**
|
||||
|
||||
+ REMOVED Remote storage interface
|
||||
+ REMOVED Remote `ping` configuration
|
||||
+ REMOVED Old/deprecated Remote server configuration (websocket_ip, websocket_port)
|
||||
+ REMOVED browser `online` reconnect listener
|
||||
- [Cleanup, deprecations - 2833a7b6](https://github.com/ripple/ripple-lib/commit/2833a7b66e696dab427464625077f9b93092d0d5)
|
||||
|
||||
+ Remove `jsbn` and use `bignumber.js` instead for big number math
|
||||
+ The `allow_nan` flag has been removed. Results for invalid amounts will always be `NaN`
|
||||
- [Refactor to use bignumber.js - d025b4a0](https://github.com/ripple/ripple-lib/commit/d025b4a0c3a98a6de27a1bee9573c85347bcd66b)
|
||||
- [Handle invalid input in parse_human - c8f18c8c](https://github.com/ripple/ripple-lib/commit/c8f18c8c8590b7b48e370e0325b6677b7720294f)
|
||||
- [Check for null in isNumber - b86790c8](https://github.com/ripple/ripple-lib/commit/b86790c8543c239a532fd7697d4652829019d385)
|
||||
- [Cleanup amount.js - d0fb291c](https://github.com/ripple/ripple-lib/commit/d0fb291c4e330193a244902156f1d74730da357d)
|
||||
|
||||
|
||||
**Changes**
|
||||
|
||||
+ [Add deprecation warnings to request constructors. The first argument to request constructor functions should be an object containing request properties](https://github.com/ripple/ripple-lib/commit/35d76b3520934285f80059c1badd6c522539104c)
|
||||
|
||||
+ [Fix taker_gets_funded exceeding offer.TakerGets](https://github.com/ripple/ripple-lib/commit/b19ecb4482b589d575382b7a5d0480b963383bb1)
|
||||
|
||||
+ [Fix unsymmetric memo serializing](https://github.com/ripple/ripple-lib/commit/1ed36fabdbd54f4d31078c2b0eaa3becc0fe2821)
|
||||
|
||||
+ [Fix IOU value passed to `Amount.from_json()`](https://github.com/ripple/ripple-lib/commit/fd1b64393dffb3d1819cd40b8d43df43a4db042d)
|
||||
|
||||
+ [Update transaction binary parsing to account for XRP delivered amounts](https://github.com/ripple/ripple-lib/commit/35a346a674e6ee1e1e495db93700d55984efc7dd)
|
||||
|
||||
+ [Bumped dependencies](https://github.com/ripple/ripple-lib/commit/f9bc7cc746b44b24b61bbe260ae2e9d9617286da)
|
||||
|
||||
|
||||
|
||||
##0.11.0
|
||||
|
||||
+ [Track the funded status of an order based on cumulative account orders](https://github.com/ripple/ripple-lib/commit/67d39737a4d5e0fcd9d9b47b9083ee00e5a9e652) and [67d3973](https://github.com/ripple/ripple-lib/commit/b6b99dde022e1e14c4797e454b1d7fca50e49482)
|
||||
|
||||
+ Remove blobvault client from ripple-lib, use the [`ripple-vault-client`](https://github.com/ripple/ripple-vault-client) instead [9b3d62b7](https://github.com/ripple/ripple-lib/commit/9b3d62b765c4c25beae6eb0fa57ef3a07f2581b1)
|
||||
|
||||
+ [Add support for `ledger` option in requestBookOffers](https://github.com/ripple/ripple-lib/commit/34c0677c453c409ef0a5b351959abdc176d3bacb)
|
||||
|
||||
+ [Add support for `limit` option in requestBookOffers](https://github.com/ripple/ripple-lib/commit/d1d4452217c878d0b377d24830b4cd8b3162f6e0)
|
||||
|
||||
+ [Add `ledgerSelect` request constructor in `Remote`](https://github.com/ripple/ripple-lib/commit/98f40abfc3aa74dec5067a2d90002756cc8acd01)
|
||||
|
||||
+ [Default to binary data for commands that accept the binary flag](https://github.com/ripple/ripple-lib/commit/7cb113fcbcfc1e3e9830a999148b3e78df3387cc)
|
||||
|
||||
+ [Fix metadata account check](https://github.com/ripple/ripple-lib/commit/3f61598d6c87e3cc877af60e2d515f9eff73dfe1)
|
||||
|
||||
+ [Double check `tes` code before emitting `success`](https://github.com/ripple/ripple-lib/commit/97a8c874903eb7309d8f755955ac80872f670582)
|
||||
|
||||
+ [Decrease redundancy in binary account_tx parsing](https://github.com/ripple/ripple-lib/commit/0aba638e6e7f4f6e22cb6424eed3897ebad90a5a)
|
||||
|
||||
+ [Abort server connection on unrecoverable TLS error](https://github.com/ripple/ripple-lib/commit/000a2ea00c57157044aeca0fb3f24b37669b163c)
|
||||
|
||||
+ [Fix complete ledgers check on subscription that is not initial](https://github.com/ripple/ripple-lib/commit/89de91301e682a46dc60aaacc7ae152e8fe1b7c7)
|
||||
|
||||
|
||||
##0.10.0
|
||||
|
||||
+ [Transaction changes](https://github.com/ripple/ripple-lib/pull/221)
|
||||
|
||||
+ **Important** `tef*` and `tel*` and errors will no longer be presented as
|
||||
final. Rather than considering these errors final, ripple-lib will wait until
|
||||
the `LastLedgerSequence` specified in the transaction is exceeded. This makes
|
||||
failures more definitive, and ensures that no transaction will resubmit
|
||||
indefinitely.
|
||||
|
||||
+ A new, final tej-class error is introduced to account for transactions that
|
||||
are locally determined to have expired: `tejMaxLedger`.
|
||||
|
||||
+ [Allow per transaction fees to be set, `transaction.setFixedFee()`](https://github.com/ripple/ripple-lib/commit/9b22f279bcbe60ee6bcf4b7fa60a48e9c197a828)
|
||||
|
||||
+ [Improve memo support](https://github.com/ripple/ripple-lib/commit/1704ac4ae144c0ce54afad86f644c75a632080b1)
|
||||
- Add `MemoFormat` property for memo
|
||||
- Enforce `MemoFormat` and `MemoType` to be valid ASCII
|
||||
- Support `text` and `json` MemoFormat
|
||||
|
||||
+ [Update jscl library](https://github.com/ripple/ripple-lib/commit/3204998fcb6f31d6c90532a737a4adb8a1e420f6)
|
||||
- Improved entropy by taking advantage of platform crypto
|
||||
- Use jscl's k256 curve instead of altering the c256 curve with k256 configuration
|
||||
- **deprecated:** the c256 curve is linked to the k256 curve to provide backwards compatibility, this link will be removed in the future
|
||||
|
||||
+ [Fix empty queue check on reconnect](https://github.com/ripple/ripple-lib/commit/3c21994adcf72d1fbd87d453ceb917f9ad6df4ec)
|
||||
|
||||
##0.9.4
|
||||
|
||||
+ [Normalize offers from book_offers and transaction stream](https://github.com/ripple/ripple-lib/commit/86ed24b94cf7c8929c87db3a63e9bbea7f767e9c)
|
||||
|
||||
+ [Fix: Amount.to_human() precision rounding](https://github.com/ripple/ripple-lib/commit/e371cc2c3ceccb3c1cfdf18b98d80093147dd8b2)
|
||||
|
||||
+ [Fix: fractional drops in funded taker_pays setter](https://github.com/ripple/ripple-lib/commit/0d7fc0a573a144caac15dd13798b23eeb1f95fb4)
|
||||
|
||||
##0.9.3
|
||||
|
||||
+ [Change `presubmit` to emit immediately before transaction submit](https://github.com/ripple/ripple-lib/commit/7a1feaa89701bf861ab31ebd8ffdc8d8d1474e29)
|
||||
|
||||
+ [Add a "core" browser build of ripple-lib which has a subset of features and smaller file size](https://github.com/ripple/ripple-lib/pull/205)
|
||||
|
||||
+ [Update binformat with missing fields from rippled](https://github.com/ripple/ripple-lib/commit/cae980788efb00191bfd0988ed836d60cdf7a9a2)
|
||||
|
||||
+ [Wait for transaction validation before returning `tec` error](https://github.com/ripple/ripple-lib/commit/6bdd4b2670906588852fc4dda457607b4aac08e4)
|
||||
|
||||
+ [Change default `max_fee` on `Remote` to `1 XRP`](https://github.com/ripple/ripple-lib/commit/d6b1728c23ff85c3cc791bed6982a750641fd95f)
|
||||
|
||||
+ [Fix: Request ledger_accept should return the Remote](https://github.com/ripple/ripple-lib/pull/209)
|
||||
|
||||
##0.9.2
|
||||
|
||||
+ [**Breaking change**: Change accountRequest method signature](https://github.com/ripple/ripple-lib/commit/6f5d1104aa3eb440c518ec4f39e264fdce15fa15)
|
||||
|
||||
+ [Add paging behavior for account requests, `account_lines` and `account_offers`](https://github.com/ripple/ripple-lib/commit/722f4e175dbbf378e51b49142d0285f87acb22d7)
|
||||
|
||||
+ [Add max_fee setter to transactions to set max fee the submitter is willing to pay] (https://github.com/ripple/ripple-lib/commit/24587fab9c8ad3840d7aa345a7037b48839e09d7)
|
||||
|
||||
+ [Fix: cap IOU Amounts to their max and min value] (https://github.com/ripple/ripple-lib/commit/f05941fbc46fdb7c6fe7ad72927af02d527ffeed)
|
||||
|
||||
Example on how to use paging with `account_offers`:
|
||||
```
|
||||
// A valid `ledger_index` or `ledger_hash` is required to provide a reliable result.
|
||||
// Results can change between ledger closes, so the provided ledger will be used as base.
|
||||
var options = {
|
||||
account: < rippleAccount >,
|
||||
limit: < Number between 10 and 400 >,
|
||||
ledger: < valid ledger_index or ledger_hash >
|
||||
}
|
||||
|
||||
// The `marker` comes back in an account request if there are more results than are returned
|
||||
// in the current response. The amount of results per response are determined by the `limit`.
|
||||
if (marker) {
|
||||
options.marker = < marker >;
|
||||
}
|
||||
|
||||
var request = remote.requestAccountOffers(options);
|
||||
```
|
||||
|
||||
[Full working example](https://github.com/geertweening/ripple-lib-scripts/blob/master/account_offers_paging.js)
|
||||
|
||||
|
||||
##0.9.1
|
||||
|
||||
+ Switch account requests to use ledgerSelect rather than ledgerChoose ([278df90](https://github.com/ripple/ripple-lib/commit/278df9025a20228de22379a53c76ca12d40fa591))
|
||||
|
||||
+ **Deprecated** setting `ident` and `account_index` on account requests ([278df90](https://github.com/ripple/ripple-lib/commit/278df9025a20228de22379a53c76ca12d40fa591))
|
||||
|
||||
+ Change initial account transaction sequence to 1 ([a3c1d06](https://github.com/ripple/ripple-lib/commit/a3c1d06eba883dc84fe2bfe700e4309795c84cac))
|
||||
|
||||
+ Fix: instance transaction withoute remote ([d3b6b81](https://github.com/ripple/ripple-lib/commit/d3b6b8127c7b01e416b400c25abf1719bdd008ca))
|
||||
|
||||
+ Fix: account root request ledger argument ([bc1f9f8](https://github.com/ripple/ripple-lib/commit/bc1f9f8a286b187d36ebaf552694e31e73742293))
|
||||
|
||||
+ Fix: rsign.js local signing and example ([d3b6b81](https://github.com/ripple/ripple-lib/commit/d3b6b8127c7b01e416b400c25abf1719bdd008ca) and [f1004c6](https://github.com/ripple/ripple-lib/commit/f1004c6db2a0ce59bbabbb8f2b355a9fd9995fd8))
|
||||
|
||||
|
||||
##0.9.0
|
||||
|
||||
+ Add routes to the vault client for KYC attestations ([ed2da574](https://github.com/ripple/ripple-lib/commit/ed2da57475acf5e9d2cf3373858f4274832bd83f))
|
||||
|
||||
+ Currency: add `show_interest` flag to show or hide interest in `Currency.to_human()` and `Currency.to_json()` [Example use in tests](https://github.com/ripple/ripple-lib/blob/947ec3edc2e7c8f1ef097e496bf552c74366e749/test/currency-test.js#L123)
|
||||
|
||||
+ Configurable maxAttempts for transaction submission ([d107092](https://github.com/ripple/ripple-lib/commit/d10709254061e9e4416d2cb78b5cac1ec0d7ffa5))
|
||||
|
||||
+ Binformat: added missing TransactionResult options ([6abed8d](https://github.com/ripple/ripple-lib/commit/6abed8dd5311765b2eb70505dadbdf5121439ca8))
|
||||
|
||||
+ **Breaking change:** make maxLoops in seed.get_key optional. [Example use in tests](https://github.com/ripple/ripple-lib/blob/23e473b6886c457781949c825b3ff48b3984e51f/test/seed-test.js) ([23e473b](https://github.com/ripple/ripple-lib/commit/23e473b6886c457781949c825b3ff48b3984e51f))
|
||||
|
||||
+ Shrinkwrap packages for dependency locking ([2dcd5f9](2dcd5f94fbc71200eb08a5044c76ef94f7971913))
|
||||
|
||||
+ Fix: Amount.to_human() precision bugs ([4be209e](https://github.com/ripple/ripple-lib/commit/4be209e286b5b209bec7bcd1212098985e15ff2f) and [7708c64](https://github.com/ripple/ripple-lib/commit/7708c64576e70ce3ac190442daceb30e4446aab7))
|
||||
|
||||
+ Fix: change handling of requestLedger options ([57b7030](https://github.com/ripple/ripple-lib/commit/57b70300f5f0c7534ede118ddbb5d8762668a4f8))
|
||||
|
||||
|
||||
##0.8.2
|
||||
|
||||
+ Currency: Allow mixed letters and numbers in currencies
|
||||
|
||||
+ Deprecate account_tx map/reduce/filterg
|
||||
|
||||
+ Fix: correct requestLedger arguments
|
||||
|
||||
+ Fix: missing subscription on error events for some server methods
|
||||
|
||||
+ Fix: orderbook reset on reconnect
|
||||
|
||||
+ Fix: ripple-lib crashing. Add potential missing error handlers
|
||||
|
||||
|
||||
##0.8.1
|
||||
|
||||
+ Wallet: Add Wallet class that generates wallets
|
||||
|
||||
+ Make npm test runnable in Windows.
|
||||
|
||||
+ Fix several stability issues, see merged PR's for details
|
||||
|
||||
+ Fix bug in Amount.to_human_full()
|
||||
|
||||
+ Fix undefined fee states when connecting to a rippled that is syncing
|
||||
|
||||
|
||||
##0.8.0
|
||||
|
||||
+ Orderbook: Added tracking of offer funds for determining when offers are not funded
|
||||
|
||||
+ Orderbook: Added tests
|
||||
|
||||
+ Orderbook: Update owner funds
|
||||
|
||||
+ Transactions: If transaction errs with `tefALREADY`, wait until all possible submissions err with the same before emitting `error`. Fixes a client "Transaction malformed" bug.
|
||||
|
||||
+ Transactions: Track submissions, don't bother submitting to unconnected servers
|
||||
|
||||
+ Request: `request.request()` now accepts an array of servers as first argument. Servers can be represented with URL, or the server object itself.
|
||||
|
||||
+ Request: `request.broadcast()` now returns the number of servers request was sent to
|
||||
|
||||
+ Server: Acquire host information from server without additional request
|
||||
|
||||
+ Amount: Add a constant for the maximum canonical value that can be expressed as a Ripple value
|
||||
|
||||
+ Amount: Make Constants static fields on the class, instead of a seperate export
|
||||
|
||||
|
||||
##0.7.39
|
||||
|
||||
+ Improvements to multi-server support. Fixed an issue where a server's score was not reset and connections would keep dropping after being connected for a significant amount of time.
|
||||
|
||||
+ Improvements in order book support. Added support for currency pairs with interest bearing currencies. You can request an order book with hex, ISO code or full name for the currency.
|
||||
|
||||
+ Fix value parsing for amount/currency order pairs, e.g. `Amount.from_human("XAU 12345.6789")`
|
||||
|
||||
+ Improved Amount parsing from human readable string given a hex currency, e.g. `Amount.from_human("10 015841551A748AD2C1F76FF6ECB0CCCD00000000")`
|
||||
|
||||
+ Improvements to username normalization in the vault client
|
||||
|
||||
+ Add 2-factor authentication support for vault client
|
||||
|
||||
+ Removed vestiges of Grunt, switched to Gulp
|
||||
|
||||
|
||||
##0.7.37
|
||||
|
||||
+ **Deprecations**
|
||||
|
||||
1. Removed humanistic amount detection in `transaction.payment`. Passing `1XRP` as the payment amount no longer works.
|
||||
2. `remote.setServer` uses full server URL rather than hostname. Example: `remote.setServer('wss://s`.ripple.com:443')`
|
||||
3. Removed constructors for deprecated transaction types from `transaction.js`.
|
||||
4. Removed `invoiceID` option from `transaction.payment`. Instead, use the `transaction.invoiceID` method.
|
||||
5. Removed `transaction.transactionManager` getter.
|
||||
|
||||
+ Improved multi-server support. Servers are now ranked dynamically, and transactions are broadcasted to all connected servers.
|
||||
|
||||
+ Automatically ping connected servers. Client configuration now should contain `ping: <seconds>` to specify the ping interval.
|
||||
|
||||
+ Added `transaction.lastLedger` to specify `LastLedgerSequence`. Setting it this way also ensures that the sequence is not bumped on subsequent requests.
|
||||
|
||||
+ Added optional `remote.accountTx` binary parsing.
|
||||
```js
|
||||
{
|
||||
binary: true,
|
||||
parseBinary: false
|
||||
}
|
||||
```
|
||||
+ Added full currency name support, e.g. `Currency.from_json('XRP').to_human({full_name:'Ripples'})` will return `XRP - Ripples`
|
||||
|
||||
+ Improved interest bearing currency support, e.g. `Currency.from_human('USD - US Dollar (2.5%pa)')`
|
||||
|
||||
+ Improve test coverage
|
||||
|
||||
+ Added blob vault client. The vault client facilitates interaction with ripple's namespace and blob vault or 3rd party blob vaults using ripple's blob vault software (https://github.com/ripple/ripple-blobvault). A list of the available functions can be found at [docs/VAULTCLIENT.md](docs/VAULTCLIENT.md)
|
||||
|
||||
|
||||
##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:
|
||||
|
||||
|
||||
105
README.md
105
README.md
@@ -1,11 +1,100 @@
|
||||
Ripple JavaScript Library - ripple-lib
|
||||
======================================
|
||||
#ripple-lib
|
||||
|
||||
This library can connect to the Ripple network via the WebSocket protocol and runs in Node.js as well as in the browser.
|
||||
A JavaScript API for interacting with Ripple in Node.js and the browser
|
||||
|
||||
Build instructions:
|
||||
* https://ripple.com/wiki/Ripple_JavaScript_library
|
||||
[](https://travis-ci.org/ripple/ripple-lib) [](https://coveralls.io/r/ripple/ripple-lib?branch=develop)
|
||||
|
||||
For more information:
|
||||
* https://ripple.com
|
||||
* https://ripple.com/wiki
|
||||
[](https://www.npmjs.org/package/ripple-lib)
|
||||
|
||||
###Features
|
||||
|
||||
+ Connect to a rippled server in JavaScript (Node.js or browser)
|
||||
+ Issue [rippled API](https://ripple.com/build/rippled-apis/) requests
|
||||
+ Listen to events on the Ripple network (transaction, ledger, etc.)
|
||||
+ Sign and submit transactions to the Ripple network
|
||||
|
||||
###In this file
|
||||
|
||||
1. [Installation](#installation)
|
||||
2. [Quick start](#quick-start)
|
||||
3. [Running tests](#running-tests)
|
||||
|
||||
###Additional documentation
|
||||
|
||||
1. [Guides](docs/GUIDES.md)
|
||||
2. [API Reference](docs/REFERENCE.md)
|
||||
3. [Wiki](https://ripple.com/wiki/Ripple_JavaScript_library)
|
||||
|
||||
###Also see
|
||||
|
||||
+ [The Ripple wiki](https://ripple.com/wiki)
|
||||
+ [ripple.com](https://ripple.com)
|
||||
|
||||
##Installation
|
||||
|
||||
**Via npm for Node.js**
|
||||
|
||||
```
|
||||
$ npm install ripple-lib
|
||||
```
|
||||
|
||||
**Via bower (for browser use)**
|
||||
|
||||
```
|
||||
$ bower install ripple
|
||||
```
|
||||
|
||||
See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions
|
||||
|
||||
|
||||
**Building ripple-lib for browser environments**
|
||||
|
||||
ripple-lib uses Gulp to generate browser builds. These steps will generate minified and non-minified builds of ripple-lib in the `build/` directory.
|
||||
|
||||
```
|
||||
$ git clone https://github.com/ripple/ripple-lib
|
||||
$ npm install
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
**Restricted browser builds**
|
||||
|
||||
You may generate browser builds that contain a subset of features. To do this, run `./node_modules/.bin/gulp build-<name>`
|
||||
|
||||
+ `build-core` Contains the functionality to make requests and listen for events such as `ledgerClose`. Only `ripple.Remote` is currently exposed. Advanced features like transaction submission and orderbook tracking are excluded from this build.
|
||||
|
||||
##Quick start
|
||||
|
||||
`Remote.js` ([remote.js](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js)) is the point of entry for interacting with rippled
|
||||
|
||||
```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({
|
||||
// see the API Reference for available options
|
||||
servers: [ 'wss://s1.ripple.com:443' ]
|
||||
});
|
||||
|
||||
remote.connect(function() {
|
||||
/* remote connected */
|
||||
remote.requestServerInfo(function(err, info) {
|
||||
// process err and info
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
##Running tests
|
||||
|
||||
1. Clone the repository
|
||||
|
||||
2. `cd` into the repository and install dependencies with `npm install`
|
||||
|
||||
3. `npm test`
|
||||
|
||||
**Generating code coverage**
|
||||
|
||||
ripple-lib uses `istanbul` to generate code coverage. To create a code coverage report, run `npm test --coverage`. The report will be created in `coverage/lcov-report/`.
|
||||
|
||||
52
bin/decode_binary.js
Executable file
52
bin/decode_binary.js
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-var */
|
||||
'use strict';
|
||||
var SerializedObject = require('..').SerializedObject;
|
||||
|
||||
function main() {
|
||||
var argv = process.argv.slice(2);
|
||||
var blob = argv.shift();
|
||||
|
||||
if (blob === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready(blob);
|
||||
}
|
||||
}
|
||||
|
||||
function read_input(callback) {
|
||||
var tx_json = '';
|
||||
process.stdin.on('data', function(data) {
|
||||
tx_json += data;
|
||||
});
|
||||
process.stdin.on('end', callback);
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function ready(blob) {
|
||||
var valid_arguments = blob;
|
||||
|
||||
if (!valid_arguments) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
} else {
|
||||
decode(blob);
|
||||
}
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
/* eslint-disable max-len */
|
||||
console.log(
|
||||
'Usage: decode_binary.js <hex_blob>\n\n',
|
||||
'Example: decode_binary.js 120000240000000161D6871AFD498D00000000000000000000000000005553440000000000550FC62003E785DC231A1058A05E56E3F09CF4E668400000000000000A732102AE75B908F0A95F740A7BFA96057637E5C2170BC8DAD13B2F7B52AE75FAEBEFCF811450F97A072F1C4357F1AD84566A609479D927C9428314550FC62003E785DC231A1058A05E56E3F09CF4E6'
|
||||
);
|
||||
/* eslint-enable max-len */
|
||||
}
|
||||
|
||||
function decode(blob) {
|
||||
var buffer = new SerializedObject(blob);
|
||||
console.log(buffer.to_json());
|
||||
}
|
||||
|
||||
main();
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
97
bin/rsign.js
Executable file
97
bin/rsign.js
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-var */
|
||||
'use strict';
|
||||
var Transaction = require('..').Transaction;
|
||||
|
||||
function read_input(callback) {
|
||||
var stdin = '';
|
||||
process.stdin.on('data', function(data) {
|
||||
stdin += data;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
callback(stdin);
|
||||
});
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
console.log(
|
||||
'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
|
||||
}) + '\''
|
||||
);
|
||||
}
|
||||
|
||||
function sign_transaction(tx_json_object, secret, verbose) {
|
||||
var tx = new Transaction();
|
||||
|
||||
tx.tx_json = tx_json_object;
|
||||
tx._secret = secret;
|
||||
tx.complete();
|
||||
|
||||
var unsigned_blob = tx.serialize().to_hex();
|
||||
var unsigned_hash = tx.signingHash();
|
||||
tx.sign();
|
||||
|
||||
if (verbose) {
|
||||
var sim = { };
|
||||
sim.tx_blob = tx.serialize().to_hex();
|
||||
sim.tx_json = tx.tx_json;
|
||||
sim.tx_signing_hash = unsigned_hash;
|
||||
sim.tx_unsigned = unsigned_blob;
|
||||
console.log(JSON.stringify(sim, null, 2));
|
||||
} else {
|
||||
console.log(tx.serialize().to_hex());
|
||||
}
|
||||
}
|
||||
|
||||
function ready(tx_json, secret, verbose) {
|
||||
if (!(tx_json && secret)) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
return;
|
||||
}
|
||||
|
||||
var tx_json_object;
|
||||
try {
|
||||
tx_json_object = JSON.parse(tx_json);
|
||||
} catch(exception) {
|
||||
console.error('Invalid JSON\n');
|
||||
print_usage();
|
||||
return;
|
||||
}
|
||||
sign_transaction(tx_json_object, secret, verbose);
|
||||
}
|
||||
|
||||
function main() {
|
||||
var argv = process.argv.slice(2);
|
||||
var verbose;
|
||||
var secret;
|
||||
var tx_json;
|
||||
|
||||
if (~argv.indexOf('-v')) {
|
||||
argv.splice(argv.indexOf('-v'), 1);
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
secret = argv.shift();
|
||||
tx_json = argv.shift();
|
||||
|
||||
if (tx_json === '-') {
|
||||
read_input(function(stdin) {
|
||||
ready(stdin, secret, verbose);
|
||||
});
|
||||
} else {
|
||||
ready(tx_json, secret, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
32
bin/validate_address.js
Executable file
32
bin/validate_address.js
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-var */
|
||||
'use strict';
|
||||
var UInt160 = require('..').UInt160;
|
||||
|
||||
function main() {
|
||||
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');
|
||||
}
|
||||
|
||||
main();
|
||||
3
circle.yml
Normal file
3
circle.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
machine:
|
||||
node:
|
||||
version: 0.12.0
|
||||
@@ -1,3 +0,0 @@
|
||||
start newcoin
|
||||
sleep 4
|
||||
start index.html
|
||||
20
docs/BUILD.md
Normal file
20
docs/BUILD.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Using Flow typechecking
|
||||
=======================
|
||||
|
||||
Stage 1
|
||||
-------
|
||||
1. Add /* @flow */ to the top of a file you want to typecheck
|
||||
2. Run `gulp typecheck` to generate a list of warnings
|
||||
|
||||
Stage 2
|
||||
-------
|
||||
When all source files have the /* @flow */ header and all warnings have been
|
||||
addressed, remove the `weak: true` option from Gulpfile.js, run
|
||||
`gulp typecheck` and remove all the additional warnings.
|
||||
|
||||
Stage 3
|
||||
-------
|
||||
Add type annotations to the source code and run `gulp strip` to strip
|
||||
the type annotations and write the output to the `out` directory. After
|
||||
type annotations are added, the program must be run from the `out` directory
|
||||
because Node does not understand the annotations
|
||||
252
docs/GUIDES.md
Normal file
252
docs/GUIDES.md
Normal file
@@ -0,0 +1,252 @@
|
||||
#Guides
|
||||
|
||||
This file provides step-by-step walkthroughs for some of the most common usages of `ripple-lib`.
|
||||
|
||||
###In this document
|
||||
|
||||
1. [Connecting to the Ripple network with `Remote`](GUIDES.md#connecting-to-the-ripple-network)
|
||||
2. [Using `Remote` functions and `Request` objects](GUIDES.md#sending-rippled-API-requests)
|
||||
3. [Listening to the network](GUIDES.md#listening-to-the-network)
|
||||
4. [Submitting a payment to the network](GUIDES.md#submitting-a-payment-to-the-network)
|
||||
* [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)
|
||||
5. [Submitting a trade offer to the network](GUIDES.md#submitting-a-trade-offer-to-the-network)
|
||||
|
||||
###Also see
|
||||
|
||||
1. [The ripple-lib README](../README.md)
|
||||
2. [The ripple-lib API Reference](REFERENCE.md)
|
||||
|
||||
##Connecting to the Ripple network
|
||||
|
||||
1. [Get ripple-lib](../README.md#installation)
|
||||
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 options = {
|
||||
trace : false,
|
||||
trusted: true,
|
||||
local_signing: true,
|
||||
servers: [
|
||||
{ host: 's-west.ripple.com', port: 443, secure: true }
|
||||
]
|
||||
};
|
||||
|
||||
var remote = new Remote(options);
|
||||
|
||||
remote.connect(function(err, res) {
|
||||
/* 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.
|
||||
|
||||
##Generating a new Ripple Wallet
|
||||
|
||||
```js
|
||||
var ripple = require('ripple-lib');
|
||||
|
||||
// subscribing to a server allows for more entropy
|
||||
var remote = new ripple.Remote({
|
||||
servers: [
|
||||
{ host: 's1.ripple.com', port: 443, secure: true }
|
||||
]
|
||||
});
|
||||
|
||||
remote.connect(function(err, res) {
|
||||
/* remote connected */
|
||||
});
|
||||
|
||||
// Wait for randomness to have been added.
|
||||
// The entropy of the random generator is increased
|
||||
// by random data received from a rippled
|
||||
remote.once('random', function(err, info) {
|
||||
var wallet = ripple.Wallet.generate();
|
||||
console.log(wallet);
|
||||
// { address: 'rEf4sbVobiiDGExrNj2PkNHGMA8eS6jWh3',
|
||||
// secret: 'shFh4a38EZpEdZxrLifEnVPAoBRce' }
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
##Sending rippled API requests
|
||||
|
||||
`Remote` contains functions for constructing a `Request` object.
|
||||
|
||||
A `Request` is an `EventEmitter` so you can listen for success or failure events -- or, instead, you can provide a callback.
|
||||
|
||||
Here is an example, using [requestServerInfo](https://ripple.com/wiki/JSON_Messages#server_info).
|
||||
|
||||
+ Constructing a `Request` with event listeners
|
||||
```js
|
||||
var request = remote.requestServerInfo();
|
||||
|
||||
request.on('success', function onSuccess(res) {
|
||||
//handle success
|
||||
});
|
||||
|
||||
request.on('error', function onError(err) {
|
||||
//handle error
|
||||
});
|
||||
|
||||
request.request();
|
||||
```
|
||||
|
||||
+ Using 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)
|
||||
|
||||
|
||||
##Listening to the network
|
||||
|
||||
See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on subscription requests.
|
||||
|
||||
```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() {
|
||||
var remote = new Remote({
|
||||
// see the API Reference for available options
|
||||
servers: [ 'wss://s1.ripple.com:443' ]
|
||||
});
|
||||
|
||||
remote.connect(function() {
|
||||
console.log('Remote connected');
|
||||
|
||||
var streams = [
|
||||
'ledger',
|
||||
'transactions'
|
||||
];
|
||||
|
||||
var request = remote.requestSubscribe(streams);
|
||||
|
||||
request.on('error', function(error) {
|
||||
console.log('request error: ', error);
|
||||
});
|
||||
|
||||
|
||||
// the `ledger_closed` and `transaction` will come in on the remote
|
||||
// since the request for subscribe is finalized after the success return
|
||||
// the streaming events will still come in, but not on the initial request
|
||||
remote.on('ledger_closed', function(ledger) {
|
||||
console.log('ledger_closed: ', JSON.stringify(ledger, null, 2));
|
||||
});
|
||||
|
||||
remote.on('transaction', function(transaction) {
|
||||
console.log('transaction: ', JSON.stringify(transaction, null, 2));
|
||||
});
|
||||
|
||||
remote.on('error', function(error) {
|
||||
console.log('remote error: ', error);
|
||||
});
|
||||
|
||||
// fire the request
|
||||
request.request();
|
||||
});
|
||||
});
|
||||
```
|
||||
* https://ripple.com/wiki/RPC_API#transactions_stream_messages
|
||||
* https://ripple.com/wiki/RPC_API#ledger_stream_messages
|
||||
|
||||
##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.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: 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.
|
||||
|
||||
|
||||
##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 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 GATEWAY = 'rrrGateWay';
|
||||
|
||||
var remote = new Remote({ /* Remote options */ });
|
||||
|
||||
remote.connect(function() {
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('OfferCreate', {
|
||||
account: MY_ADDRESS,
|
||||
taker_pays: '100',
|
||||
taker_gets: '1/USD/' + GATEWAY
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
/* handle submission errors / success */
|
||||
});
|
||||
});
|
||||
```
|
||||
354
docs/REFERENCE.md
Normal file
354
docs/REFERENCE.md
Normal file
@@ -0,0 +1,354 @@
|
||||
#API Reference
|
||||
|
||||
__(More examples coming soon!)__
|
||||
|
||||
###In this document:
|
||||
|
||||
1. [`Remote` options](REFERENCE.md#remote-options)
|
||||
2. [`Request` constructors](REFERENCE.md#request-constructor-functions)
|
||||
+ [Server requests](REFERENCE.md#server-requests)
|
||||
+ [Ledger requests](REFERENCE.md#ledger-requests)
|
||||
+ [Transaction requests](REFERENCE.md#transaction-requests)
|
||||
+ [Account requests](REFERENCE.md#account-requests)
|
||||
+ [Orderbook requests](REFERENCE.md#orderbook-requests)
|
||||
+ [Transaction requests](REFERENCE.md#transaction-requests)
|
||||
3. [`Transaction` constructors](REFERENCE.md#transaction-constructors)
|
||||
+ [Transaction events](REFERENCE.md#transaction-events)
|
||||
|
||||
###Also see:
|
||||
|
||||
1. [The ripple-lib README](../README.md)
|
||||
2. [The ripple-lib GUIDES](GUIDES.md)a
|
||||
|
||||
#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 options = { };
|
||||
|
||||
var remote = new Remote(options);
|
||||
```
|
||||
|
||||
A new `Remote` can be created with the following options:
|
||||
|
||||
+ `trace` *boolean default: false* Log all of the events emitted
|
||||
+ `max_listeners` *number default: 0* Set maxListeners for servers
|
||||
+ `trusted` *boolean default: false*, if remote is trusted (boolean)
|
||||
+ `local_signing` *boolean default: true*
|
||||
+ `local_fee` *boolean default: true* Set whether the transaction fee range will be set locally, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `fee_cushion` *number default: 1.2* Extra fee multiplier to account for async fee changes, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `max_fee` *number default: Infinity* Maximum acceptable transaction fee, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)
|
||||
+ `servers` *array* Array of server objects of the following form:
|
||||
|
||||
```js
|
||||
{
|
||||
host: <string>,
|
||||
port: <number>,
|
||||
secure: <boolean>
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
'wss://host:port'
|
||||
```
|
||||
|
||||
#Request constructor functions
|
||||
|
||||
Some requests have helper methods to construct the requests object and set properties on the message object. These will often be the more used requests and the helper methods is the preferred way of constructing these requests.
|
||||
Other request can still be made, but the type will have to be passed in directly to request constructor. See examples below.
|
||||
|
||||
If the method is camelCased and starts with `request`, it's a helper method that wraps the request constructor.
|
||||
|
||||
##Server requests
|
||||
|
||||
**[requestServerInfo([callback])](https://ripple.com/wiki/JSON_Messages#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.setServer`. Example:
|
||||
|
||||
```js
|
||||
var request = remote.requestServerInfo();
|
||||
|
||||
request.setServer('wss://s1.ripple.com');
|
||||
|
||||
request.request(function(err, res) {
|
||||
|
||||
});
|
||||
```
|
||||
**[requestPeers([callback])](https://ripple.com/wiki/JSON_Messages#peers)**
|
||||
|
||||
**[requestConnect(ip, port, [callback])](https://ripple.com/wiki/JSON_Messages#connect)**
|
||||
|
||||
**[unl_list([callback])](https://ripple.com/wiki/JSON_Messages#unl_list)**
|
||||
|
||||
```js
|
||||
var request = remote.request('un_list');
|
||||
|
||||
request.setServer('wss://s1.ripple.com');
|
||||
|
||||
request.request(function(err, res) {
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
**[unl_add(addr, comment, [callback])](https://ripple.com/wiki/JSON_Messages#unl_add)**
|
||||
|
||||
**[unl_delete(node, [callback])](https://ripple.com/wiki/JSON_Messages#unl_delete)**
|
||||
|
||||
|
||||
|
||||
##Ledger requests
|
||||
|
||||
**[requestLedger([opts], [callback])](https://ripple.com/wiki/JSON_Messages#ledger)**
|
||||
|
||||
**[requestLedgerHeader([callback])](https://wiki.ripple.com/JSON_Messages#ledger_data)**
|
||||
|
||||
**[requestLedgerCurrent([callback])](https://ripple.com/wiki/JSON_Messages#ledger_current)**
|
||||
|
||||
**[requestLedgerEntry(type, [callback])](https://ripple.com/wiki/JSON_Messages#ledger_entry)**
|
||||
|
||||
**[requestSubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#subscribe)**
|
||||
|
||||
Start receiving selected streams from the server.
|
||||
|
||||
**[requestUnsubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#unsubscribe)**
|
||||
|
||||
Stop receiving selected streams from the server.
|
||||
|
||||
##Account requests
|
||||
|
||||
**[requestAccountInfo(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_info)**
|
||||
|
||||
Return information about the specified account.
|
||||
|
||||
```
|
||||
var options = {
|
||||
account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
ledger: 'validated'
|
||||
};
|
||||
|
||||
var request = remote.requestAccountInfo(options, function(err, info) {
|
||||
/* process info */
|
||||
});
|
||||
|
||||
|
||||
// response
|
||||
{
|
||||
ledger_current_index: <number>,
|
||||
account_data: {
|
||||
Account: <string>,
|
||||
Balance: <number>,
|
||||
Flags: <number>,
|
||||
LedgerEntryType: <string>,
|
||||
OwnerCount: <number>,
|
||||
PreviousTxnID: <string>,
|
||||
PreviousTxnLgrSeq: <number>,
|
||||
Sequence: <number> ,
|
||||
index: <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**[requestAccountLines(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_lines)**
|
||||
|
||||
**[requestAccountOffers(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_offers)**
|
||||
|
||||
Return the specified account's outstanding offers.
|
||||
|
||||
Requests for both `account_lines` and `account_offers` support paging. The amount of results per response can be configured with the `limit`.
|
||||
The responses can be paged through by using the `marker`.
|
||||
|
||||
```
|
||||
// A valid `ledger_index` or `ledger_hash` is required to provide a reliable result.
|
||||
// Results can change between ledger closes, so the provided ledger will be used as base.
|
||||
var options = {
|
||||
account: < rippleAccount >,
|
||||
limit: < Number between 10 and 400 >,
|
||||
ledger: < valid ledger_index or ledger_hash >
|
||||
}
|
||||
|
||||
// The `marker` comes back in an account request if there are more results than are returned
|
||||
// in the current response. The amount of results per response are determined by the `limit`.
|
||||
if (marker) {
|
||||
options.marker = < marker >;
|
||||
}
|
||||
|
||||
var request = remote.requestAccountOffers(options);
|
||||
```
|
||||
|
||||
|
||||
**[requestAccountTransactions(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_tx)**
|
||||
|
||||
Fetch a list of transactions that applied to this account.
|
||||
|
||||
Options:
|
||||
|
||||
+ `account`
|
||||
+ `ledger_index_min`
|
||||
+ `ledger_index_max`
|
||||
+ `binary` *false*
|
||||
+ `count` *false*
|
||||
+ `descending` *false*
|
||||
+ `offset` *0*
|
||||
+ `limit`
|
||||
+ `forward` *false*
|
||||
+ `fwd_marker`
|
||||
+ `rev_marker`
|
||||
|
||||
**[requestWalletAccounts(seed, [callback])](https://ripple.com/wiki/JSON_Messages#wallet_accounts)**
|
||||
|
||||
Return a list of accounts for a wallet. *Requires trusted remote*
|
||||
|
||||
**requestAccountBalance(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.
|
||||
|
||||
**requestAccountFlags(account, [ledger], [callback])**
|
||||
|
||||
Return the flags for an account.
|
||||
|
||||
**requestOwnerCount(account, [ledger], [callback])**
|
||||
|
||||
Return the owner count for an account.
|
||||
|
||||
**requestRippleBalance(account, issuer, currency, [ledger], [callback])**
|
||||
|
||||
Return a request to get a ripple balance
|
||||
|
||||
##Orderbook requests
|
||||
|
||||
**[requestBookOffers(options, [callback])](https://ripple.com/wiki/JSON_Messages#book_offers)**
|
||||
|
||||
Return the offers for an order book, also called a *snapshot*
|
||||
|
||||
```js
|
||||
var options = {
|
||||
gets: {
|
||||
issuer: < issuer >,
|
||||
currency: < currency >
|
||||
},
|
||||
pays: {
|
||||
issuer: < issuer >,
|
||||
currency: < currency >
|
||||
},
|
||||
limit: < limit >
|
||||
};
|
||||
|
||||
var request = remote.requestBookOffers(options);
|
||||
|
||||
request.request(function(err, offers) {
|
||||
//handle offers
|
||||
});
|
||||
```
|
||||
|
||||
##Transaction requests
|
||||
|
||||
**[requestTransactionEntry(hash, [ledger_hash], [callback])](https://ripple.com/wiki/JSON_Messages#transaction_entry)**
|
||||
|
||||
Searches a particular ledger for a transaction hash. Default ledger is the open ledger.
|
||||
|
||||
**[requestTransaction(hash, [callback])](https://ripple.com/wiki/JSON_Messages#tx)**
|
||||
|
||||
Searches ledger history for validated transaction hashes.
|
||||
|
||||
**[requestSign(secret, tx_json, [callback])](https://ripple.com/wiki/JSON_Messages#sign)**
|
||||
|
||||
Sign a transaction. *Requires trusted remote*
|
||||
|
||||
**[requestSubmit([callback])](https://ripple.com/wiki/JSON_Messages#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.
|
||||
|
||||
**[pathFind(src_account, dst_account, dst_amount, src_currencies)](https://ripple.com/wiki/JSON_Messages#path_find)**
|
||||
|
||||
#Transaction constructors
|
||||
|
||||
Use `remote.createTransaction('TransactionType', [options])` to construct a transaction. To submit, use `transaction.submit([callback])`.
|
||||
|
||||
**Payment**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: DEST_ADDRESS,
|
||||
amount: AMOUNT
|
||||
});
|
||||
```
|
||||
|
||||
**AccountSet**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('AccountSet', {
|
||||
account: MY_ADDRESS,
|
||||
set: 'RequireDest',
|
||||
clear: 'RequireAuth'
|
||||
});
|
||||
```
|
||||
|
||||
**TrustSet**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('TrustSet', {
|
||||
account: MY_ADDRESS,
|
||||
limit: '1/USD/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
});
|
||||
```
|
||||
|
||||
**OfferCreate**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('OfferCreate', {
|
||||
account: MY_ADDRESS,
|
||||
taker_pays: '1',
|
||||
taker_gets: '1/USD/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
});
|
||||
```
|
||||
|
||||
##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.
|
||||
+ `presubmit` Immediately before transaction is submitted
|
||||
+ `postsubmit` Immediately after transaction is submitted
|
||||
+ `submitted` Transaction has been submitted to the network. The submission may result in a remote error or success.
|
||||
+ `resubmitted` Transaction is beginning resubmission.
|
||||
+ `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.
|
||||
+ `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.
|
||||
|
||||
##Complete payment example
|
||||
|
||||
```js
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: DEST_ADDRESS,
|
||||
amount: AMOUNT
|
||||
});
|
||||
|
||||
transaction.on('resubmitted', function() {
|
||||
// initial submission failed, resubmitting
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
// submission has finalized with either an error or success.
|
||||
// the transaction will not be retried after this point
|
||||
});
|
||||
```
|
||||
|
||||
#Amount objects
|
||||
|
||||
Coming Soon
|
||||
168
docs/VAULTCLIENT.md
Normal file
168
docs/VAULTCLIENT.md
Normal file
@@ -0,0 +1,168 @@
|
||||
ripple-vault-client
|
||||
===================
|
||||
|
||||
A javascript / http client to interact with Ripple Vault servers.
|
||||
|
||||
The purpose of this tool is to enable applications in any javascript
|
||||
environment to login with the ripple vault and access the decrypted
|
||||
data stored using credentials originally obtained at ripple.com
|
||||
|
||||
|
||||
## Vault Client Usage
|
||||
|
||||
vaultClient = new ripple.VaultClient(domain);
|
||||
|
||||
vaultClient.getAuthInfo(username, callback);
|
||||
|
||||
vaultClient.getRippleName(address, url, callback);
|
||||
|
||||
vaultClient.exists(username, callback);
|
||||
|
||||
|
||||
|
||||
vaultClient.login(username, password, callback);
|
||||
|
||||
vaultClient.relogin(id, cryptKey, callback);
|
||||
|
||||
vaultClient.unlock(username, password, encryptSecret, callback);
|
||||
|
||||
vaultClient.loginAndUnlock(username, password, callback);
|
||||
|
||||
|
||||
|
||||
vaultClient.register(options, callback);
|
||||
|
||||
vaultClient.deleteBlob(options, callback);
|
||||
|
||||
vaultClient.recoverBlob(options, callback);
|
||||
|
||||
vaultClient.rename(options, callback);
|
||||
|
||||
vaultClient.changePassword(options, callback);
|
||||
|
||||
vaultClient.verify(username, token, callback);
|
||||
|
||||
vaultClient.resendEmail(options, callback);
|
||||
|
||||
vaultClient.updateProfile(options, fn);
|
||||
|
||||
|
||||
# Blob Methods
|
||||
|
||||
blob.encrypt();
|
||||
|
||||
blob.decrypt(encryptedBlob);
|
||||
|
||||
blob.encryptSecret(encryptionKey);
|
||||
|
||||
blob.decryptSecret(encryptionKey, secret);
|
||||
|
||||
blob.set(pointer, value, callback);
|
||||
|
||||
blob.unset(pointer, callback);
|
||||
|
||||
blob.extend(pointer, value, callback);
|
||||
|
||||
blob.unshift(pointer, value, callback);
|
||||
|
||||
blob.filter(pointer, field, value, subcommands, callback);
|
||||
|
||||
|
||||
## Identity Vault
|
||||
|
||||
The identity vault stores identity information inside the encrypted
|
||||
blob vault. The identity fields can be additionally encrypted with the
|
||||
unlock key, that encrypts the secret, for added security. Methods are
|
||||
accessed from the 'identity' property of the blob object.
|
||||
|
||||
|
||||
# Identity fields
|
||||
+ name
|
||||
+ entityType (individual, corporation, organization)
|
||||
+ email
|
||||
+ phone
|
||||
+ address
|
||||
+ contact
|
||||
+ line1
|
||||
+ line2
|
||||
+ city
|
||||
+ postalCode
|
||||
+ region - state/province/region
|
||||
+ country
|
||||
+ nationalID
|
||||
+ number
|
||||
+ type (ssn, taxID, passport, driversLicense, other)
|
||||
+ country - issuing country
|
||||
+ birthday
|
||||
+ birthplace
|
||||
|
||||
|
||||
# Identity Methods
|
||||
|
||||
blob.identity.set(pointer, key, value, callback);
|
||||
|
||||
blob.identity.unset(pointer, key, callback);
|
||||
|
||||
blob.identity.get(pointer, key);
|
||||
|
||||
blob.identity.getAll(key);
|
||||
|
||||
blob.identity.getFullAddress(key); //get text string of full address
|
||||
|
||||
|
||||
## Spec Tests
|
||||
|
||||
Run `npm test` to test the high-level behavior specs
|
||||
|
||||
Ripple Txt
|
||||
✓ should get the content of a ripple.txt file from a given domain
|
||||
✓ should get currencies from a ripple.txt file for a given domain
|
||||
✓ should get the domain from a given url
|
||||
|
||||
AuthInfo
|
||||
✓ should get auth info
|
||||
|
||||
VaultClient
|
||||
#initialization
|
||||
✓ should be initialized with a domain
|
||||
✓ should default to ripple.com without a domain
|
||||
#exists
|
||||
✓ should determine if a username exists on the domain
|
||||
#login
|
||||
✓ with username and password should retrive the blob, crypt key, and id
|
||||
#relogin
|
||||
✓ should retrieve the decrypted blob with blob vault url, id, and crypt key
|
||||
#unlock
|
||||
✓ should access the wallet secret using encryption secret, username and password
|
||||
#loginAndUnlock
|
||||
✓ should get the decrypted blob and decrypted secret given name and password
|
||||
#register
|
||||
✓ should create a new blob
|
||||
#deleteBlob
|
||||
✓ should remove an existing blob
|
||||
#updateProfile
|
||||
✓ should update profile parameters associated with a blob
|
||||
|
||||
Blob
|
||||
✓ #set
|
||||
✓ #extend
|
||||
✓ #unset
|
||||
✓ #unshift
|
||||
✓ #filter
|
||||
✓ #consolidate
|
||||
#rename
|
||||
✓ should change the username of a blob
|
||||
#changePassword
|
||||
✓ should change the password and keys of a blob
|
||||
#recoverBlob
|
||||
✓ should recover the blob given a username and secret
|
||||
#verifyEmail
|
||||
✓ should verify an email given a username and token
|
||||
#resendVerifcationEmail
|
||||
✓ should resend a verification given options
|
||||
identity
|
||||
✓ #identity_set
|
||||
✓ #identity_get
|
||||
✓ #identity_getAll
|
||||
✓ #identity_getFullAddress
|
||||
✓ #identity_unset
|
||||
81
grunt.js
81
grunt.js
@@ -1,81 +0,0 @@
|
||||
module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: '<json:package.json>',
|
||||
meta: {
|
||||
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
|
||||
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
|
||||
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
|
||||
'* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
|
||||
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
|
||||
},
|
||||
concat: {
|
||||
sjcl: {
|
||||
src: [
|
||||
"src/js/sjcl/core/sjcl.js",
|
||||
"src/js/sjcl/core/aes.js",
|
||||
"src/js/sjcl/core/bitArray.js",
|
||||
"src/js/sjcl/core/codecString.js",
|
||||
"src/js/sjcl/core/codecHex.js",
|
||||
"src/js/sjcl/core/codecBase64.js",
|
||||
"src/js/sjcl/core/codecBytes.js",
|
||||
"src/js/sjcl/core/sha256.js",
|
||||
"src/js/sjcl/core/sha512.js",
|
||||
"src/js/sjcl/core/sha1.js",
|
||||
"src/js/sjcl/core/ccm.js",
|
||||
// "src/js/sjcl/core/cbc.js",
|
||||
// "src/js/sjcl/core/ocb2.js",
|
||||
"src/js/sjcl/core/hmac.js",
|
||||
"src/js/sjcl/core/pbkdf2.js",
|
||||
"src/js/sjcl/core/random.js",
|
||||
"src/js/sjcl/core/convenience.js",
|
||||
"src/js/sjcl/core/bn.js",
|
||||
"src/js/sjcl/core/ecc.js",
|
||||
"src/js/sjcl/core/srp.js",
|
||||
"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-validecc.js",
|
||||
"src/js/sjcl-custom/sjcl-ecdsa-der.js"
|
||||
],
|
||||
dest: 'build/sjcl.js'
|
||||
}
|
||||
},
|
||||
webpack: {
|
||||
lib: {
|
||||
src: "src/js/index.js",
|
||||
dest: "build/ripple-<%= pkg.version %>.js",
|
||||
libary: "ripple", // misspelling fixed in later versions of webpack
|
||||
library: "ripple"
|
||||
},
|
||||
lib_debug: {
|
||||
src: "src/js/index.js",
|
||||
dest: "build/ripple-<%= pkg.version %>-debug.js",
|
||||
libary: "ripple", // misspelling fixed in later versions of webpack
|
||||
library: "ripple",
|
||||
debug: true
|
||||
},
|
||||
lib_min: {
|
||||
src: "src/js/index.js",
|
||||
dest: "build/ripple-<%= pkg.version %>-min.js",
|
||||
libary: "ripple", // misspelling fixed in later versions of webpack
|
||||
library: "ripple",
|
||||
minimize: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
sjcl: {
|
||||
files: ['<config:concat.sjcl.src>'],
|
||||
tasks: 'concat:sjcl'
|
||||
},
|
||||
lib: {
|
||||
files: 'src/js/*.js',
|
||||
tasks: 'webpack'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Tasks
|
||||
grunt.registerTask('default', 'concat:sjcl webpack');
|
||||
};
|
||||
60
npm-shrinkwrap.json
generated
Normal file
60
npm-shrinkwrap.json
generated
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.12.7",
|
||||
"npm-shrinkwrap-version": "5.4.0",
|
||||
"node-version": "v0.12.7",
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
|
||||
},
|
||||
"babel-runtime": {
|
||||
"version": "5.8.24",
|
||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.24.tgz",
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.1.4.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz"
|
||||
},
|
||||
"extend": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz"
|
||||
},
|
||||
"ripple-wallet-generator": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ripple-wallet-generator/-/ripple-wallet-generator-1.0.3.tgz"
|
||||
},
|
||||
"sjcl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.3.tgz"
|
||||
},
|
||||
"ws": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz",
|
||||
"dependencies": {
|
||||
"options": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz"
|
||||
},
|
||||
"ultron": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
package.json
75
package.json
@@ -1,38 +1,71 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.7.11",
|
||||
"description": "Ripple JavaScript client library",
|
||||
|
||||
"version": "0.12.7",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
"src/js/*.js",
|
||||
"build/sjcl.js",
|
||||
"bin/rsign.js"
|
||||
"dist/npm/*",
|
||||
"bin/*",
|
||||
"build/*",
|
||||
"test/*",
|
||||
"Gulpfile.js"
|
||||
],
|
||||
"main": "src/js",
|
||||
"main": "dist/npm/",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"async": "~0.1.22",
|
||||
"ws": "~0.4.22",
|
||||
"extend": "~1.1.1",
|
||||
"simple-jsonrpc": "~0.0.1"
|
||||
"async": "~0.9.0",
|
||||
"babel-runtime": "^5.8.20",
|
||||
"bignumber.js": "^2.0.3",
|
||||
"extend": "~1.2.1",
|
||||
"lodash": "^3.1.0",
|
||||
"lru-cache": "~2.5.0",
|
||||
"ripple-wallet-generator": "^1.0.3",
|
||||
"sjcl": "^1.0.3",
|
||||
"ws": "~0.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.3.17",
|
||||
"buster": "~0.6.2",
|
||||
"grunt-webpack": "~0.4.0"
|
||||
"assert-diff": "^1.0.1",
|
||||
"babel": "^5.3.3",
|
||||
"babel-core": "^5.8.23",
|
||||
"babel-loader": "^5.3.2",
|
||||
"coveralls": "~2.10.0",
|
||||
"eslint": "^1.3.1",
|
||||
"gulp": "~3.8.10",
|
||||
"gulp-bump": "~0.1.13",
|
||||
"gulp-clean-dest": "^0.1.0",
|
||||
"gulp-filelog": "^0.4.1",
|
||||
"gulp-flowtype": "^0.4.1",
|
||||
"gulp-plumber": "^0.6.6",
|
||||
"gulp-react": "^2.0.0",
|
||||
"gulp-rename": "~1.2.0",
|
||||
"gulp-uglify": "~1.1.0",
|
||||
"gulp-util": "^3.0.3",
|
||||
"gulp-watch": "^4.1.0",
|
||||
"istanbul": "~0.3.5",
|
||||
"map-stream": "~0.1.0",
|
||||
"mocha": "~2.1.0",
|
||||
"nock": "^0.34.1",
|
||||
"webpack": "~1.5.3",
|
||||
"yargs": "~1.3.1"
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"test": "buster test"
|
||||
"build": "gulp",
|
||||
"compile": "babel -i runtime -d dist/npm/ src/",
|
||||
"compile-with-source-maps": "babel -i runtime -s -t -d dist/npm/ src/",
|
||||
"prepublish": "npm run compile",
|
||||
"postinstall": "cd node_modules/sjcl; ./configure --with-all --compress=none; make",
|
||||
"test": "istanbul test _mocha",
|
||||
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
||||
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; fi; eslint --reset -c eslintrc src/*.js",
|
||||
"perf": "./scripts/perf_test.sh"
|
||||
},
|
||||
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jedmccaleb/NewCoin.git"
|
||||
"url": "git://github.com/ripple/ripple-lib.git"
|
||||
},
|
||||
|
||||
"readmeFilename": "README.md"
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
44
scripts/modpow.js
Normal file
44
scripts/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 });
|
||||
11
scripts/perf_test.sh
Executable file
11
scripts/perf_test.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
URL="https://www.dropbox.com/s/a0gy7vbb86eeqlq/ledger-full-1000000.json?dl=1"
|
||||
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
DEST="$DIR/cache/ledger-full-1000000.json"
|
||||
if [ ! -e "$DEST" ]
|
||||
then
|
||||
echo "Downloading test data..."
|
||||
mkdir -p "$DIR/cache"
|
||||
curl -L "$URL" > "$DEST"
|
||||
fi
|
||||
npm run compile && time node "$DIR/verify_ledger_json.js" "$DEST"
|
||||
43
scripts/publish
Normal file
43
scripts/publish
Normal file
@@ -0,0 +1,43 @@
|
||||
echo "PUBLISH"
|
||||
|
||||
function exit_on_error {
|
||||
res=$?
|
||||
[[ ${res:-99} -eq 0 ]] || exit $res
|
||||
}
|
||||
|
||||
rm -rf build
|
||||
|
||||
npm install
|
||||
gulp
|
||||
npm test
|
||||
exit_on_error
|
||||
|
||||
echo ""
|
||||
echo "publish to npm"
|
||||
npm publish
|
||||
exit_on_error
|
||||
|
||||
rm -rf dist/bower
|
||||
echo ""
|
||||
echo "publish to bower"
|
||||
|
||||
git clone git@github.com:ripple/bower-ripple.git dist/bower
|
||||
gulp bower
|
||||
exit_on_error
|
||||
|
||||
cd dist/bower
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
exit_on_error
|
||||
|
||||
git commit -m "[TASK] add v$version"
|
||||
exit_on_error
|
||||
|
||||
git tag "v$version"
|
||||
exit_on_error
|
||||
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
|
||||
cd ..
|
||||
43
scripts/publish_rc
Normal file
43
scripts/publish_rc
Normal file
@@ -0,0 +1,43 @@
|
||||
echo "PUBLISH RELEASE CANDIDATE"
|
||||
|
||||
function exit_on_error {
|
||||
res=$?
|
||||
[[ ${res:-99} -eq 0 ]] || exit $res
|
||||
}
|
||||
|
||||
rm -rf build
|
||||
|
||||
npm install
|
||||
gulp
|
||||
npm test
|
||||
exit_on_error
|
||||
|
||||
echo ""
|
||||
echo "publish rc to npm"
|
||||
npm publish --tag beta
|
||||
exit_on_error
|
||||
|
||||
rm -rf dist/bower
|
||||
echo ""
|
||||
echo "publish to bower"
|
||||
|
||||
git clone git@github.com:ripple/bower-ripple.git dist/bower
|
||||
gulp bower
|
||||
exit_on_error
|
||||
|
||||
cd dist/bower
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
exit_on_error
|
||||
|
||||
git commit -m "[TASK] add v$version"
|
||||
exit_on_error
|
||||
|
||||
git tag "v$version"
|
||||
exit_on_error
|
||||
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
|
||||
cd ..
|
||||
12
scripts/publish_to_bower
Normal file
12
scripts/publish_to_bower
Normal file
@@ -0,0 +1,12 @@
|
||||
rm -rf dist/bower
|
||||
git clone git@github.com:ripple/bower-ripple.git dist/bower
|
||||
gulp bower
|
||||
cd dist/bower
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
git commit -m "[TASK] add v$version"
|
||||
git tag "v$version"
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
cd ..
|
||||
66
scripts/verify_ledger_json.js
Executable file
66
scripts/verify_ledger_json.js
Executable file
@@ -0,0 +1,66 @@
|
||||
/* eslint-disable no-var */
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var Amount = require('../dist/npm').Amount;
|
||||
var Ledger = require('../dist/npm/ledger').Ledger;
|
||||
|
||||
function parse_options(from, flags) {
|
||||
var argv = from.slice(),
|
||||
opts_ = {argv: argv};
|
||||
|
||||
flags.forEach(function(f) {
|
||||
// Do we have the flag?
|
||||
var flag_index = argv.indexOf('--' + f);
|
||||
// normalize the name of the flag
|
||||
var flag = f.replace('-', '_');
|
||||
// opts_ has Boolean value for normalized flag key
|
||||
opts_[flag] = flag_index !== -1;
|
||||
if (opts_[flag]) {
|
||||
// remove the flag from the argv
|
||||
argv.splice(flag_index, 1);
|
||||
}
|
||||
});
|
||||
return opts_;
|
||||
}
|
||||
|
||||
var opts = parse_options(process.argv.slice(2), // remove `node` and `this.js`
|
||||
['sanity-test']);
|
||||
|
||||
if (opts.argv.length < 1) {
|
||||
console.error('Usage: scripts/verify_ledger_json path/to/ledger.json');
|
||||
console.error(' optional: --sanity-test (json>binary>json>binary)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var json = fs.readFileSync(opts.argv[0], 'utf-8');
|
||||
var ledger = Ledger.from_json(JSON.parse(json));
|
||||
|
||||
// This will serialize each accountState object to binary and then back to json
|
||||
// before finally serializing for hashing. This is mostly to expose any issues
|
||||
// with ripple-libs binary <--> json codecs.
|
||||
if (opts.sanity_test) {
|
||||
console.log('All accountState nodes will be processed from ' +
|
||||
'json->binary->json->binary. This may take some time ' +
|
||||
'with large ledgers.');
|
||||
}
|
||||
|
||||
// To recompute the hashes of some ledgers, we must allow values that slipped in
|
||||
// before strong policies were in place.
|
||||
Amount.strict_mode = false;
|
||||
|
||||
console.log('Transaction hash in header: ' +
|
||||
ledger.ledger_json.transaction_hash);
|
||||
console.log('Calculated transaction hash: ' +
|
||||
ledger.calc_tx_hash().to_hex());
|
||||
|
||||
console.log('Account state hash in header: ' +
|
||||
ledger.ledger_json.account_hash);
|
||||
|
||||
if (ledger.ledger_json.accountState) {
|
||||
console.log('Calculated account state hash: ' +
|
||||
ledger.calc_account_hash({sanity_test: opts.sanity_test})
|
||||
.to_hex());
|
||||
} else {
|
||||
console.log('Ledger has no accountState');
|
||||
}
|
||||
396
src/account.js
Normal file
396
src/account.js
Normal file
@@ -0,0 +1,396 @@
|
||||
// Routines for working with an account.
|
||||
//
|
||||
// You should not instantiate this class yourself, instead use Remote#account.
|
||||
//
|
||||
// Events:
|
||||
// wallet_clean : True, iff the wallet has been updated.
|
||||
// wallet_dirty : True, iff the wallet needs to be updated.
|
||||
// balance: The current stamp balance.
|
||||
// balance_proposed
|
||||
//
|
||||
|
||||
// var network = require('./network.js');
|
||||
var async = require('async');
|
||||
var util = require('util');
|
||||
var extend = require('extend');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var TransactionManager = require('./transactionmanager').TransactionManager;
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
/**
|
||||
* @constructor Account
|
||||
* @param {Remote} remote
|
||||
* @param {String} account
|
||||
*/
|
||||
|
||||
function Account(remote, account) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._account = UInt160.from_json(account);
|
||||
this._account_id = this._account.to_json();
|
||||
this._subs = 0;
|
||||
|
||||
// Ledger entry object
|
||||
// Important: This must never be overwritten, only extend()-ed
|
||||
this._entry = { };
|
||||
|
||||
function listenerAdded(type, listener) {
|
||||
if (~Account.subscribeEvents.indexOf(type)) {
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.request_subscribe()
|
||||
.add_account(self._account_id)
|
||||
.broadcast().request();
|
||||
}
|
||||
self._subs += 1;
|
||||
}
|
||||
};
|
||||
|
||||
this.on('newListener', listenerAdded);
|
||||
|
||||
function listenerRemoved(type, listener) {
|
||||
if (~Account.subscribeEvents.indexOf(type)) {
|
||||
self._subs -= 1;
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.request_unsubscribe()
|
||||
.add_account(self._account_id)
|
||||
.broadcast().request();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
var changed = false;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
util.inherits(Account, EventEmitter);
|
||||
|
||||
/**
|
||||
* List of events that require a remote subscription to the account.
|
||||
*/
|
||||
|
||||
Account.subscribeEvents = [ 'transaction', 'entry' ];
|
||||
|
||||
Account.prototype.toJson = function() {
|
||||
return this._account.to_json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the AccountId is valid.
|
||||
*
|
||||
* Note: This does not tell you whether the account exists in the ledger.
|
||||
*/
|
||||
|
||||
Account.prototype.isValid = function() {
|
||||
return this._account.is_valid();
|
||||
};
|
||||
|
||||
/**
|
||||
* Request account info
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Account.prototype.getInfo = function(callback) {
|
||||
return this._remote.requestAccountInfo({account: 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} callback
|
||||
*/
|
||||
|
||||
Account.prototype.entry = function(callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
function accountInfo(err, info) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
extend(self._entry, info.account_data);
|
||||
self.emit('entry', self._entry);
|
||||
callback(null, info);
|
||||
}
|
||||
};
|
||||
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Account.prototype.getNextSequence = function(callback) {
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
function isNotFound(err) {
|
||||
return err && typeof err === 'object'
|
||||
&& typeof err.remote === 'object'
|
||||
&& err.remote.error === 'actNotFound';
|
||||
};
|
||||
|
||||
function accountInfo(err, info) {
|
||||
if (isNotFound(err)) {
|
||||
// New accounts will start out as sequence one
|
||||
callback(null, 1);
|
||||
} else if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, info.account_data.Sequence);
|
||||
}
|
||||
};
|
||||
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve this account's Ripple trust lines.
|
||||
*
|
||||
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
|
||||
* 'lines' event. (Not yet implemented.)
|
||||
*
|
||||
* @param {function(err, lines)} callback Called with the result
|
||||
*/
|
||||
|
||||
Account.prototype.lines = function(callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
function accountLines(err, res) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
self._lines = res.lines;
|
||||
self.emit('lines', self._lines);
|
||||
callback(null, res);
|
||||
}
|
||||
}
|
||||
|
||||
this._remote.requestAccountLines({account: this._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 callback = typeof callback === 'function' ? callback : function(){};
|
||||
|
||||
self.lines(function(err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var line;
|
||||
|
||||
top:
|
||||
for (var i=0; i<data.lines.length; i++) {
|
||||
var l = data.lines[i];
|
||||
if (l.account === address && l.currency === currency) {
|
||||
line = l;
|
||||
break top;
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, line);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify object of a relevant transaction.
|
||||
*
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*
|
||||
* @param {Object} message
|
||||
*/
|
||||
|
||||
Account.prototype.notify =
|
||||
Account.prototype.notifyTx = function(transaction) {
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (!this._subs) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('transaction', transaction);
|
||||
|
||||
var account = transaction.transaction.Account;
|
||||
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isThisAccount = (account === this._account_id);
|
||||
|
||||
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound', transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a transaction to an account's
|
||||
* transaction manager
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
|
||||
Account.prototype.submit = function(transaction) {
|
||||
this._transactionManager.submit(transaction);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the given public key is valid for this account
|
||||
*
|
||||
* @param {Hex-encoded String|RippleAddress} public_key
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @callback
|
||||
* @param {Error} err
|
||||
* @param {Boolean} true if the public key is valid and active, false otherwise
|
||||
*/
|
||||
Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
var self = this;
|
||||
var public_key_as_uint160;
|
||||
|
||||
try {
|
||||
public_key_as_uint160 = Account._publicKeyToAddress(public_key);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
function getAccountInfo(async_callback) {
|
||||
self.getInfo(function(err, account_info_res){
|
||||
|
||||
// If the remote responds with an Account Not Found error then the account
|
||||
// is unfunded and thus we can assume that the master key is active
|
||||
if (err && err.remote && err.remote.error === 'actNotFound') {
|
||||
async_callback(null, null);
|
||||
} else {
|
||||
async_callback(err, account_info_res);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function publicKeyIsValid(account_info_res, async_callback) {
|
||||
// Catch the case of unfunded accounts
|
||||
if (!account_info_res) {
|
||||
|
||||
if (public_key_as_uint160 === self._account_id) {
|
||||
async_callback(null, true);
|
||||
} else {
|
||||
async_callback(null, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var account_info = account_info_res.account_data;
|
||||
|
||||
// Respond with true if the RegularKey is set and matches the given public key or
|
||||
// if the public key matches the account address and the lsfDisableMaster is not set
|
||||
if (account_info.RegularKey &&
|
||||
account_info.RegularKey === public_key_as_uint160) {
|
||||
async_callback(null, true);
|
||||
} else if (account_info.Account === public_key_as_uint160 &&
|
||||
((account_info.Flags & 0x00100000) === 0)) {
|
||||
async_callback(null, true);
|
||||
} else {
|
||||
async_callback(null, false);
|
||||
}
|
||||
};
|
||||
|
||||
var steps = [
|
||||
getAccountInfo,
|
||||
publicKeyIsValid
|
||||
];
|
||||
|
||||
async.waterfall(steps, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a hex-encoded public key to a Ripple Address
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {Hex-encoded string|RippleAddress} public_key
|
||||
* @returns {RippleAddress}
|
||||
*/
|
||||
Account._publicKeyToAddress = function(public_key) {
|
||||
// Based on functions in /src/js/ripple/keypair.js
|
||||
function hexToUInt160(public_key) {
|
||||
var bits = sjcl.codec.hex.toBits(public_key);
|
||||
var hash = sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
var address = UInt160.from_bits(hash);
|
||||
address.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
return address.to_json();
|
||||
};
|
||||
|
||||
if (UInt160.is_valid(public_key)) {
|
||||
return public_key;
|
||||
} else if (/^[0-9a-fA-F]+$/.test(public_key)) {
|
||||
return hexToUInt160(public_key);
|
||||
} else {
|
||||
throw new Error('Public key is invalid. Must be a UInt160 or a hex string');
|
||||
}
|
||||
};
|
||||
|
||||
exports.Account = Account;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
1011
src/amount.js
Normal file
1011
src/amount.js
Normal file
File diff suppressed because it is too large
Load Diff
438
src/autobridgecalculator.js
Normal file
438
src/autobridgecalculator.js
Normal file
@@ -0,0 +1,438 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Amount = require('./amount').Amount;
|
||||
const Utils = require('./orderbookutils');
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
assert(!_.isNull(number) && !isNaN(number), message);
|
||||
}
|
||||
|
||||
function assertValidLegOneOffer(legOneOffer, message) {
|
||||
assert(legOneOffer);
|
||||
assert.strictEqual(typeof legOneOffer, 'object', message);
|
||||
assert.strictEqual(typeof legOneOffer.TakerPays, 'object', message);
|
||||
assertValidNumber(legOneOffer.TakerGets, message);
|
||||
}
|
||||
|
||||
function AutobridgeCalculator(currencyGets, currencyPays,
|
||||
legOneOffers, legTwoOffers, issuerGets, issuerPays) {
|
||||
this._currencyGets = currencyGets;
|
||||
this._currencyGetsHex = currencyGets.to_hex();
|
||||
this._currencyPaysHex = currencyPays.to_hex();
|
||||
this._issuerGets = issuerGets;
|
||||
this._issuerPays = issuerPays;
|
||||
this.legOneOffers = _.cloneDeep(legOneOffers);
|
||||
this.legTwoOffers = _.cloneDeep(legTwoOffers);
|
||||
|
||||
this._ownerFundsLeftover = {};
|
||||
}
|
||||
|
||||
AutobridgeCalculator.NULL_AMOUNT = Utils.normalizeAmount('0');
|
||||
|
||||
/**
|
||||
* Calculates an ordered array of autobridged offers by quality
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.calculate = function() {
|
||||
let legOnePointer = 0;
|
||||
let legTwoPointer = 0;
|
||||
|
||||
const offersAutobridged = [];
|
||||
|
||||
this.clearOwnerFundsLeftover();
|
||||
|
||||
while (this.legOneOffers[legOnePointer] && this.legTwoOffers[legTwoPointer]) {
|
||||
const legOneOffer = this.legOneOffers[legOnePointer];
|
||||
const legTwoOffer = this.legTwoOffers[legTwoPointer];
|
||||
const leftoverFunds = this.getLeftoverOwnerFunds(legOneOffer.Account);
|
||||
let autobridgedOffer;
|
||||
|
||||
if (legOneOffer.Account === legTwoOffer.Account) {
|
||||
this.unclampLegOneOwnerFunds(legOneOffer);
|
||||
} else if (!legOneOffer.is_fully_funded && !leftoverFunds.is_zero()) {
|
||||
this.adjustLegOneFundedAmount(legOneOffer);
|
||||
}
|
||||
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
|
||||
|
||||
if (legOneTakerGetsFunded.is_zero()) {
|
||||
legOnePointer++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (legTwoTakerPaysFunded.is_zero()) {
|
||||
legTwoPointer++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// using private fields for speed
|
||||
if (legOneTakerGetsFunded._value.comparedTo(
|
||||
legTwoTakerPaysFunded._value) > 0) {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithClampedLegOne(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legTwoPointer++;
|
||||
} else if (legTwoTakerPaysFunded._value.comparedTo(
|
||||
legOneTakerGetsFunded._value) > 0) {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithClampedLegTwo(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legOnePointer++;
|
||||
} else {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithoutClamps(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legOnePointer++;
|
||||
legTwoPointer++;
|
||||
}
|
||||
|
||||
offersAutobridged.push(autobridgedOffer);
|
||||
}
|
||||
|
||||
return offersAutobridged;
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the output from leg one is greater than the input to leg two.
|
||||
* Therefore, we must effectively clamp leg one output to leg two input.
|
||||
*
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegOne =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets);
|
||||
|
||||
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer);
|
||||
const autobridgedTakerPays = legTwoTakerPaysFunded.multiply(legOneQuality);
|
||||
|
||||
if (legOneOffer.Account === legTwoOffer.Account) {
|
||||
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer);
|
||||
const updatedTakerGets = legOneTakerGets.subtract(legTwoTakerPaysFunded);
|
||||
|
||||
this.setLegOneTakerGets(legOneOffer, updatedTakerGets);
|
||||
|
||||
this.clampLegOneOwnerFunds(legOneOffer);
|
||||
} else {
|
||||
// Update funded amount since leg one offer was not completely consumed
|
||||
const updatedTakerGetsFunded = legOneTakerGetsFunded
|
||||
.subtract(legTwoTakerPaysFunded);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, updatedTakerGetsFunded);
|
||||
}
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the input from leg two is greater than the output to leg one.
|
||||
* Therefore, we must effectively clamp leg two input to leg one output.
|
||||
*
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegTwo =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
|
||||
const legTwoQuality = Utils.getOfferQuality(legTwoOffer, this._currencyGets);
|
||||
|
||||
const autobridgedTakerGets = legOneTakerGetsFunded.divide(legTwoQuality);
|
||||
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer);
|
||||
|
||||
// Update funded amount since leg two offer was not completely consumed
|
||||
legTwoOffer.taker_gets_funded = Utils.getOfferTakerGetsFunded(legTwoOffer)
|
||||
.subtract(autobridgedTakerGets)
|
||||
.to_text();
|
||||
legTwoOffer.taker_pays_funded = legTwoTakerPaysFunded
|
||||
.subtract(legOneTakerGetsFunded)
|
||||
.to_text();
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the output from leg one and the input to leg two are the same.
|
||||
* We do not need to clamp either.
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithoutClamps =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer);
|
||||
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer);
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear owner funds leftovers
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.clearOwnerFundsLeftover = function() {
|
||||
this._ownerFundsLeftover = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset owner funds leftovers for an account to 0
|
||||
*
|
||||
* @param {String} account
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.resetOwnerFundsLeftover = function(account) {
|
||||
this._ownerFundsLeftover[account] = Utils.normalizeAmount('0');
|
||||
|
||||
return this._ownerFundsLeftover[account];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve leftover funds found after clamping leg one by account
|
||||
*
|
||||
* @param {String} account
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getLeftoverOwnerFunds = function(account) {
|
||||
let amount = this._ownerFundsLeftover[account];
|
||||
|
||||
if (!amount) {
|
||||
amount = AutobridgeCalculator.NULL_AMOUNT.clone();
|
||||
}
|
||||
|
||||
return amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add funds to account's leftover funds
|
||||
*
|
||||
* @param {String} account
|
||||
* @param {Amount} amount
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.addLeftoverOwnerFunds =
|
||||
function(account, amount) {
|
||||
assert(amount instanceof Amount, 'Amount is invalid');
|
||||
|
||||
this._ownerFundsLeftover[account] = this.getLeftoverOwnerFunds(account)
|
||||
.add(amount);
|
||||
|
||||
return this._ownerFundsLeftover[account];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set account's leftover funds
|
||||
*
|
||||
* @param {String} account
|
||||
* @param {Amount} amount
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLeftoverOwnerFunds =
|
||||
function(account, amount) {
|
||||
assert(amount instanceof Amount, 'Amount is invalid');
|
||||
|
||||
this._ownerFundsLeftover[account] = amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format an autobridged offer and compute synthetic values (e.g. quality)
|
||||
*
|
||||
* @param {Amount} takerGets
|
||||
* @param {Amount} takerPays
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.formatAutobridgedOffer =
|
||||
function(takerGets, takerPays) {
|
||||
assert(takerGets instanceof Amount, 'Autobridged taker gets is invalid');
|
||||
assert(takerPays instanceof Amount, 'Autobridged taker pays is invalid');
|
||||
|
||||
const autobridgedOffer = {};
|
||||
const quality = takerPays.divide(takerGets);
|
||||
|
||||
autobridgedOffer.TakerGets = {
|
||||
value: takerGets.to_text(),
|
||||
currency: this._currencyGetsHex,
|
||||
issuer: this._issuerGets
|
||||
};
|
||||
|
||||
autobridgedOffer.TakerPays = {
|
||||
value: takerPays.to_text(),
|
||||
currency: this._currencyPaysHex,
|
||||
issuer: this._issuerPays
|
||||
};
|
||||
|
||||
autobridgedOffer.quality = quality.to_text();
|
||||
|
||||
autobridgedOffer.taker_gets_funded = autobridgedOffer.TakerGets.value;
|
||||
autobridgedOffer.taker_pays_funded = autobridgedOffer.TakerPays.value;
|
||||
|
||||
autobridgedOffer.autobridged = true;
|
||||
|
||||
autobridgedOffer.BookDirectory = Utils.convertOfferQualityToHexFromText(autobridgedOffer.quality);
|
||||
autobridgedOffer.qualityHex = autobridgedOffer.BookDirectory;
|
||||
|
||||
return autobridgedOffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove funds clamp on leg one offer. This is necessary when the two offers
|
||||
* are owned by the same account. In this case, it doesn't matter if offer one
|
||||
* is not fully funded. Leg one out goes to leg two in and since its the same
|
||||
* account, an infinite amount can flow.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.unclampLegOneOwnerFunds = function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
|
||||
legOneOffer.initTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
|
||||
|
||||
this.setLegOneTakerGetsFunded(
|
||||
legOneOffer,
|
||||
Utils.getOfferTakerGets(legOneOffer)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply clamp back on leg one offer after a round of autobridge calculation
|
||||
* completes. We must reapply clamps that have been removed because we cannot
|
||||
* guarantee that the next offer from leg two will also be from the same
|
||||
* account.
|
||||
*
|
||||
* When we reapply, it could happen that the amount of TakerGets left after
|
||||
* the autobridge calculation is less than the original funded amount. In this
|
||||
* case, we have extra funds we can use towards unfunded offers with worse
|
||||
* quality by the same owner.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.clampLegOneOwnerFunds = function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
|
||||
const takerGets = Utils.getOfferTakerGets(legOneOffer);
|
||||
|
||||
if (takerGets.compareTo(legOneOffer.initTakerGetsFunded) > 0) {
|
||||
// After clamping, TakerGets is still greater than initial funded amount
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, legOneOffer.initTakerGetsFunded);
|
||||
} else {
|
||||
const updatedLeftover = legOneOffer.initTakerGetsFunded.subtract(takerGets);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, takerGets);
|
||||
this.addLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Increase leg one offer funded amount with extra funds found after applying
|
||||
* clamp.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.adjustLegOneFundedAmount =
|
||||
function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(!legOneOffer.is_fully_funded, 'Leg one offer cannot be fully funded');
|
||||
|
||||
const fundedSum = Utils.getOfferTakerGetsFunded(legOneOffer)
|
||||
.add(this.getLeftoverOwnerFunds(legOneOffer.Account));
|
||||
|
||||
if (fundedSum.compareTo(Utils.getOfferTakerGets(legOneOffer)) >= 0) {
|
||||
// There are enough extra funds to fully fund the offer
|
||||
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer);
|
||||
const updatedLeftover = fundedSum.subtract(legOneTakerGets);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, legOneTakerGets);
|
||||
this.setLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover);
|
||||
} else {
|
||||
// There are not enough extra funds to fully fund the offer
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, fundedSum);
|
||||
this.resetOwnerFundsLeftover(legOneOffer.Account);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set taker gets funded amount for a IOU:XRP offer. Also calculates taker
|
||||
* pays funded using offer quality and updates is_fully_funded flag
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
* @param {Amount} takerGetsFunded
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLegOneTakerGetsFunded =
|
||||
function setLegOneTakerGetsFunded(legOneOffer, takerGetsFunded) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(takerGetsFunded instanceof Amount, 'Taker gets funded is invalid');
|
||||
|
||||
legOneOffer.taker_gets_funded = takerGetsFunded.to_text();
|
||||
legOneOffer.taker_pays_funded = takerGetsFunded
|
||||
.multiply(Utils.getOfferQuality(legOneOffer, this._currencyGets))
|
||||
.to_text();
|
||||
|
||||
if (legOneOffer.taker_gets_funded === legOneOffer.TakerGets.value) {
|
||||
legOneOffer.is_fully_funded = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set taker gets amount for a IOU:XRP offer. Also calculates taker pays
|
||||
* using offer quality
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
* @param {Amount} takerGets
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLegOneTakerGets =
|
||||
function(legOneOffer, takerGets) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(takerGets instanceof Amount, 'Taker gets funded is invalid');
|
||||
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets);
|
||||
|
||||
legOneOffer.TakerGets = takerGets.to_text();
|
||||
legOneOffer.TakerPays = takerGets.multiply(legOneQuality);
|
||||
};
|
||||
|
||||
module.exports = AutobridgeCalculator;
|
||||
154
src/base.js
Normal file
154
src/base.js
Normal file
@@ -0,0 +1,154 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const sjcl = require('./utils').sjcl;
|
||||
const utils = require('./utils');
|
||||
const extend = require('extend');
|
||||
const convertBase = require('./baseconverter');
|
||||
|
||||
const Base = {};
|
||||
|
||||
const 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
|
||||
});
|
||||
|
||||
function sha256(bytes) {
|
||||
return sjcl.codec.bytes.fromBits(
|
||||
sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
|
||||
}
|
||||
|
||||
function encodeString(alphabet, input) {
|
||||
if (input.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const leadingZeros = _.takeWhile(input, function(d) {
|
||||
return d === 0;
|
||||
});
|
||||
const out = convertBase(input, 256, 58).map(function(digit) {
|
||||
if (digit < 0 || digit >= alphabet.length) {
|
||||
throw new Error('Value ' + digit + ' is out of bounds for encoding');
|
||||
}
|
||||
return alphabet[digit];
|
||||
});
|
||||
const prefix = leadingZeros.map(function() {
|
||||
return alphabet[0];
|
||||
});
|
||||
return prefix.concat(out).join('');
|
||||
}
|
||||
|
||||
function decodeString(indexes, input) {
|
||||
if (input.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const input58 = input.split('').map(function(c) {
|
||||
const charCode = c.charCodeAt(0);
|
||||
if (charCode >= indexes.length || indexes[charCode] === -1) {
|
||||
throw new Error('Character ' + c + ' is not valid for encoding');
|
||||
}
|
||||
return indexes[charCode];
|
||||
});
|
||||
const leadingZeros = _.takeWhile(input58, function(d) {
|
||||
return d === 0;
|
||||
});
|
||||
const out = convertBase(input58, 58, 256);
|
||||
return leadingZeros.concat(out);
|
||||
}
|
||||
|
||||
function Base58(alphabet) {
|
||||
const indexes = utils.arraySet(128, -1);
|
||||
for (let i = 0; i < alphabet.length; i++) {
|
||||
indexes[alphabet.charCodeAt(i)] = i;
|
||||
}
|
||||
return {
|
||||
decode: decodeString.bind(null, indexes),
|
||||
encode: encodeString.bind(null, alphabet)
|
||||
};
|
||||
}
|
||||
|
||||
Base.encoders = {};
|
||||
Object.keys(alphabets).forEach(function(alphabet) {
|
||||
Base.encoders[alphabet] = new Base58(alphabets[alphabet]);
|
||||
});
|
||||
|
||||
// --> input: big-endian array of bytes.
|
||||
// <-- string at least as long as input.
|
||||
Base.encode = function(input, alpha) {
|
||||
return this.encoders[alpha || 'ripple'].encode(input);
|
||||
};
|
||||
|
||||
// --> input: String
|
||||
// <-- array of bytes or undefined.
|
||||
Base.decode = function(input, alpha) {
|
||||
if (typeof input !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return this.encoders[alpha || 'ripple'].decode(input);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
Base.verify_checksum = function(bytes) {
|
||||
const computed = sha256(sha256(bytes.slice(0, -4))).slice(0, 4);
|
||||
const checksum = bytes.slice(-4);
|
||||
return _.isEqual(computed, checksum);
|
||||
};
|
||||
|
||||
// --> input: Array
|
||||
// <-- String
|
||||
Base.encode_check = function(version, input, alphabet) {
|
||||
const buffer = [].concat(version, input);
|
||||
const check = sha256(sha256(buffer)).slice(0, 4);
|
||||
|
||||
return Base.encode([].concat(buffer, check), alphabet);
|
||||
};
|
||||
|
||||
// --> input : String
|
||||
// <-- NaN || sjcl.bn
|
||||
Base.decode_check = function(version, input, alphabet) {
|
||||
const buffer = Base.decode(input, alphabet);
|
||||
|
||||
if (!buffer || buffer.length < 5) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// Single valid version
|
||||
if (typeof version === 'number' && buffer[0] !== version) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// Multiple allowed versions
|
||||
if (Array.isArray(version) && _.every(version, function(v) {
|
||||
return v !== buffer[0];
|
||||
})) {
|
||||
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 sjcl.bn.fromBits(
|
||||
sjcl.codec.bytes.toBits(buffer.slice(0, -4)));
|
||||
};
|
||||
|
||||
exports.Base = Base;
|
||||
43
src/baseconverter.js
Normal file
43
src/baseconverter.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
function normalize(digitArray) {
|
||||
let i = 0;
|
||||
while (digitArray[i] === 0) {
|
||||
++i;
|
||||
}
|
||||
if (i > 0) {
|
||||
digitArray.splice(0, i);
|
||||
}
|
||||
return digitArray;
|
||||
}
|
||||
|
||||
function divmod(digitArray, base, divisor) {
|
||||
let remainder = 0;
|
||||
let temp, divided;
|
||||
let j = -1;
|
||||
|
||||
const length = digitArray.length;
|
||||
const quotient = new Array(length);
|
||||
|
||||
while (++j < length) {
|
||||
temp = remainder * base + digitArray[j];
|
||||
divided = temp / divisor;
|
||||
quotient[j] = divided << 0;
|
||||
remainder = temp % divisor;
|
||||
}
|
||||
return {quotient: normalize(quotient), remainder: remainder};
|
||||
}
|
||||
|
||||
function convertBase(digitArray, fromBase, toBase) {
|
||||
const result = [];
|
||||
let dividend = digitArray, qr;
|
||||
while (dividend.length > 0) {
|
||||
qr = divmod(dividend, fromBase, toBase);
|
||||
result.unshift(qr.remainder);
|
||||
dividend = qr.quotient;
|
||||
}
|
||||
return normalize(result);
|
||||
}
|
||||
|
||||
module.exports = convertBase;
|
||||
440
src/binformat.js
Normal file
440
src/binformat.js
Normal file
@@ -0,0 +1,440 @@
|
||||
'use strict';
|
||||
|
||||
/*eslint no-multi-spaces:0,space-in-brackets:0,key-spacing:0,comma-spacing:0*/
|
||||
|
||||
/**
|
||||
* Data type map.
|
||||
*
|
||||
* Mapping of type ids to data types. The type id is specified by the high
|
||||
*
|
||||
* For reference, see rippled's definition:
|
||||
* https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol
|
||||
* /SField.cpp
|
||||
*/
|
||||
|
||||
exports.types = [
|
||||
undefined,
|
||||
|
||||
// Common
|
||||
'Int16', // 1
|
||||
'Int32', // 2
|
||||
'Int64', // 3
|
||||
'Hash128', // 4
|
||||
'Hash256', // 5
|
||||
'Amount', // 6
|
||||
'VL', // 7
|
||||
'Account', // 8
|
||||
|
||||
// 9-13 reserved
|
||||
undefined, // 9
|
||||
undefined, // 10
|
||||
undefined, // 11
|
||||
undefined, // 12
|
||||
undefined, // 13
|
||||
|
||||
'Object', // 14
|
||||
'Array', // 15
|
||||
|
||||
// Uncommon
|
||||
'Int8', // 16
|
||||
'Hash160', // 17
|
||||
'PathSet', // 18
|
||||
'Vector256' // 19
|
||||
];
|
||||
|
||||
/**
|
||||
* Field type map.
|
||||
*
|
||||
* Mapping of field type id to field type name.
|
||||
*/
|
||||
|
||||
const FIELDS_MAP = exports.fields = {
|
||||
// Common types
|
||||
1: { // Int16
|
||||
1: 'LedgerEntryType',
|
||||
2: 'TransactionType'
|
||||
},
|
||||
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: 'Amendment',
|
||||
20: 'TicketID'
|
||||
},
|
||||
6: { // Amount
|
||||
1: 'Amount',
|
||||
2: 'Balance',
|
||||
3: 'LimitAmount',
|
||||
4: 'TakerPays',
|
||||
5: 'TakerGets',
|
||||
6: 'LowLimit',
|
||||
7: 'HighLimit',
|
||||
8: 'Fee',
|
||||
9: 'SendMax',
|
||||
16: 'MinimumOffer',
|
||||
17: 'RippleEscrow',
|
||||
18: 'DeliveredAmount'
|
||||
},
|
||||
7: { // VL
|
||||
1: 'PublicKey',
|
||||
2: 'MessageKey',
|
||||
3: 'SigningPubKey',
|
||||
4: 'TxnSignature',
|
||||
5: 'Generator',
|
||||
6: 'Signature',
|
||||
7: 'Domain',
|
||||
8: 'FundCode',
|
||||
9: 'RemoveCode',
|
||||
10: 'ExpireCode',
|
||||
11: 'CreateCode',
|
||||
12: 'MemoType',
|
||||
13: 'MemoData',
|
||||
14: 'MemoFormat'
|
||||
},
|
||||
8: { // Account
|
||||
1: 'Account',
|
||||
2: 'Owner',
|
||||
3: 'Destination',
|
||||
4: 'Issuer',
|
||||
7: 'Target',
|
||||
8: 'RegularKey'
|
||||
},
|
||||
14: { // Object
|
||||
1: undefined, // end of Object
|
||||
2: 'TransactionMetaData',
|
||||
3: 'CreatedNode',
|
||||
4: 'DeletedNode',
|
||||
5: 'ModifiedNode',
|
||||
6: 'PreviousFields',
|
||||
7: 'FinalFields',
|
||||
8: 'NewFields',
|
||||
9: 'TemplateEntry',
|
||||
10: 'Memo'
|
||||
},
|
||||
15: { // Array
|
||||
1: undefined, // 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: 'Amendments'
|
||||
}
|
||||
};
|
||||
|
||||
let INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
|
||||
|
||||
Object.keys(FIELDS_MAP).forEach(function(k1) {
|
||||
Object.keys(FIELDS_MAP[k1]).forEach(function(k2) {
|
||||
INVERSE_FIELDS_MAP[FIELDS_MAP[k1][k2]] = [ Number(k1), Number(k2) ];
|
||||
});
|
||||
});
|
||||
|
||||
const REQUIRED = exports.REQUIRED = 0,
|
||||
OPTIONAL = exports.OPTIONAL = 1,
|
||||
DEFAULT = exports.DEFAULT = 2;
|
||||
|
||||
const base = [
|
||||
[ 'TransactionType' , REQUIRED ],
|
||||
[ 'Flags' , OPTIONAL ],
|
||||
[ 'SourceTag' , OPTIONAL ],
|
||||
[ 'LastLedgerSequence' , OPTIONAL ],
|
||||
[ 'Account' , REQUIRED ],
|
||||
[ 'Sequence' , REQUIRED ],
|
||||
[ 'Fee' , REQUIRED ],
|
||||
[ 'OperationLimit' , OPTIONAL ],
|
||||
[ 'SigningPubKey' , REQUIRED ],
|
||||
[ 'TxnSignature' , OPTIONAL ],
|
||||
[ 'AccountTxnID' , OPTIONAL ],
|
||||
[ 'Memos' , OPTIONAL ]
|
||||
];
|
||||
|
||||
exports.tx = {
|
||||
AccountSet: [3].concat(base, [
|
||||
[ 'EmailHash' , OPTIONAL ],
|
||||
[ 'WalletLocator' , OPTIONAL ],
|
||||
[ 'WalletSize' , OPTIONAL ],
|
||||
[ 'MessageKey' , OPTIONAL ],
|
||||
[ 'Domain' , OPTIONAL ],
|
||||
[ 'TransferRate' , OPTIONAL ],
|
||||
[ 'SetFlag' , OPTIONAL ],
|
||||
[ 'ClearFlag' , OPTIONAL ]
|
||||
]),
|
||||
TrustSet: [20].concat(base, [
|
||||
[ 'LimitAmount' , OPTIONAL ],
|
||||
[ 'QualityIn' , OPTIONAL ],
|
||||
[ 'QualityOut' , OPTIONAL ]
|
||||
]),
|
||||
OfferCreate: [7].concat(base, [
|
||||
[ 'TakerPays' , REQUIRED ],
|
||||
[ 'TakerGets' , REQUIRED ],
|
||||
[ 'Expiration' , OPTIONAL ],
|
||||
[ 'OfferSequence' , OPTIONAL ]
|
||||
]),
|
||||
OfferCancel: [8].concat(base, [
|
||||
[ 'OfferSequence' , REQUIRED ]
|
||||
]),
|
||||
SetRegularKey: [5].concat(base, [
|
||||
[ 'RegularKey' , OPTIONAL ]
|
||||
]),
|
||||
Payment: [0].concat(base, [
|
||||
[ 'Destination' , REQUIRED ],
|
||||
[ 'Amount' , REQUIRED ],
|
||||
[ 'SendMax' , OPTIONAL ],
|
||||
[ 'Paths' , DEFAULT ],
|
||||
[ 'InvoiceID' , OPTIONAL ],
|
||||
[ 'DestinationTag' , OPTIONAL ]
|
||||
]),
|
||||
Contract: [9].concat(base, [
|
||||
[ 'Expiration' , REQUIRED ],
|
||||
[ 'BondAmount' , REQUIRED ],
|
||||
[ 'StampEscrow' , REQUIRED ],
|
||||
[ 'RippleEscrow' , REQUIRED ],
|
||||
[ 'CreateCode' , OPTIONAL ],
|
||||
[ 'FundCode' , OPTIONAL ],
|
||||
[ 'RemoveCode' , OPTIONAL ],
|
||||
[ 'ExpireCode' , OPTIONAL ]
|
||||
]),
|
||||
RemoveContract: [10].concat(base, [
|
||||
[ 'Target' , REQUIRED ]
|
||||
]),
|
||||
EnableFeature: [100].concat(base, [
|
||||
[ 'Feature' , REQUIRED ]
|
||||
]),
|
||||
EnableAmendment: [100].concat(base, [
|
||||
[ 'Amendment' , REQUIRED ]
|
||||
]),
|
||||
SetFee: [101].concat(base, [
|
||||
[ 'BaseFee' , REQUIRED ],
|
||||
[ 'ReferenceFeeUnits' , REQUIRED ],
|
||||
[ 'ReserveBase' , REQUIRED ],
|
||||
[ 'ReserveIncrement' , REQUIRED ]
|
||||
]),
|
||||
TicketCreate: [10].concat(base, [
|
||||
[ 'Target' , OPTIONAL ],
|
||||
[ 'Expiration' , OPTIONAL ]
|
||||
]),
|
||||
TicketCancel: [11].concat(base, [
|
||||
[ 'TicketID' , REQUIRED ]
|
||||
])
|
||||
};
|
||||
|
||||
const sleBase = [
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED]
|
||||
];
|
||||
|
||||
exports.ledger = {
|
||||
AccountRoot: [97].concat(sleBase,[
|
||||
['Sequence', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['TransferRate', OPTIONAL],
|
||||
['WalletSize', OPTIONAL],
|
||||
['OwnerCount', REQUIRED],
|
||||
['EmailHash', OPTIONAL],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['AccountTxnID', OPTIONAL],
|
||||
['WalletLocator', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['MessageKey', OPTIONAL],
|
||||
['Domain', OPTIONAL],
|
||||
['Account', REQUIRED],
|
||||
['RegularKey', OPTIONAL]]),
|
||||
Contract: [99].concat(sleBase,[
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['Expiration', REQUIRED],
|
||||
['BondAmount', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['Balance', REQUIRED],
|
||||
['FundCode', OPTIONAL],
|
||||
['RemoveCode', OPTIONAL],
|
||||
['ExpireCode', OPTIONAL],
|
||||
['CreateCode', OPTIONAL],
|
||||
['Account', REQUIRED],
|
||||
['Owner', REQUIRED],
|
||||
['Issuer', REQUIRED]]),
|
||||
DirectoryNode: [100].concat(sleBase,[
|
||||
['IndexNext', OPTIONAL],
|
||||
['IndexPrevious', OPTIONAL],
|
||||
['ExchangeRate', OPTIONAL],
|
||||
['RootIndex', REQUIRED],
|
||||
['Owner', OPTIONAL],
|
||||
['TakerPaysCurrency', OPTIONAL],
|
||||
['TakerPaysIssuer', OPTIONAL],
|
||||
['TakerGetsCurrency', OPTIONAL],
|
||||
['TakerGetsIssuer', OPTIONAL],
|
||||
['Indexes', REQUIRED]]),
|
||||
EnabledFeatures: [102].concat(sleBase,[
|
||||
['Features', REQUIRED]]),
|
||||
FeeSettings: [115].concat(sleBase,[
|
||||
['ReferenceFeeUnits', REQUIRED],
|
||||
['ReserveBase', REQUIRED],
|
||||
['ReserveIncrement', REQUIRED],
|
||||
['BaseFee', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL]]),
|
||||
GeneratorMap: [103].concat(sleBase,[
|
||||
['Generator', REQUIRED]]),
|
||||
LedgerHashes: [104].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['FirstLedgerSequence', OPTIONAL],
|
||||
['LastLedgerSequence', OPTIONAL],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Hashes', REQUIRED]]),
|
||||
Nickname: [110].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['MinimumOffer', OPTIONAL],
|
||||
['Account', REQUIRED]]),
|
||||
Offer: [111].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['Sequence', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['Expiration', OPTIONAL],
|
||||
['BookNode', REQUIRED],
|
||||
['OwnerNode', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['BookDirectory', REQUIRED],
|
||||
['TakerPays', REQUIRED],
|
||||
['TakerGets', REQUIRED],
|
||||
['Account', REQUIRED]]),
|
||||
RippleState: [114].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['HighQualityIn', OPTIONAL],
|
||||
['HighQualityOut', OPTIONAL],
|
||||
['LowQualityIn', OPTIONAL],
|
||||
['LowQualityOut', OPTIONAL],
|
||||
['LowNode', OPTIONAL],
|
||||
['HighNode', OPTIONAL],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['LowLimit', REQUIRED],
|
||||
['HighLimit', REQUIRED]])
|
||||
};
|
||||
|
||||
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, // Deprecated, old ambiguous unfunded.
|
||||
tecMASTER_DISABLED : 130,
|
||||
tecNO_REGULAR_KEY : 131,
|
||||
tecOWNERS : 132,
|
||||
tecNO_ISSUER : 133,
|
||||
tecNO_AUTH : 134,
|
||||
tecNO_LINE : 135,
|
||||
tecINSUFF_FEE : 136,
|
||||
tecFROZEN : 137,
|
||||
tecNO_TARGET : 138,
|
||||
tecNO_PERMISSION : 139,
|
||||
tecNO_ENTRY : 140,
|
||||
tecINSUFFICIENT_RESERVE : 141,
|
||||
tecNEED_MASTER_KEY : 142,
|
||||
tecDST_TAG_NEEDED : 143,
|
||||
tecINTERNAL : 144
|
||||
};
|
||||
402
src/currency.js
Normal file
402
src/currency.js
Normal file
@@ -0,0 +1,402 @@
|
||||
'use strict';
|
||||
|
||||
var extend = require('extend');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var utils = require('./utils');
|
||||
var Float = require('./ieee754').Float;
|
||||
|
||||
//
|
||||
// Currency support
|
||||
//
|
||||
|
||||
var Currency = extend(function() {
|
||||
// Internal form: 0 = XRP. 3 letter-code.
|
||||
// XXX Internal should be 0 or hex with three letter annotation when valid.
|
||||
|
||||
// Json form:
|
||||
// '', 'XRP', '0': 0
|
||||
// 3-letter code: ...
|
||||
// XXX Should support hex, C++ doesn't currently allow it.
|
||||
|
||||
this._value = NaN;
|
||||
this._update();
|
||||
}, UInt160);
|
||||
|
||||
Currency.prototype = Object.create(extend({}, UInt160.prototype));
|
||||
Currency.prototype.constructor = Currency;
|
||||
|
||||
Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
|
||||
|
||||
/**
|
||||
* Tries to correctly interpret a Currency as entered by a user.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* USD => currency
|
||||
* USD - Dollar => currency with optional full currency
|
||||
* name
|
||||
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate
|
||||
* per year
|
||||
* XAU - Gold (-0.5%pa) => Optionally allowed full currency name
|
||||
* USD (1%pa) => US dollars with 1% effective interest
|
||||
* per year
|
||||
* INR - Indian Rupees => Optional full currency name with spaces
|
||||
* TYX - 30-Year Treasuries => Optional full currency with numbers
|
||||
* and a dash
|
||||
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers,
|
||||
* dash and interest rate
|
||||
*
|
||||
* The regular expression below matches above cases, broken down for better
|
||||
* understanding:
|
||||
*
|
||||
* ^\s* // start with any amount of whitespace
|
||||
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3
|
||||
* digit numeric currency-code. See ISO 4217
|
||||
* (\s*-\s*[- \w]+) // optional full currency name following the dash
|
||||
* after currency code, full currency code can
|
||||
* contain letters, numbers and dashes
|
||||
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and
|
||||
* . notation (-0.5%pa)
|
||||
* \s*$ // end with any amount of whitespace
|
||||
*
|
||||
*/
|
||||
|
||||
/*eslint-disable max-len*/
|
||||
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9\<\>\(\)\{\}\[\]\|\?\!\@\#\$\%\^\&]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
|
||||
/*eslint-enable max-len*/
|
||||
|
||||
Currency.from_json = function(j, shouldInterpretXrpAsIou) {
|
||||
return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
|
||||
};
|
||||
|
||||
Currency.from_human = function(j, opts) {
|
||||
return (new Currency().parse_human(j, opts));
|
||||
};
|
||||
|
||||
// this._value = NaN on error.
|
||||
Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
|
||||
this._value = NaN;
|
||||
|
||||
if (j instanceof Currency) {
|
||||
this._value = j.copyTo({})._value;
|
||||
this._update();
|
||||
return this;
|
||||
}
|
||||
|
||||
switch (typeof j) {
|
||||
case 'number':
|
||||
if (!isNaN(j)) {
|
||||
this.parse_number(j);
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
if (!j || j === '0') {
|
||||
// Empty string or XRP
|
||||
this.parse_hex(shouldInterpretXrpAsIou
|
||||
? Currency.HEX_CURRENCY_BAD
|
||||
: Currency.HEX_ZERO);
|
||||
break;
|
||||
}
|
||||
|
||||
if (j === '1') {
|
||||
// 'no currency'
|
||||
this.parse_hex(Currency.HEX_ONE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (/^[A-F0-9]{40}$/.test(j)) {
|
||||
// Hex format
|
||||
this.parse_hex(j);
|
||||
break;
|
||||
}
|
||||
|
||||
// match the given string to see if it's in an allowed format
|
||||
var matches = j.match(this.human_RE);
|
||||
|
||||
if (matches) {
|
||||
var currencyCode = matches[1];
|
||||
|
||||
// for the currency 'XRP' case
|
||||
// we drop everything else that could have been provided
|
||||
// e.g. 'XRP - Ripple'
|
||||
if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) {
|
||||
this.parse_hex(shouldInterpretXrpAsIou
|
||||
? Currency.HEX_CURRENCY_BAD
|
||||
: Currency.HEX_ZERO);
|
||||
|
||||
// early break, we can't have interest on XRP
|
||||
break;
|
||||
}
|
||||
|
||||
// the full currency is matched as it is part of the valid currency
|
||||
// format, but not stored
|
||||
// var full_currency = matches[2] || '';
|
||||
var interest = matches[3] || '';
|
||||
|
||||
// interest is defined as interest per year, per annum (pa)
|
||||
var percentage = interest.match(/(-?\d+\.?\d+)/);
|
||||
|
||||
currencyCode = currencyCode.toUpperCase();
|
||||
|
||||
var currencyData = utils.arraySet(20, 0);
|
||||
|
||||
if (percentage) {
|
||||
/*
|
||||
* 20 byte layout of a interest bearing currency
|
||||
*
|
||||
* 01 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __
|
||||
* CURCODE- DATE------- RATE------------------- RESERVED---
|
||||
*/
|
||||
|
||||
// byte 1 for type, use '1' to denote demurrage currency
|
||||
currencyData[0] = 1;
|
||||
|
||||
// byte 2-4 for currency code
|
||||
currencyData[1] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[2] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[3] = currencyCode.charCodeAt(2) & 0xff;
|
||||
|
||||
// byte 5-8 are for reference date, but should always be 0 so we
|
||||
// won't fill it
|
||||
|
||||
// byte 9-16 are for the interest
|
||||
percentage = parseFloat(percentage[0]);
|
||||
|
||||
// the interest or demurrage is expressed as a yearly (per annum)
|
||||
// value
|
||||
var secondsPerYear = 31536000; // 60 * 60 * 24 * 365
|
||||
|
||||
// Calculating the interest e-fold
|
||||
// 0.5% demurrage is expressed 0.995, 0.005 less than 1
|
||||
// 0.5% interest is expressed as 1.005, 0.005 more than 1
|
||||
var interestEfold = secondsPerYear / Math.log(1 + percentage / 100);
|
||||
var bytes = Float.toIEEE754Double(interestEfold);
|
||||
|
||||
for (var i = 0; i <= bytes.length; i++) {
|
||||
currencyData[8 + i] = bytes[i] & 0xff;
|
||||
}
|
||||
|
||||
// the last 4 bytes are reserved for future use, so we won't fill
|
||||
// those
|
||||
|
||||
} else {
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
|
||||
}
|
||||
|
||||
this.parse_bytes(currencyData);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Currency.prototype.parse_human = function(j) {
|
||||
return this.parse_json(j);
|
||||
};
|
||||
|
||||
/**
|
||||
* Recalculate internal representation.
|
||||
*
|
||||
* You should never need to call this.
|
||||
*/
|
||||
|
||||
Currency.prototype._update = function() {
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
if (!bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._native = false;
|
||||
this._type = -1;
|
||||
this._interest_start = NaN;
|
||||
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 === '\u0000\u0000\u0000') {
|
||||
this._native = true;
|
||||
this._iso_code = 'XRP';
|
||||
}
|
||||
|
||||
this._type = 0;
|
||||
} else if (bytes[0] === 0x01) { // Demurrage currency
|
||||
this._iso_code = String.fromCharCode(bytes[1])
|
||||
+ String.fromCharCode(bytes[2])
|
||||
+ String.fromCharCode(bytes[3]);
|
||||
|
||||
this._type = 1;
|
||||
this._interest_start = (bytes[4] << 24) +
|
||||
(bytes[5] << 16) +
|
||||
(bytes[6] << 8) +
|
||||
(bytes[7]);
|
||||
this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16));
|
||||
}
|
||||
};
|
||||
|
||||
// XXX Probably not needed anymore?
|
||||
/*
|
||||
Currency.prototype.parse_bytes = function(byte_array) {
|
||||
if (Array.isArray(byte_array) && byte_array.length === 20) {
|
||||
var result;
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
|
||||
&& (i===12 || i===13 || i===14 || byte_array[0]===0)
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
var currencyCode = String.fromCharCode(byte_array[12])
|
||||
+ String.fromCharCode(byte_array[13])
|
||||
+ String.fromCharCode(byte_array[14]);
|
||||
if (/^[A-Z0-9]{3}$/.test(currencyCode) && currencyCode !== 'XRP' ) {
|
||||
this._value = currencyCode;
|
||||
} else if (currencyCode === '\0\0\0') {
|
||||
this._value = 0;
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
} else {
|
||||
// XXX Should support non-standard currency codes
|
||||
this._value = NaN;
|
||||
}
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
*/
|
||||
|
||||
Currency.prototype.is_native = function() {
|
||||
return this._native;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Boolean} whether this currency is an interest-bearing currency
|
||||
*/
|
||||
|
||||
Currency.prototype.has_interest = function() {
|
||||
return this._type === 1
|
||||
&& !isNaN(this._interest_start)
|
||||
&& !isNaN(this._interest_period);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} referenceDate number of seconds since the Ripple Epoch
|
||||
* (0:00 on January 1, 2000 UTC) used to calculate the
|
||||
* interest over provided interval pass in one years
|
||||
* worth of seconds to ge the yearly interest
|
||||
* @returns {number} interest for provided interval, can be negative for
|
||||
* demurred currencies
|
||||
*/
|
||||
Currency.prototype.get_interest_at = function(referenceDate) {
|
||||
if (!this.has_interest()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// use one year as a default period
|
||||
if (!referenceDate) {
|
||||
referenceDate = this._interest_start + 3600 * 24 * 365;
|
||||
}
|
||||
|
||||
if (referenceDate instanceof Date) {
|
||||
referenceDate = utils.fromTimestamp(referenceDate.getTime());
|
||||
}
|
||||
|
||||
// calculate interest by e-fold number
|
||||
return Math.exp((referenceDate - this._interest_start)
|
||||
/ this._interest_period);
|
||||
};
|
||||
|
||||
Currency.prototype.get_interest_percentage_at
|
||||
= function(referenceDate, decimals) {
|
||||
var interest = this.get_interest_at(referenceDate, decimals);
|
||||
|
||||
// convert to percentage
|
||||
interest = (interest * 100) - 100;
|
||||
var decimalMultiplier = decimals ? Math.pow(10, decimals) : 100;
|
||||
|
||||
// round to two decimals behind the dot
|
||||
return Math.round(interest * decimalMultiplier) / decimalMultiplier;
|
||||
};
|
||||
|
||||
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
|
||||
//
|
||||
// We could be doing further checks into the internal format of the
|
||||
// currency data, since there are some values that are invalid.
|
||||
//
|
||||
// Currency.prototype.is_valid = function() {
|
||||
// return UInt.prototype.is_valid() && ...;
|
||||
// };
|
||||
|
||||
Currency.prototype.to_json = function(opts) {
|
||||
if (!this.is_valid()) {
|
||||
// XXX This is backwards compatible behavior, but probably not very good.
|
||||
return 'XRP';
|
||||
}
|
||||
|
||||
if (!opts) {
|
||||
opts = {};
|
||||
}
|
||||
|
||||
var currency;
|
||||
var fullName = opts && opts.full_name ? ' - ' + opts.full_name : '';
|
||||
opts.show_interest = opts.show_interest !== undefined
|
||||
? opts.show_interest
|
||||
: this.has_interest();
|
||||
|
||||
if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) {
|
||||
currency = this._iso_code + fullName;
|
||||
if (opts.show_interest) {
|
||||
var decimals = !isNaN(opts.decimals) ? opts.decimals : undefined;
|
||||
var interestPercentage = this.has_interest()
|
||||
? this.get_interest_percentage_at(
|
||||
this._interest_start + 3600 * 24 * 365, decimals
|
||||
)
|
||||
: 0;
|
||||
currency += ' (' + interestPercentage + '%pa)';
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback to returning the raw currency hex
|
||||
currency = this.to_hex();
|
||||
|
||||
// XXX This is to maintain backwards compatibility, but it is very, very
|
||||
// odd behavior, so we should deprecate it and get rid of it as soon as
|
||||
// possible.
|
||||
if (currency === Currency.HEX_ONE) {
|
||||
currency = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return currency;
|
||||
};
|
||||
|
||||
Currency.prototype.to_human = function(opts) {
|
||||
// to_human() will always print the human-readable currency code if available.
|
||||
return this.to_json(opts);
|
||||
};
|
||||
|
||||
Currency.prototype.get_iso = function() {
|
||||
return this._iso_code;
|
||||
};
|
||||
|
||||
exports.Currency = Currency;
|
||||
25
src/hashprefixes.js
Normal file
25
src/hashprefixes.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Prefix for hashing functions.
|
||||
*
|
||||
* These prefixes are inserted before the source material used to
|
||||
* generate various hashes. This is done to put each hash in its own
|
||||
* "space." This way, two different types of objects with the
|
||||
* same binary data will produce different hashes.
|
||||
*
|
||||
* Each prefix is a 4-byte value with the last byte set to zero
|
||||
* and the first three bytes formed from the ASCII equivalent of
|
||||
* some arbitrary string. For example "TXN".
|
||||
*/
|
||||
|
||||
// transaction plus signature to give transaction ID
|
||||
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
// transaction plus metadata
|
||||
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
|
||||
// inner node in tree
|
||||
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
|
||||
// leaf node in tree
|
||||
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
|
||||
// inner transaction to sign
|
||||
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
|
||||
// inner transaction to sign (TESTNET)
|
||||
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
|
||||
107
src/ieee754.js
Normal file
107
src/ieee754.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// Convert a JavaScript number to IEEE-754 Double Precision
|
||||
// value represented as an array of 8 bytes (octets)
|
||||
//
|
||||
// Based on:
|
||||
// http://cautionsingularityahead.blogspot.com/2010/04/javascript-and-ieee754-redux.html
|
||||
//
|
||||
// Found and modified from:
|
||||
// https://gist.github.com/bartaz/1119041
|
||||
|
||||
var Float = exports.Float = {};
|
||||
|
||||
Float.toIEEE754 = function(v, ebits, fbits) {
|
||||
|
||||
var bias = (1 << (ebits - 1)) - 1;
|
||||
|
||||
// Compute sign, exponent, fraction
|
||||
var s, e, f;
|
||||
if (isNaN(v)) {
|
||||
e = (1 << bias) - 1; f = 1; s = 0;
|
||||
}
|
||||
else if (v === Infinity || v === -Infinity) {
|
||||
e = (1 << bias) - 1; f = 0; s = (v < 0) ? 1 : 0;
|
||||
}
|
||||
else if (v === 0) {
|
||||
e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
|
||||
}
|
||||
else {
|
||||
s = v < 0;
|
||||
v = Math.abs(v);
|
||||
|
||||
if (v >= Math.pow(2, 1 - bias)) {
|
||||
var ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
|
||||
e = ln + bias;
|
||||
f = v * Math.pow(2, fbits - ln) - Math.pow(2, fbits);
|
||||
}
|
||||
else {
|
||||
e = 0;
|
||||
f = v / Math.pow(2, 1 - bias - fbits);
|
||||
}
|
||||
}
|
||||
|
||||
// Pack sign, exponent, fraction
|
||||
var i, bits = [];
|
||||
for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = Math.floor(f / 2); }
|
||||
for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = Math.floor(e / 2); }
|
||||
bits.push(s ? 1 : 0);
|
||||
bits.reverse();
|
||||
var str = bits.join('');
|
||||
|
||||
// Bits to bytes
|
||||
var bytes = [];
|
||||
while (str.length) {
|
||||
bytes.push(parseInt(str.substring(0, 8), 2));
|
||||
str = str.substring(8);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Float.fromIEEE754 = function(bytes, ebits, fbits) {
|
||||
|
||||
// Bytes to bits
|
||||
var bits = [];
|
||||
for (var i = bytes.length; i; i -= 1) {
|
||||
var byte = bytes[i - 1];
|
||||
for (var j = 8; j; j -= 1) {
|
||||
bits.push(byte % 2 ? 1 : 0); byte = byte >> 1;
|
||||
}
|
||||
}
|
||||
bits.reverse();
|
||||
var str = bits.join('');
|
||||
|
||||
// Unpack sign, exponent, fraction
|
||||
var bias = (1 << (ebits - 1)) - 1;
|
||||
var s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
|
||||
var e = parseInt(str.substring(1, 1 + ebits), 2);
|
||||
var f = parseInt(str.substring(1 + ebits), 2);
|
||||
|
||||
// Produce number
|
||||
if (e === (1 << ebits) - 1) {
|
||||
return f !== 0 ? NaN : s * Infinity;
|
||||
}
|
||||
else if (e > 0) {
|
||||
return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits));
|
||||
}
|
||||
else if (f !== 0) {
|
||||
return s * Math.pow(2, -(bias-1)) * (f / Math.pow(2, fbits));
|
||||
}
|
||||
else {
|
||||
return s * 0;
|
||||
}
|
||||
}
|
||||
|
||||
Float.fromIEEE754Double = function(b) { return Float.fromIEEE754(b, 11, 52); }
|
||||
Float.toIEEE754Double = function(v) { return Float.toIEEE754(v, 11, 52); }
|
||||
Float.fromIEEE754Single = function(b) { return Float.fromIEEE754(b, 8, 23); }
|
||||
Float.toIEEE754Single = function(v) { return Float.toIEEE754(v, 8, 23); }
|
||||
|
||||
|
||||
// Convert array of octets to string binary representation
|
||||
// by bartaz
|
||||
|
||||
Float.toIEEE754DoubleString = function(v) {
|
||||
return exports.toIEEE754Double(v)
|
||||
.map(function(n){ for(n = n.toString(2);n.length < 8;n="0"+n); return n })
|
||||
.join('')
|
||||
.replace(/(.)(.{11})(.{52})/, "$1 $2 $3")
|
||||
}
|
||||
68
src/index.js
Normal file
68
src/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
exports.Remote = require('./remote').Remote;
|
||||
exports.Request = require('./request').Request;
|
||||
exports.Amount = require('./amount').Amount;
|
||||
exports.Account = require('./account').Account;
|
||||
exports.Transaction = require('./transaction').Transaction;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.Base = require('./base').Base;
|
||||
exports.UInt128 = require('./uint128').UInt128;
|
||||
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.Message = require('./message').Message;
|
||||
exports.binformat = require('./binformat');
|
||||
exports.utils = require('./utils');
|
||||
exports.Server = require('./server').Server;
|
||||
exports.Ledger = require('./ledger').Ledger;
|
||||
exports.TransactionQueue = require('./transactionqueue').TransactionQueue;
|
||||
exports.RangeSet = require('./rangeset').RangeSet;
|
||||
exports.convertBase = require('./baseconverter');
|
||||
|
||||
exports._test = {
|
||||
Log: require('./log'),
|
||||
PathFind: require('./pathfind').PathFind,
|
||||
TransactionManager: require('./transactionmanager').TransactionManager
|
||||
};
|
||||
|
||||
// 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
|
||||
// any time without warning.
|
||||
//
|
||||
// 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('./utils').sjcl;
|
||||
exports.Wallet = require('ripple-wallet-generator')({sjcl: exports.sjcl});
|
||||
exports.types = require('./serializedtypes');
|
||||
|
||||
// camelCase to under_scored API conversion
|
||||
function attachUnderscored(name) {
|
||||
const o = exports[name];
|
||||
|
||||
Object.keys(o.prototype).forEach(function(key) {
|
||||
const UPPERCASE = /([A-Z]{1})[a-z]+/g;
|
||||
|
||||
if (!UPPERCASE.test(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const 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
src/js
Submodule
1
src/js
Submodule
Submodule src/js added at 16dde36fa2
@@ -1,3 +0,0 @@
|
||||
# Ripple JavaScript library
|
||||
|
||||
This library lets you connect to a ripple server via websockets.
|
||||
@@ -1,148 +0,0 @@
|
||||
// Routines for working with an account.
|
||||
//
|
||||
// You should not instantiate this class yourself, instead use Remote#account.
|
||||
//
|
||||
// Events:
|
||||
// wallet_clean : True, iff the wallet has been updated.
|
||||
// wallet_dirty : True, iff the wallet needs to be updated.
|
||||
// balance : The current stamp balance.
|
||||
// balance_proposed
|
||||
//
|
||||
|
||||
// var network = require("./network.js");
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
|
||||
var extend = require('extend');
|
||||
|
||||
var Account = function (remote, account) {
|
||||
EventEmitter.call(this);
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._account = UInt160.from_json(account);
|
||||
this._account_id = this._account.to_json();
|
||||
this._subs = 0;
|
||||
|
||||
// Ledger entry object
|
||||
// Important: This must never be overwritten, only extend()-ed
|
||||
this._entry = {};
|
||||
|
||||
this.on('newListener', function (type, listener) {
|
||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
self._remote.request_subscribe()
|
||||
.accounts(self._account_id)
|
||||
.request();
|
||||
}
|
||||
self._subs += 1;
|
||||
}
|
||||
});
|
||||
|
||||
this.on('removeListener', function (type, listener) {
|
||||
if (Account.subscribe_events.indexOf(type) !== -1) {
|
||||
self._subs -= 1;
|
||||
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
self._remote.request_unsubscribe()
|
||||
.accounts(self._account_id)
|
||||
.request();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._remote.on('prepare_subscribe', function (request) {
|
||||
if (self._subs) request.accounts(self._account_id);
|
||||
});
|
||||
|
||||
this.on('transaction', function (msg) {
|
||||
var changed = false;
|
||||
msg.mmeta.each(function (an) {
|
||||
if (an.entryType === 'AccountRoot' &&
|
||||
an.fields.Account === self._account_id) {
|
||||
extend(self._entry, an.fieldsNew, an.fieldsFinal);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
self.emit('entry', self._entry);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
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 ()
|
||||
{
|
||||
return this._account.to_json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the AccountId is valid.
|
||||
*
|
||||
* Note: This does not tell you whether the account exists in the ledger.
|
||||
*/
|
||||
Account.prototype.is_valid = function ()
|
||||
{
|
||||
return this._account.is_valid();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
Account.prototype.entry = function (callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
self._remote.request_account_info(this._account_id)
|
||||
.on('success', function (e) {
|
||||
extend(self._entry, e.account_data);
|
||||
self.emit('entry', self._entry);
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
callback(null, e);
|
||||
}
|
||||
})
|
||||
.on('error', function (e) {
|
||||
callback(e);
|
||||
})
|
||||
.request();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify object of a relevant transaction.
|
||||
*
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*/
|
||||
Account.prototype.notifyTx = function (message)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
exports.Account = Account;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
1022
src/js/amount.js
1022
src/js/amount.js
File diff suppressed because it is too large
Load Diff
140
src/js/base.js
140
src/js/base.js
@@ -1,140 +0,0 @@
|
||||
|
||||
var sjcl = require('../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
|
||||
var Base = {};
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
var sha256 = function (bytes) {
|
||||
return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
|
||||
};
|
||||
|
||||
var sha256hash = function (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 = [];
|
||||
|
||||
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) {
|
||||
buffer.push(alphabet[0]);
|
||||
}
|
||||
|
||||
return buffer.reverse().join("");
|
||||
};
|
||||
|
||||
// --> input: String
|
||||
// <-- array of bytes or undefined.
|
||||
Base.decode = function (input, alpha) {
|
||||
var alphabet = alphabets[alpha || 'ripple'];
|
||||
var bi_base = new BigInteger(String(alphabet.length));
|
||||
var bi_value = nbi();
|
||||
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]);
|
||||
|
||||
if (v < 0)
|
||||
return undefined;
|
||||
|
||||
var r = nbi();
|
||||
|
||||
r.fromInt(v);
|
||||
|
||||
bi_value = bi_value.multiply(bi_base).add(r);
|
||||
}
|
||||
|
||||
// toByteArray:
|
||||
// - Returns leading zeros!
|
||||
// - Returns signed bytes!
|
||||
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])
|
||||
extra += 1;
|
||||
|
||||
if (extra)
|
||||
bytes = bytes.slice(extra);
|
||||
|
||||
var zeros = 0;
|
||||
|
||||
while (zeros !== input.length && input[zeros] === alphabet[0])
|
||||
zeros += 1;
|
||||
|
||||
return [].concat(utils.arraySet(zeros, 0), bytes);
|
||||
};
|
||||
|
||||
// --> input: Array
|
||||
// <-- String
|
||||
Base.encode_check = function (version, input, alphabet) {
|
||||
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[0] !== version || buffer.length < 5)
|
||||
return NaN;
|
||||
|
||||
var computed = sha256hash(buffer.slice(0, -4)).slice(0, 4);
|
||||
var checksum = buffer.slice(-4);
|
||||
var i;
|
||||
|
||||
for (i = 0; i != 4; i += 1)
|
||||
if (computed[i] !== checksum[i])
|
||||
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 +0,0 @@
|
||||
var ST = require("./serializedtypes");
|
||||
|
||||
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 ]
|
||||
];
|
||||
|
||||
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 ]
|
||||
]),
|
||||
TrustSet: [20].concat(base, [
|
||||
[ 'LimitAmount' , OPTIONAL, 3, ST.Amount ],
|
||||
[ 'QualityIn' , OPTIONAL, 20, ST.Int32 ],
|
||||
[ 'QualityOut' , OPTIONAL, 21, ST.Int32 ]
|
||||
]),
|
||||
OfferCreate: [7].concat(base, [
|
||||
[ 'TakerPays' , REQUIRED, 4, ST.Amount ],
|
||||
[ 'TakerGets' , REQUIRED, 5, ST.Amount ],
|
||||
[ 'Expiration' , OPTIONAL, 10, ST.Int32 ]
|
||||
]),
|
||||
OfferCancel: [8].concat(base, [
|
||||
[ 'OfferSequence' , REQUIRED, 25, ST.Int32 ]
|
||||
]),
|
||||
SetRegularKey: [5].concat(base, [
|
||||
[ 'RegularKey' , REQUIRED, 8, ST.Account ]
|
||||
]),
|
||||
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 ]
|
||||
]),
|
||||
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 ]
|
||||
]),
|
||||
RemoveContract: [10].concat(base, [
|
||||
[ 'Target' , REQUIRED, 7, ST.Account ]
|
||||
]),
|
||||
EnableFeature: [100].concat(base, [
|
||||
[ 'Feature' , REQUIRED, 19, ST.Hash256 ]
|
||||
]),
|
||||
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 ]
|
||||
])
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
// This object serves as a singleton to store config options
|
||||
|
||||
var extend = require("extend");
|
||||
|
||||
var config = module.exports = {
|
||||
load: function (newOpts) {
|
||||
extend(config, newOpts);
|
||||
return config;
|
||||
}
|
||||
};
|
||||
Submodule src/js/cryptojs deleted from c3c843c513
@@ -1,87 +0,0 @@
|
||||
|
||||
//
|
||||
// Currency support
|
||||
//
|
||||
|
||||
// XXX Internal form should be UInt160.
|
||||
var Currency = function () {
|
||||
// Internal form: 0 = XRP. 3 letter-code.
|
||||
// XXX Internal should be 0 or hex with three letter annotation when valid.
|
||||
|
||||
// Json form:
|
||||
// '', 'XRP', '0': 0
|
||||
// 3-letter code: ...
|
||||
// XXX Should support hex, C++ doesn't currently allow it.
|
||||
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
// Given "USD" return the json.
|
||||
Currency.json_rewrite = function (j) {
|
||||
return Currency.from_json(j).to_json();
|
||||
};
|
||||
|
||||
Currency.from_json = function (j) {
|
||||
if (j instanceof Currency) return j.clone();
|
||||
else if ('string' === typeof j || 'number' === typeof j) return (new Currency()).parse_json(j);
|
||||
else return new Currency(); // NaN
|
||||
};
|
||||
|
||||
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 || "0" === j || "XRP" === j) {
|
||||
this._value = 0;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Currency.prototype.is_native = function () {
|
||||
return !isNaN(this._value) && !this._value;
|
||||
};
|
||||
|
||||
Currency.prototype.is_valid = function () {
|
||||
return 'string' === typeof this._value || !isNaN(this._value);
|
||||
};
|
||||
|
||||
Currency.prototype.to_json = function () {
|
||||
return this._value ? this._value : "XRP";
|
||||
};
|
||||
|
||||
Currency.prototype.to_human = function () {
|
||||
return this._value ? this._value : "XRP";
|
||||
};
|
||||
|
||||
exports.Currency = Currency;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,22 +0,0 @@
|
||||
exports.Remote = require('./remote').Remote;
|
||||
exports.Amount = require('./amount').Amount;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.UInt160 = require('./amount').UInt160;
|
||||
exports.Seed = require('./amount').Seed;
|
||||
exports.Transaction = require('./transaction').Transaction;
|
||||
exports.Meta = require('./meta').Meta;
|
||||
|
||||
exports.utils = require('./utils');
|
||||
|
||||
// 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
|
||||
// any time without warning.
|
||||
//
|
||||
// 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.config = require('./config');
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
1219
src/js/jsbn.js
1219
src/js/jsbn.js
File diff suppressed because it is too large
Load Diff
@@ -1,67 +0,0 @@
|
||||
var sjcl = require('../../build/sjcl');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
|
||||
var KeyPair = function ()
|
||||
{
|
||||
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.prototype.parse_bn_secret = function (j)
|
||||
{
|
||||
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], j);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns public key as sjcl public key.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
KeyPair.prototype._pub = function ()
|
||||
{
|
||||
var curve = this._curve;
|
||||
|
||||
if (!this._pubkey && this._secret) {
|
||||
var exponent = this._secret._exponent;
|
||||
this._pubkey = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(exponent));
|
||||
}
|
||||
|
||||
return this._pubkey;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
KeyPair.prototype.sign = function (hash)
|
||||
{
|
||||
hash = UInt256.from_json(hash);
|
||||
return this._secret.signDER(hash.to_bits(), 0);
|
||||
};
|
||||
|
||||
exports.KeyPair = KeyPair;
|
||||
137
src/js/meta.js
137
src/js/meta.js
@@ -1,137 +0,0 @@
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
/**
|
||||
* 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 = {};
|
||||
|
||||
["CreatedNode", "ModifiedNode", "DeletedNode"].forEach(function (x) {
|
||||
if (an[x]) result.diffType = x;
|
||||
});
|
||||
|
||||
if (!result.diffType) return null;
|
||||
|
||||
an = an[result.diffType];
|
||||
|
||||
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 || {};
|
||||
|
||||
this.nodes.push(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a function on each affected node.
|
||||
*
|
||||
* The callback is passed two parameters. The first is a node object which looks
|
||||
* like this:
|
||||
*
|
||||
* {
|
||||
* // Type of diff, e.g. CreatedNode, ModifiedNode
|
||||
* diffType: 'CreatedNode'
|
||||
*
|
||||
* // Type of node affected, e.g. RippleState, AccountRoot
|
||||
* entryType: 'RippleState',
|
||||
*
|
||||
* // Index of the ledger this change occurred in
|
||||
* ledgerIndex: '01AB01AB...',
|
||||
*
|
||||
* // Contains all fields with later versions taking precedence
|
||||
* //
|
||||
* // This is a shorthand for doing things like checking which account
|
||||
* // this affected without having to check the diffType.
|
||||
* fields: {...},
|
||||
*
|
||||
* // Old fields (before the change)
|
||||
* fieldsPrev: {...},
|
||||
*
|
||||
* // New fields (that have been added)
|
||||
* fieldsNew: {...},
|
||||
*
|
||||
* // Changed fields
|
||||
* fieldsFinal: {...}
|
||||
* }
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
for (var i = 0, l = this.nodes.length; i < l; i++) {
|
||||
fn(this.nodes[i], i);
|
||||
}
|
||||
};
|
||||
|
||||
var amountFieldsAffectingIssuer = [
|
||||
"LowLimit", "HighLimit", "TakerPays", "TakerGets"
|
||||
];
|
||||
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;
|
||||
|
||||
for (var i in fields) {
|
||||
var field = fields[i];
|
||||
|
||||
if ("string" === typeof field && UInt160.is_valid(field)) {
|
||||
accounts.push(field);
|
||||
} else if (amountFieldsAffectingIssuer.indexOf(i) !== -1) {
|
||||
var amount = Amount.from_json(field);
|
||||
var issuer = amount.issuer();
|
||||
if (issuer.is_valid() && !issuer.is_zero()) {
|
||||
accounts.push(issuer.to_json());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
accounts = utils.arrayUnique(accounts);
|
||||
|
||||
return accounts;
|
||||
};
|
||||
|
||||
Meta.prototype.getAffectedBooks = function ()
|
||||
{
|
||||
var books = [];
|
||||
|
||||
this.each(function (an) {
|
||||
if (an.entryType !== 'Offer') return;
|
||||
|
||||
var gets = Amount.from_json(an.fields.TakerGets);
|
||||
var pays = Amount.from_json(an.fields.TakerPays);
|
||||
|
||||
var getsKey = gets.currency().to_json();
|
||||
if (getsKey !== 'XRP') getsKey += '/' + gets.issuer().to_json();
|
||||
|
||||
var paysKey = pays.currency().to_json();
|
||||
if (paysKey !== 'XRP') paysKey += '/' + pays.issuer().to_json();
|
||||
|
||||
var key = getsKey + ":" + paysKey;
|
||||
|
||||
books.push(key);
|
||||
});
|
||||
|
||||
books = utils.arrayUnique(books);
|
||||
|
||||
return 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
|
||||
@@ -1,250 +0,0 @@
|
||||
// Routines for working with an orderbook.
|
||||
//
|
||||
// One OrderBook object represents one half of an order book. (i.e. bids OR
|
||||
// asks) Which one depends on the ordering of the parameters.
|
||||
//
|
||||
// Events:
|
||||
// - transaction A transaction that affects the order book.
|
||||
|
||||
// var network = require("./network.js");
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
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) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._currency_gets = currency_gets;
|
||||
this._issuer_gets = issuer_gets;
|
||||
this._currency_pays = currency_pays;
|
||||
this._issuer_pays = issuer_pays;
|
||||
|
||||
this._subs = 0;
|
||||
|
||||
// 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.on('newListener', function (type, listener) {
|
||||
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
self._subscribe();
|
||||
}
|
||||
self._subs += 1;
|
||||
}
|
||||
});
|
||||
|
||||
this.on('removeListener', function (type, listener) {
|
||||
if (OrderBook.subscribe_events.indexOf(type) !== -1) {
|
||||
self._subs -= 1;
|
||||
|
||||
if (!self._subs && 'open' === self._remote._online_state) {
|
||||
self._sync = false;
|
||||
self._remote.request_unsubscribe()
|
||||
.books([self.to_json()])
|
||||
.request();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._remote.on('connect', function () {
|
||||
if (self._subs) {
|
||||
self._subscribe();
|
||||
}
|
||||
});
|
||||
|
||||
this._remote.on('disconnect', function () {
|
||||
self._sync = false;
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
util.inherits(OrderBook, EventEmitter);
|
||||
|
||||
/**
|
||||
* List of events that require a remote subscription to the orderbook.
|
||||
*/
|
||||
OrderBook.subscribe_events = ['transaction', 'model', 'trade'];
|
||||
|
||||
/**
|
||||
* Subscribes to orderbook.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
OrderBook.prototype._subscribe = function ()
|
||||
{
|
||||
var self = this;
|
||||
self._remote.request_subscribe()
|
||||
.books([self.to_json()], true)
|
||||
.on('error', function () {
|
||||
// XXX What now?
|
||||
})
|
||||
.on('success', function (res) {
|
||||
self._sync = true;
|
||||
self._offers = res.offers;
|
||||
self.emit('model', self._offers);
|
||||
})
|
||||
.request();
|
||||
};
|
||||
|
||||
OrderBook.prototype.to_json = function ()
|
||||
{
|
||||
var json = {
|
||||
"taker_gets": {
|
||||
"currency": this._currency_gets
|
||||
},
|
||||
"taker_pays": {
|
||||
"currency": this._currency_pays
|
||||
}
|
||||
};
|
||||
|
||||
if (this._currency_gets !== "XRP") json["taker_gets"]["issuer"] = this._issuer_gets;
|
||||
if (this._currency_pays !== "XRP") json["taker_pays"]["issuer"] = this._issuer_pays;
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the OrderBook is valid.
|
||||
*
|
||||
* Note: This only checks whether the parameters (currencies and issuer) are
|
||||
* syntactically valid. It does not check anything against the ledger.
|
||||
*/
|
||||
OrderBook.prototype.is_valid = function ()
|
||||
{
|
||||
// XXX Should check for same currency (non-native) && same issuer
|
||||
return (
|
||||
Currency.is_valid(this._currency_pays) &&
|
||||
(this._currency_pays === "XRP" || UInt160.is_valid(this._issuer_pays)) &&
|
||||
Currency.is_valid(this._currency_gets) &&
|
||||
(this._currency_gets === "XRP" || UInt160.is_valid(this._issuer_gets)) &&
|
||||
!(this._currency_pays === "XRP" && this._currency_gets === "XRP")
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify object of a relevant transaction.
|
||||
*
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*/
|
||||
OrderBook.prototype.notifyTx = function (message)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
var changed = false;
|
||||
|
||||
var trade_gets = Amount.from_json("0" + ((this._currency_gets === 'XRP') ? "" :
|
||||
"/" + this._currency_gets +
|
||||
"/" + this._issuer_gets));
|
||||
var trade_pays = Amount.from_json("0" + ((this._currency_pays === 'XRP') ? "" :
|
||||
"/" + this._currency_pays +
|
||||
"/" + this._issuer_pays));
|
||||
|
||||
message.mmeta.each(function (an) {
|
||||
if (an.entryType !== 'Offer') return;
|
||||
|
||||
var i, l, offer;
|
||||
if (an.diffType === 'DeletedNode' ||
|
||||
an.diffType === 'ModifiedNode') {
|
||||
for (i = 0, l = self._offers.length; i < l; i++) {
|
||||
offer = self._offers[i];
|
||||
if (offer.index === an.ledgerIndex) {
|
||||
if (an.diffType === 'DeletedNode') {
|
||||
self._offers.splice(i, 1);
|
||||
}
|
||||
else extend(offer, an.fieldsFinal);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We don't want to count a OfferCancel as a trade
|
||||
if (message.transaction.TransactionType === "OfferCancel") return;
|
||||
|
||||
trade_gets = trade_gets.add(an.fieldsPrev.TakerGets);
|
||||
trade_pays = trade_pays.add(an.fieldsPrev.TakerPays);
|
||||
if (an.diffType === 'ModifiedNode') {
|
||||
trade_gets = trade_gets.subtract(an.fieldsFinal.TakerGets);
|
||||
trade_pays = trade_pays.subtract(an.fieldsFinal.TakerPays);
|
||||
}
|
||||
} else if (an.diffType === 'CreatedNode') {
|
||||
var price = Amount.from_json(an.fields.TakerPays).ratio_human(an.fields.TakerGets);
|
||||
for (i = 0, l = self._offers.length; i < l; i++) {
|
||||
offer = self._offers[i];
|
||||
var priceItem = Amount.from_json(offer.TakerPays).ratio_human(offer.TakerGets);
|
||||
|
||||
if (price.compareTo(priceItem) <= 0) {
|
||||
var obj = an.fields;
|
||||
obj.index = an.ledgerIndex;
|
||||
self._offers.splice(i, 0, an.fields);
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get offers model asynchronously.
|
||||
*
|
||||
* This function takes a callback and calls it with an array containing the
|
||||
* current set of offers in this order book.
|
||||
*
|
||||
* If the data is available immediately, the callback may be called synchronously.
|
||||
*/
|
||||
OrderBook.prototype.offers = function (callback)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
if ("function" === typeof callback) {
|
||||
if (this._sync) {
|
||||
callback(this._offers);
|
||||
} else {
|
||||
this.once('model', function (offers) {
|
||||
callback(offers);
|
||||
});
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return latest known offers.
|
||||
*
|
||||
* Usually, this will just be an empty array if the order book hasn't been
|
||||
* loaded yet. But this accessor may be convenient in some circumstances.
|
||||
*/
|
||||
OrderBook.prototype.offersSync = function ()
|
||||
{
|
||||
return this._offers;
|
||||
};
|
||||
|
||||
exports.OrderBook = OrderBook;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
1258
src/js/remote.js
1258
src/js/remote.js
File diff suppressed because it is too large
Load Diff
116
src/js/seed.js
116
src/js/seed.js
@@ -1,116 +0,0 @@
|
||||
//
|
||||
// Seed support
|
||||
//
|
||||
|
||||
var sjcl = require('../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var jsbn = require('./jsbn');
|
||||
var extend = require('extend');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
|
||||
var Base = require('./base').Base,
|
||||
UInt = require('./uint').UInt,
|
||||
UInt256 = require('./uint256').UInt256,
|
||||
KeyPair = require('./keypair').KeyPair;
|
||||
|
||||
var Seed = extend(function () {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._curve = sjcl.ecc.curves['c256'];
|
||||
this._value = NaN;
|
||||
}, UInt);
|
||||
|
||||
Seed.width = 16;
|
||||
Seed.prototype = extend({}, UInt.prototype);
|
||||
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 (!j.length) {
|
||||
this._value = NaN;
|
||||
// XXX Should actually always try and continue if it failed.
|
||||
} else if (j[0] === "s") {
|
||||
this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
|
||||
} else if (j.length === 32) {
|
||||
this._value = this.parse_hex(j);
|
||||
// XXX Should also try 1751
|
||||
} else {
|
||||
this.parse_passphrase(j);
|
||||
}
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Seed.prototype.parse_passphrase = function (j) {
|
||||
if ("string" !== typeof j) {
|
||||
throw new Error("Passphrase must be a string");
|
||||
}
|
||||
|
||||
var hash = sjcl.hash.sha512.hash(sjcl.codec.utf8String.toBits(j));
|
||||
var bits = sjcl.bitArray.bitSlice(hash, 0, 128);
|
||||
|
||||
this.parse_bits(bits);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Seed.prototype.to_json = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
return NaN;
|
||||
|
||||
var output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
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()) {
|
||||
throw new Error("Cannot generate keys from invalid seed!");
|
||||
}
|
||||
// XXX Should loop over keys until we find the right one
|
||||
|
||||
var curve = this._curve;
|
||||
|
||||
var seq = 0;
|
||||
|
||||
var private_gen, public_gen, i = 0;
|
||||
do {
|
||||
private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.to_bytes(), i)));
|
||||
i++;
|
||||
} while (!curve.r.greaterEquals(private_gen));
|
||||
|
||||
public_gen = curve.G.mult(private_gen);
|
||||
|
||||
var sec;
|
||||
i = 0;
|
||||
do {
|
||||
sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i)));
|
||||
i++;
|
||||
} while (!curve.r.greaterEquals(sec));
|
||||
|
||||
sec = sec.add(private_gen).mod(curve.r);
|
||||
|
||||
return KeyPair.from_bn_secret(sec);
|
||||
};
|
||||
|
||||
exports.Seed = Seed;
|
||||
@@ -1,144 +0,0 @@
|
||||
var binformat = require('./binformat'),
|
||||
sjcl = require('../../build/sjcl'),
|
||||
extend = require('extend'),
|
||||
stypes = require('./serializedtypes');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
|
||||
var SerializedObject = function () {
|
||||
this.buffer = [];
|
||||
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);
|
||||
|
||||
if ("number" === typeof obj.TransactionType) {
|
||||
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
|
||||
if (!obj.TransactionType) {
|
||||
throw new Error("Transaction type ID is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
if ("string" === typeof obj.TransactionType) {
|
||||
typedef = binformat.tx[obj.TransactionType].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.");
|
||||
|
||||
so.serialize(typedef, obj);
|
||||
|
||||
return so;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.append = function (bytes) {
|
||||
this.buffer = this.buffer.concat(bytes);
|
||||
this.pointer += bytes.length;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.to_bits = function ()
|
||||
{
|
||||
return sjcl.codec.bytes.toBits(this.buffer);
|
||||
};
|
||||
|
||||
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());
|
||||
|
||||
// Serialize fields
|
||||
for (var i = 0, l = typedef.length; i < l; i++) {
|
||||
var spec = typedef[i];
|
||||
this.serialize_field(spec, obj);
|
||||
}
|
||||
};
|
||||
|
||||
SerializedObject.prototype.signing_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);
|
||||
|
||||
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));
|
||||
|
||||
try {
|
||||
Type.serialize(this, obj[name]);
|
||||
} catch (e) {
|
||||
// Add field name to message and rethrow
|
||||
e.message = "Error serializing '"+name+"': "+e.message;
|
||||
throw e;
|
||||
}
|
||||
} else if (presence === binformat.REQUIRED) {
|
||||
throw new Error('Missing required field '+name);
|
||||
}
|
||||
};
|
||||
|
||||
SerializedObject.get_field_header = function (type_id, field_id)
|
||||
{
|
||||
var buffer = [0];
|
||||
if (type_id > 0xf) buffer.push(type_id & 0xff);
|
||||
else buffer[0] += (type_id & 0xf) << 4;
|
||||
|
||||
if (field_id > 0xf) buffer.push(field_id & 0xff);
|
||||
else buffer[0] += field_id & 0xf;
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
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) {
|
||||
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;
|
||||
};
|
||||
|
||||
exports.SerializedObject = SerializedObject;
|
||||
@@ -1,319 +0,0 @@
|
||||
/**
|
||||
* Type definitions for binary format.
|
||||
*
|
||||
* This file should not be included directly. Instead, find the format you're
|
||||
* trying to parse or serialize in binformat.js and pass that to
|
||||
* SerializedObject.parse() or SerializedObject.serialize().
|
||||
*/
|
||||
|
||||
var extend = require('extend'),
|
||||
utils = require('./utils'),
|
||||
sjcl = require('../../build/sjcl');
|
||||
|
||||
var amount = require('./amount'),
|
||||
UInt160 = amount.UInt160,
|
||||
Amount = amount.Amount,
|
||||
Currency= amount.Currency;
|
||||
|
||||
// Shortcuts
|
||||
var hex = sjcl.codec.hex,
|
||||
bytes = sjcl.codec.bytes;
|
||||
|
||||
var SerializedType = function (methods) {
|
||||
extend(this, methods);
|
||||
};
|
||||
|
||||
SerializedType.prototype.serialize_hex = function (so, hexData) {
|
||||
var byteData = bytes.fromBits(hex.toBits(hexData));
|
||||
this.serialize_varint(so, byteData.length);
|
||||
so.append(byteData);
|
||||
};
|
||||
|
||||
SerializedType.prototype.serialize_varint = function (so, val) {
|
||||
if (val < 0) {
|
||||
throw new Error("Variable integers are unsigned.");
|
||||
}
|
||||
if (val <= 192) {
|
||||
so.append([val]);
|
||||
} else if (val <= 12,480) {
|
||||
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
|
||||
]);
|
||||
} else throw new Error("Variable integer overflow.");
|
||||
};
|
||||
|
||||
var STInt8 = exports.Int8 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
so.append([val & 0xff]);
|
||||
},
|
||||
parse: function (so) {
|
||||
return so.read(1)[0];
|
||||
}
|
||||
});
|
||||
|
||||
var STInt16 = exports.Int16 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
so.append([
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
]);
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Int16 not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STInt32 = exports.Int32 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
so.append([
|
||||
val >>> 24 & 0xff,
|
||||
val >>> 16 & 0xff,
|
||||
val >>> 8 & 0xff,
|
||||
val & 0xff
|
||||
]);
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Int32 not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STInt64 = exports.Int64 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Int64 not implemented");
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Int64 not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STHash128 = exports.Hash128 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Hash128 not implemented");
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Hash128 not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STHash256 = exports.Hash256 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Hash256 not implemented");
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Hash256 not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STHash160 = exports.Hash160 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Hash160 not implemented");
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Hash160 not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
// Internal
|
||||
var STCurrency = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var currency = val.to_json();
|
||||
if ("string" === typeof currency && currency.length === 3) {
|
||||
var currencyCode = currency.toUpperCase(),
|
||||
currencyData = utils.arraySet(20, 0);
|
||||
|
||||
if (!/^[A-Z]{3}$/.test(currencyCode)) {
|
||||
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 {
|
||||
throw new Error('Tried to serialize invalid/unimplemented currency type.');
|
||||
}
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Currency not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
// Amount (64-bit integer)
|
||||
var valueBytes = utils.arraySet(8, 0);
|
||||
if (amount.is_native()) {
|
||||
var valueHex = amount._value.toString(16);
|
||||
|
||||
// Enforce correct length (64 bits)
|
||||
if (valueHex.length > 16) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
while (valueHex.length < 16) {
|
||||
valueHex = "0" + valueHex;
|
||||
}
|
||||
|
||||
valueBytes = bytes.fromBits(hex.toBits(valueHex));
|
||||
// Clear most significant two bits - these bits should already be 0 if
|
||||
// Amount enforces the range correctly, but we'll clear them anyway just
|
||||
// so this code can make certain guarantees about the encoded value.
|
||||
valueBytes[0] &= 0x3f;
|
||||
if (!amount.is_negative()) valueBytes[0] |= 0x40;
|
||||
} else {
|
||||
var hi = 0, lo = 0;
|
||||
|
||||
// First bit: non-native
|
||||
hi |= 1 << 31;
|
||||
|
||||
if (!amount.is_zero()) {
|
||||
// Second bit: non-negative?
|
||||
if (!amount.is_negative()) hi |= 1 << 30;
|
||||
|
||||
// Next eight bits: offset/exponent
|
||||
hi |= ((97 + amount._offset) & 0xff) << 22;
|
||||
|
||||
// Remaining 52 bits: mantissa
|
||||
hi |= amount._value.shiftRight(32).intValue() & 0x3fffff;
|
||||
lo = amount._value.intValue() & 0xffffffff;
|
||||
}
|
||||
|
||||
valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
|
||||
}
|
||||
|
||||
so.append(valueBytes);
|
||||
|
||||
if (!amount.is_native()) {
|
||||
// Currency (160-bit hash)
|
||||
var currency = amount.currency();
|
||||
STCurrency.serialize(so, currency);
|
||||
|
||||
// Issuer (160-bit hash)
|
||||
so.append(amount.issuer().to_bytes());
|
||||
}
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Amount not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STVL = exports.VariableLength = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
if ("string" === typeof val) this.serialize_hex(so, val);
|
||||
else throw new Error("Unknown datatype.");
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing VL not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STAccount = exports.Account = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
var account = UInt160.from_json(val);
|
||||
this.serialize_hex(so, account.to_hex());
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Account not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STPathSet = exports.PathSet = new SerializedType({
|
||||
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++) {
|
||||
// Boundary
|
||||
if (i) STInt8.serialize(so, this.typeBoundary);
|
||||
|
||||
for (var j = 0, l2 = val[i].length; j < l2; j++) {
|
||||
var entry = val[i][j];
|
||||
|
||||
var type = 0;
|
||||
|
||||
if (entry.account) type |= this.typeAccount;
|
||||
if (entry.currency) type |= this.typeCurrency;
|
||||
if (entry.issuer) type |= this.typeIssuer;
|
||||
|
||||
STInt8.serialize(so, type);
|
||||
|
||||
if (entry.account) {
|
||||
so.append(UInt160.from_json(entry.account).to_bytes());
|
||||
}
|
||||
if (entry.currency) {
|
||||
var currency = Currency.from_json(entry.currency);
|
||||
STCurrency.serialize(so, currency);
|
||||
}
|
||||
if (entry.issuer) {
|
||||
so.append(UInt160.from_json(entry.issuer).to_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
STInt8.serialize(so, this.typeEnd);
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing PathSet not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STVector256 = exports.Vector256 = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Vector256 not implemented");
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Vector256 not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STObject = exports.Object = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Object not implemented");
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Object not implemented");
|
||||
}
|
||||
});
|
||||
|
||||
var STArray = exports.Array = new SerializedType({
|
||||
serialize: function (so, val) {
|
||||
// XXX
|
||||
throw new Error("Serializing Array not implemented");
|
||||
},
|
||||
parse: function (so) {
|
||||
// XXX
|
||||
throw new Error("Parsing Array not implemented");
|
||||
}
|
||||
});
|
||||
255
src/js/server.js
255
src/js/server.js
@@ -1,255 +0,0 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
var utils = require('./utils');
|
||||
|
||||
var Server = function (remote, cfg)
|
||||
{
|
||||
EventEmitter.call(this);
|
||||
|
||||
if ("object" !== typeof cfg || "string" !== typeof cfg.url) {
|
||||
throw new Error("Invalid server configuration.");
|
||||
}
|
||||
|
||||
this._remote = remote;
|
||||
this._cfg = cfg;
|
||||
|
||||
this._ws = null;
|
||||
this._connected = false;
|
||||
this._should_connect = false;
|
||||
this._state = null;
|
||||
|
||||
this._id = 0;
|
||||
this._retry = 0;
|
||||
|
||||
this._requests = {};
|
||||
|
||||
this.on('message', this._handle_message.bind(this));
|
||||
this.on('response_subscribe', this._handle_response_subscribe.bind(this));
|
||||
};
|
||||
|
||||
util.inherits(Server, EventEmitter);
|
||||
|
||||
/**
|
||||
* Server states that we will treat as the server being online.
|
||||
*
|
||||
* Our requirements are that the server can process transactions and notify
|
||||
* us of changes.
|
||||
*/
|
||||
Server.online_states = [
|
||||
'proposing',
|
||||
'validating',
|
||||
'full'
|
||||
];
|
||||
|
||||
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._remote.trace) console.log("server: connect: %s", this._cfg.url);
|
||||
|
||||
// Ensure any existing socket is given the command to close first.
|
||||
if (this._ws) this._ws.close();
|
||||
|
||||
var WebSocket = require('ws');
|
||||
var ws = this._ws = new WebSocket(this._cfg.url);
|
||||
|
||||
this._should_connect = 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.onerror = function (e) {
|
||||
// If we are no longer the active socket, simply ignore any event
|
||||
if (ws !== self._ws) return;
|
||||
|
||||
if (self._remote.trace) console.log("server: onerror: %s", 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.
|
||||
|
||||
// This means that this handler is hardly ever called in practice. If it is,
|
||||
// it probably means the server's WebSocket implementation is corrupt, or
|
||||
// the connection is somehow producing corrupt data.
|
||||
|
||||
// Most WebSocket applications simply log and ignore this error. Once we
|
||||
// support for multiple servers, we may consider doing something like
|
||||
// lowering this server's quality score.
|
||||
|
||||
// However, in Node.js this event may be triggered instead of the close
|
||||
// event, so we need to handle it.
|
||||
handleConnectionClose();
|
||||
};
|
||||
|
||||
// Failure to open.
|
||||
ws.onclose = function () {
|
||||
// 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();
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a Request object to this server.
|
||||
*/
|
||||
Server.prototype.request = function (request)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
// Only bother if we are still connected.
|
||||
if (self._ws) {
|
||||
request.message.id = self._id;
|
||||
|
||||
self._requests[request.message.id] = request;
|
||||
|
||||
// Advance message ID
|
||||
self._id++;
|
||||
|
||||
if (self._state === "online" ||
|
||||
(request.message.command === "subscribe" && self._ws.readyState === 1)) {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject("server: request: %s", request.message);
|
||||
}
|
||||
|
||||
self._ws.send(JSON.stringify(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._ws.send(JSON.stringify(request.message));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (self._remote.trace) {
|
||||
utils.logObject("server: request: DROPPING: %s", request.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._set_state = function (state) {
|
||||
if (state !== this._state) {
|
||||
this._state = state;
|
||||
|
||||
this.emit('state', state);
|
||||
if (state === 'online') {
|
||||
this.emit('connect');
|
||||
} else if (state === 'offline') {
|
||||
this.emit('disconnect');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._handle_message = function (json) {
|
||||
var self = this;
|
||||
|
||||
var message = JSON.parse(json);
|
||||
|
||||
if (message.type === '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);
|
||||
|
||||
request.emit('success', message.result);
|
||||
self.emit('response_'+request.message.command, message.result, request, message);
|
||||
self._remote.emit('response_'+request.message.command, message.result, request, message);
|
||||
} else if (message.error) {
|
||||
if (self._remote.trace) utils.logObject("server: error: %s", message);
|
||||
|
||||
request.emit('error', {
|
||||
'error' : 'remoteError',
|
||||
'error_message' : 'Remote reported an error.',
|
||||
'remote' : message
|
||||
});
|
||||
}
|
||||
} else if (message.type === 'serverStatus') {
|
||||
// This message is only received when online. As we are connected, it is the definative final state.
|
||||
self._set_state(
|
||||
Server.online_states.indexOf(message.server_status) !== -1
|
||||
? 'online'
|
||||
: 'offline');
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._handle_response_subscribe = function (message)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
self._server_status = message.server_status;
|
||||
|
||||
if (Server.online_states.indexOf(message.server_status) !== -1) {
|
||||
self._set_state('online');
|
||||
}
|
||||
};
|
||||
|
||||
exports.Server = Server;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
Submodule src/js/sjcl deleted from dbdef434e7
@@ -1,61 +0,0 @@
|
||||
sjcl.bn.ZERO = new sjcl.bn(0);
|
||||
|
||||
/** [ this / that , this % that ] */
|
||||
sjcl.bn.prototype.divRem = function (that) {
|
||||
if (typeof(that) !== "object") { that = new this._class(that); }
|
||||
var thisa = this.abs(), thata = that.abs(), quot = new this._class(0),
|
||||
ci = 0;
|
||||
if (!thisa.greaterEquals(thata)) {
|
||||
this.initWith(0);
|
||||
return this;
|
||||
} else if (thisa.equals(thata)) {
|
||||
this.initWith(1);
|
||||
return this;
|
||||
}
|
||||
|
||||
for (; thisa.greaterEquals(thata); ci++) {
|
||||
thata.doubleM();
|
||||
}
|
||||
for (; ci > 0; ci--) {
|
||||
quot.doubleM();
|
||||
thata.halveM();
|
||||
if (thisa.greaterEquals(thata)) {
|
||||
quot.addM(1);
|
||||
thisa.subM(that).normalize();
|
||||
}
|
||||
}
|
||||
return [quot, thisa];
|
||||
};
|
||||
|
||||
/** this /= that (rounded to nearest int) */
|
||||
sjcl.bn.prototype.divRound = function (that) {
|
||||
var dr = this.divRem(that), quot = dr[0], rem = dr[1];
|
||||
|
||||
if (rem.doubleM().greaterEquals(that)) {
|
||||
quot.addM(1);
|
||||
}
|
||||
|
||||
return quot;
|
||||
};
|
||||
|
||||
/** this /= that (rounded down) */
|
||||
sjcl.bn.prototype.div = function (that) {
|
||||
var dr = this.divRem(that);
|
||||
return dr[0];
|
||||
};
|
||||
|
||||
sjcl.bn.prototype.sign = function () {
|
||||
return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1;
|
||||
};
|
||||
|
||||
/** -this */
|
||||
sjcl.bn.prototype.neg = function () {
|
||||
return sjcl.bn.ZERO.sub(this);
|
||||
};
|
||||
|
||||
/** |this| */
|
||||
sjcl.bn.prototype.abs = function () {
|
||||
if (this.sign() === -1) {
|
||||
return this.neg();
|
||||
} else return this;
|
||||
};
|
||||
@@ -1,207 +0,0 @@
|
||||
/** @fileOverview Javascript RIPEMD-160 implementation.
|
||||
*
|
||||
* @author Artem S Vybornov <vybornov@gmail.com>
|
||||
*/
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* Context for a RIPEMD-160 operation in progress.
|
||||
* @constructor
|
||||
* @class RIPEMD, 160 bits.
|
||||
*/
|
||||
sjcl.hash.ripemd160 = function (hash) {
|
||||
if (hash) {
|
||||
this._h = hash._h.slice(0);
|
||||
this._buffer = hash._buffer.slice(0);
|
||||
this._length = hash._length;
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hash a string or an array of words.
|
||||
* @static
|
||||
* @param {bitArray|String} data the data to hash.
|
||||
* @return {bitArray} The hash value, an array of 5 big-endian words.
|
||||
*/
|
||||
sjcl.hash.ripemd160.hash = function (data) {
|
||||
return (new sjcl.hash.ripemd160()).update(data).finalize();
|
||||
};
|
||||
|
||||
sjcl.hash.ripemd160.prototype = {
|
||||
/**
|
||||
* Reset the hash state.
|
||||
* @return this
|
||||
*/
|
||||
reset: function () {
|
||||
this._h = _h0.slice(0);
|
||||
this._buffer = [];
|
||||
this._length = 0;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the hash state.
|
||||
* @param {bitArray|String} data the data to hash.
|
||||
* @return this
|
||||
*/
|
||||
update: function (data) {
|
||||
if ( typeof data === "string" )
|
||||
data = sjcl.codec.utf8String.toBits(data);
|
||||
|
||||
var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data),
|
||||
ol = this._length,
|
||||
nl = this._length = ol + sjcl.bitArray.bitLength(data);
|
||||
for (i = 512+ol & -512; i <= nl; i+= 512) {
|
||||
var words = b.splice(0,16);
|
||||
for ( var w = 0; w < 16; ++w )
|
||||
words[w] = _cvt(words[w]);
|
||||
|
||||
_block.call( this, words );
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Complete hashing and output the hash value.
|
||||
* @return {bitArray} The hash value, an array of 5 big-endian words.
|
||||
*/
|
||||
finalize: function () {
|
||||
var b = sjcl.bitArray.concat( this._buffer, [ sjcl.bitArray.partial(1,1) ] ),
|
||||
l = ( this._length + 1 ) % 512,
|
||||
z = ( l > 448 ? 512 : 448 ) - l % 448,
|
||||
zp = z % 32;
|
||||
|
||||
if ( zp > 0 )
|
||||
b = sjcl.bitArray.concat( b, [ sjcl.bitArray.partial(zp,0) ] )
|
||||
for ( ; z >= 32; z -= 32 )
|
||||
b.push(0);
|
||||
|
||||
b.push( _cvt( this._length | 0 ) );
|
||||
b.push( _cvt( Math.floor(this._length / 0x100000000) ) );
|
||||
|
||||
while ( b.length ) {
|
||||
var words = b.splice(0,16);
|
||||
for ( var w = 0; w < 16; ++w )
|
||||
words[w] = _cvt(words[w]);
|
||||
|
||||
_block.call( this, words );
|
||||
}
|
||||
|
||||
var h = this._h;
|
||||
this.reset();
|
||||
|
||||
for ( var w = 0; w < 5; ++w )
|
||||
h[w] = _cvt(h[w]);
|
||||
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
var _h0 = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ];
|
||||
|
||||
var _k1 = [ 0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e ];
|
||||
var _k2 = [ 0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000 ];
|
||||
for ( var i = 4; i >= 0; --i ) {
|
||||
for ( var j = 1; j < 16; ++j ) {
|
||||
_k1.splice(i,0,_k1[i]);
|
||||
_k2.splice(i,0,_k2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var _r1 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
|
||||
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
|
||||
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
|
||||
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 ];
|
||||
var _r2 = [ 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
|
||||
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
|
||||
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
|
||||
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
|
||||
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 ];
|
||||
|
||||
var _s1 = [ 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
|
||||
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
|
||||
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
|
||||
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
|
||||
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ];
|
||||
var _s2 = [ 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
|
||||
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
|
||||
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
|
||||
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
|
||||
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ];
|
||||
|
||||
function _f0(x,y,z) {
|
||||
return x ^ y ^ z;
|
||||
};
|
||||
|
||||
function _f1(x,y,z) {
|
||||
return (x & y) | (~x & z);
|
||||
};
|
||||
|
||||
function _f2(x,y,z) {
|
||||
return (x | ~y) ^ z;
|
||||
};
|
||||
|
||||
function _f3(x,y,z) {
|
||||
return (x & z) | (y & ~z);
|
||||
};
|
||||
|
||||
function _f4(x,y,z) {
|
||||
return x ^ (y | ~z);
|
||||
};
|
||||
|
||||
function _rol(n,l) {
|
||||
return (n << l) | (n >>> (32-l));
|
||||
}
|
||||
|
||||
function _cvt(n) {
|
||||
return ( (n & 0xff << 0) << 24 )
|
||||
| ( (n & 0xff << 8) << 8 )
|
||||
| ( (n & 0xff << 16) >>> 8 )
|
||||
| ( (n & 0xff << 24) >>> 24 );
|
||||
}
|
||||
|
||||
function _block(X) {
|
||||
var A1 = this._h[0], B1 = this._h[1], C1 = this._h[2], D1 = this._h[3], E1 = this._h[4],
|
||||
A2 = this._h[0], B2 = this._h[1], C2 = this._h[2], D2 = this._h[3], E2 = this._h[4];
|
||||
|
||||
var j = 0, T;
|
||||
|
||||
for ( ; j < 16; ++j ) {
|
||||
T = _rol( A1 + _f0(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1;
|
||||
A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T;
|
||||
T = _rol( A2 + _f4(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2;
|
||||
A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; }
|
||||
for ( ; j < 32; ++j ) {
|
||||
T = _rol( A1 + _f1(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1;
|
||||
A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T;
|
||||
T = _rol( A2 + _f3(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2;
|
||||
A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; }
|
||||
for ( ; j < 48; ++j ) {
|
||||
T = _rol( A1 + _f2(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1;
|
||||
A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T;
|
||||
T = _rol( A2 + _f2(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2;
|
||||
A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; }
|
||||
for ( ; j < 64; ++j ) {
|
||||
T = _rol( A1 + _f3(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1;
|
||||
A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T;
|
||||
T = _rol( A2 + _f1(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2;
|
||||
A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; }
|
||||
for ( ; j < 80; ++j ) {
|
||||
T = _rol( A1 + _f4(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1;
|
||||
A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T;
|
||||
T = _rol( A2 + _f0(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2;
|
||||
A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; }
|
||||
|
||||
T = this._h[1] + C1 + D2;
|
||||
this._h[1] = this._h[2] + D1 + E2;
|
||||
this._h[2] = this._h[3] + E1 + A2;
|
||||
this._h[3] = this._h[4] + A1 + B2;
|
||||
this._h[4] = this._h[0] + B1 + C2;
|
||||
this._h[0] = T;
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -1,72 +0,0 @@
|
||||
// ----- for secp256k1 ------
|
||||
|
||||
// Overwrite NIST-P256 with secp256k1 so we're on even footing
|
||||
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",
|
||||
0,
|
||||
7,
|
||||
"0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||
"0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8"
|
||||
);
|
||||
|
||||
// Replace point addition and doubling algorithms
|
||||
// NIST-P256 is a=-3, we need algorithms for a=0
|
||||
sjcl.ecc.pointJac.prototype.add = function(T) {
|
||||
var S = this;
|
||||
if (S.curve !== T.curve) {
|
||||
throw("sjcl.ecc.add(): Points must be on the same curve to add them!");
|
||||
}
|
||||
|
||||
if (S.isIdentity) {
|
||||
return T.toJac();
|
||||
} else if (T.isIdentity) {
|
||||
return S;
|
||||
}
|
||||
|
||||
var z1z1 = S.z.square();
|
||||
var h = T.x.mul(z1z1).subM(S.x);
|
||||
var s2 = T.y.mul(S.z).mul(z1z1);
|
||||
|
||||
if (h.equals(0)) {
|
||||
if (S.y.equals(T.y.mul(z1z1.mul(S.z)))) {
|
||||
// same point
|
||||
return S.doubl();
|
||||
} else {
|
||||
// inverses
|
||||
return new sjcl.ecc.pointJac(S.curve);
|
||||
}
|
||||
}
|
||||
|
||||
var hh = h.square();
|
||||
var i = hh.copy().doubleM().doubleM();
|
||||
var j = h.mul(i);
|
||||
var r = s2.sub(S.y).doubleM();
|
||||
var v = S.x.mul(i);
|
||||
|
||||
var x = r.square().subM(j).subM(v.copy().doubleM());
|
||||
var y = r.mul(v.sub(x)).subM(S.y.mul(j).doubleM());
|
||||
var z = S.z.add(h).square().subM(z1z1).subM(hh);
|
||||
|
||||
return new sjcl.ecc.pointJac(this.curve,x,y,z);
|
||||
};
|
||||
|
||||
sjcl.ecc.pointJac.prototype.doubl = function () {
|
||||
if (this.isIdentity) { return this; }
|
||||
|
||||
var a = this.x.square();
|
||||
var b = this.y.square();
|
||||
var c = b.square();
|
||||
var d = this.x.add(b).square().subM(a).subM(c).doubleM();
|
||||
var e = a.mul(3);
|
||||
var f = e.square();
|
||||
var x = f.sub(d.copy().doubleM());
|
||||
var y = e.mul(d.sub(x)).subM(c.doubleM().doubleM().doubleM());
|
||||
var z = this.y.mul(this.z).doubleM();
|
||||
return new sjcl.ecc.pointJac(this.curve, x, y, z);
|
||||
};
|
||||
|
||||
sjcl.ecc.point.prototype.toBytesCompressed = function () {
|
||||
var header = this.y.mod(2).toString() == "0x0" ? 0x02 : 0x03;
|
||||
return [header].concat(sjcl.codec.bytes.fromBits(this.x.toBits()))
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
sjcl.ecc.ecdsa.secretKey.prototype = {
|
||||
sign: function(hash, paranoia) {
|
||||
var R = this._curve.r,
|
||||
l = R.bitLength(),
|
||||
k = sjcl.bn.random(R.sub(1), paranoia).add(1),
|
||||
r = this._curve.G.mult(k).x.mod(R),
|
||||
s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)).mul(k.inverseMod(R)).mod(R);
|
||||
|
||||
return sjcl.bitArray.concat(r.toBits(l), s.toBits(l));
|
||||
}
|
||||
};
|
||||
|
||||
sjcl.ecc.ecdsa.publicKey.prototype = {
|
||||
verify: function(hash, rs) {
|
||||
var w = sjcl.bitArray,
|
||||
R = this._curve.r,
|
||||
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),
|
||||
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;
|
||||
|
||||
if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) || !r2.equals(r)) {
|
||||
throw (new sjcl.exception.corrupt("signature didn't check out"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -1,680 +0,0 @@
|
||||
//
|
||||
// Transactions
|
||||
//
|
||||
// Construction:
|
||||
// remote.transaction() // Build a transaction object.
|
||||
// .offer_create(...) // Set major parameters.
|
||||
// .set_flags() // Set optional parameters.
|
||||
// .on() // Register for events.
|
||||
// .submit(); // Send to network.
|
||||
//
|
||||
// Events:
|
||||
// 'success' : Transaction submitted without error.
|
||||
// 'error' : Error submitting transaction.
|
||||
// 'proposed' : Advisory proposed status transaction.
|
||||
// - A client should expect 0 to multiple results.
|
||||
// - Might not get back. The remote might just forward the transaction.
|
||||
// - A success could be reverted in final.
|
||||
// - local error: other remotes might like it.
|
||||
// - malformed error: local server thought it was malformed.
|
||||
// - The client should only trust this when talking to a trusted server.
|
||||
// 'final' : Final status of transaction.
|
||||
// - Only expect a final from dishonest servers after a tesSUCCESS or ter*.
|
||||
// 'lost' : Gave up looking for on ledger_closed.
|
||||
// 'pending' : Transaction was not found on ledger_closed.
|
||||
// 'state' : Follow the state of a transaction.
|
||||
// 'client_submitted' - Sent to remote
|
||||
// |- 'remoteError' - Remote rejected transaction.
|
||||
// \- 'client_proposed' - Remote provisionally accepted transaction.
|
||||
// |- 'client_missing' - Transaction has not appeared in ledger as expected.
|
||||
// | |\- 'client_lost' - No longer monitoring missing transaction.
|
||||
// |/
|
||||
// |- 'tesSUCCESS' - Transaction in ledger as expected.
|
||||
// |- 'ter...' - Transaction failed.
|
||||
// \- 'tec...' - Transaction claimed fee only.
|
||||
//
|
||||
// Notes:
|
||||
// - All transactions including those with local and malformed errors may be
|
||||
// forwarded anyway.
|
||||
// - A malicous server can:
|
||||
// - give any proposed result.
|
||||
// - it may declare something correct as incorrect or something correct as incorrect.
|
||||
// - it may not communicate with the rest of the network.
|
||||
// - may or may not forward.
|
||||
//
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
||||
var sjcl = require('../../build/sjcl');
|
||||
|
||||
var Amount = require('./amount').Amount;
|
||||
var Currency = require('./amount').Currency;
|
||||
var UInt160 = require('./amount').UInt160;
|
||||
var Seed = require('./seed').Seed;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
|
||||
var config = require('./config');
|
||||
|
||||
var SUBMIT_MISSING = 4; // Report missing.
|
||||
var SUBMIT_LOST = 8; // Give up tracking.
|
||||
|
||||
// A class to implement transactions.
|
||||
// - Collects parameters
|
||||
// - Allow event listeners to be attached to determine the outcome.
|
||||
var Transaction = function (remote) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
// YYY Make private as many variables as possible.
|
||||
var self = this;
|
||||
|
||||
this.callback = undefined;
|
||||
this.remote = remote;
|
||||
this._secret = undefined;
|
||||
this._build_path = false;
|
||||
this.tx_json = { // Transaction data.
|
||||
'Flags' : 0, // XXX Would be nice if server did not require this.
|
||||
};
|
||||
this.hash = undefined;
|
||||
this.submit_index = undefined; // ledger_current_index was this when transaction was submited.
|
||||
this.state = undefined; // Under construction.
|
||||
this.finalized = false;
|
||||
|
||||
this.on('success', function (message) {
|
||||
if (message.engine_result) {
|
||||
self.hash = message.tx_json.hash;
|
||||
|
||||
self.set_state('client_proposed');
|
||||
|
||||
self.emit('proposed', {
|
||||
'tx_json' : message.tx_json,
|
||||
'result' : message.engine_result,
|
||||
'result_code' : message.engine_result_code,
|
||||
'result_message' : message.engine_result_message,
|
||||
'rejected' : self.isRejected(message.engine_result_code), // If server is honest, don't expect a final if rejected.
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.on('error', function (message) {
|
||||
// Might want to give more detailed information.
|
||||
self.set_state('remoteError');
|
||||
});
|
||||
};
|
||||
|
||||
util.inherits(Transaction, EventEmitter);
|
||||
|
||||
// XXX This needs to be determined from the network.
|
||||
Transaction.fees = {
|
||||
'default' : Amount.from_json("10"),
|
||||
'nickname_create' : Amount.from_json("1000"),
|
||||
'offer' : Amount.from_json("10"),
|
||||
};
|
||||
|
||||
Transaction.flags = {
|
||||
'OfferCreate' : {
|
||||
'Passive' : 0x00010000,
|
||||
'ImmediateOrCancel' : 0x00020000,
|
||||
'FillOrKill' : 0x00040000,
|
||||
'Sell' : 0x00080000,
|
||||
},
|
||||
|
||||
'Payment' : {
|
||||
'NoRippleDirect' : 0x00010000,
|
||||
'PartialPayment' : 0x00020000,
|
||||
'LimitQuality' : 0x00040000,
|
||||
},
|
||||
};
|
||||
|
||||
Transaction.formats = require('./binformat').tx;
|
||||
|
||||
Transaction.HASH_SIGN = 0x53545800;
|
||||
Transaction.HASH_SIGN_TESTNET = 0x73747800;
|
||||
|
||||
Transaction.prototype.consts = {
|
||||
'telLOCAL_ERROR' : -399,
|
||||
'temMALFORMED' : -299,
|
||||
'tefFAILURE' : -199,
|
||||
'terRETRY' : -99,
|
||||
'tesSUCCESS' : 0,
|
||||
'tecCLAIMED' : 100,
|
||||
};
|
||||
|
||||
Transaction.prototype.isTelLocal = function (ter) {
|
||||
return ter >= this.consts.telLOCAL_ERROR && ter < this.consts.temMALFORMED;
|
||||
};
|
||||
|
||||
Transaction.prototype.isTemMalformed = function (ter) {
|
||||
return ter >= this.consts.temMALFORMED && ter < this.consts.tefFAILURE;
|
||||
};
|
||||
|
||||
Transaction.prototype.isTefFailure = function (ter) {
|
||||
return ter >= this.consts.tefFAILURE && ter < this.consts.terRETRY;
|
||||
};
|
||||
|
||||
Transaction.prototype.isTerRetry = function (ter) {
|
||||
return ter >= this.consts.terRETRY && ter < this.consts.tesSUCCESS;
|
||||
};
|
||||
|
||||
Transaction.prototype.isTepSuccess = function (ter) {
|
||||
return ter >= this.consts.tesSUCCESS;
|
||||
};
|
||||
|
||||
Transaction.prototype.isTecClaimed = function (ter) {
|
||||
return ter >= this.consts.tecCLAIMED;
|
||||
};
|
||||
|
||||
Transaction.prototype.isRejected = function (ter) {
|
||||
return this.isTelLocal(ter) || this.isTemMalformed(ter) || this.isTefFailure(ter);
|
||||
};
|
||||
|
||||
Transaction.prototype.set_state = function (state) {
|
||||
if (this.state !== state) {
|
||||
this.state = state;
|
||||
this.emit('state', state);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempts to complete the transaction for submission.
|
||||
*
|
||||
* This function seeks to fill out certain fields, such as Fee and
|
||||
* SigningPubKey, which can be determined by the library based on network
|
||||
* information and other fields.
|
||||
*/
|
||||
Transaction.prototype.complete = function () {
|
||||
var tx_json = this.tx_json;
|
||||
|
||||
if (undefined === tx_json.Fee && this.remote.local_fee) {
|
||||
tx_json.Fee = Transaction.fees['default'].to_json();
|
||||
}
|
||||
|
||||
if (undefined === tx_json.SigningPubKey && (!this.remote || this.remote.local_signing)) {
|
||||
var seed = Seed.from_json(this._secret);
|
||||
var key = seed.get_key(this.tx_json.Account);
|
||||
tx_json.SigningPubKey = key.to_hex_pub();
|
||||
}
|
||||
};
|
||||
|
||||
Transaction.prototype.serialize = function () {
|
||||
return SerializedObject.from_json(this.tx_json);
|
||||
};
|
||||
|
||||
Transaction.prototype.signing_hash = function () {
|
||||
var prefix = config.testnet
|
||||
? Transaction.HASH_SIGN_TESTNET
|
||||
: Transaction.HASH_SIGN;
|
||||
|
||||
return SerializedObject.from_json(this.tx_json).signing_hash(prefix);
|
||||
};
|
||||
|
||||
Transaction.prototype.sign = function () {
|
||||
var seed = Seed.from_json(this._secret),
|
||||
hash = this.signing_hash();
|
||||
|
||||
var key = seed.get_key(this.tx_json.Account),
|
||||
sig = key.sign(hash, 0),
|
||||
hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
|
||||
|
||||
this.tx_json.TxnSignature = hex;
|
||||
};
|
||||
|
||||
// Submit a transaction to the network.
|
||||
// XXX Don't allow a submit without knowing ledger_index.
|
||||
// XXX Have a network canSubmit(), post events for following.
|
||||
// XXX Also give broader status for tracking through network disconnects.
|
||||
// callback = function (status, info) {
|
||||
// // status is final status. Only works under a ledger_accepting conditions.
|
||||
// switch status:
|
||||
// case 'tesSUCCESS': all is well.
|
||||
// case 'tejSecretUnknown': unable to sign transaction - secret unknown
|
||||
// case 'tejServerUntrusted': sending secret to untrusted server.
|
||||
// case 'tejInvalidAccount': locally detected error.
|
||||
// case 'tejLost': locally gave up looking
|
||||
// default: some other TER
|
||||
// }
|
||||
Transaction.prototype.submit = function (callback) {
|
||||
var self = this;
|
||||
var tx_json = this.tx_json;
|
||||
|
||||
this.callback = callback;
|
||||
|
||||
if ('string' !== typeof tx_json.Account)
|
||||
{
|
||||
(this.callback || this.emit)('error', {
|
||||
'error' : 'tejInvalidAccount',
|
||||
'error_message' : 'Bad account.'
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
// YYY Might check paths for invalid accounts.
|
||||
|
||||
this.complete();
|
||||
|
||||
if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) {
|
||||
// There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them.
|
||||
|
||||
this.submit_index = this.remote._ledger_current_index;
|
||||
|
||||
// When a ledger closes, look for the result.
|
||||
var on_ledger_closed = function (message) {
|
||||
var ledger_hash = message.ledger_hash;
|
||||
var ledger_index = message.ledger_index;
|
||||
var stop = false;
|
||||
|
||||
// XXX make sure self.hash is available.
|
||||
self.remote.request_transaction_entry(self.hash)
|
||||
.ledger_hash(ledger_hash)
|
||||
.on('success', function (message) {
|
||||
if (self.finalized) return;
|
||||
|
||||
self.set_state(message.metadata.TransactionResult);
|
||||
self.remote.removeListener('ledger_closed', on_ledger_closed);
|
||||
self.emit('final', message);
|
||||
self.finalized = true;
|
||||
|
||||
if (self.callback)
|
||||
self.callback(message.metadata.TransactionResult, message);
|
||||
})
|
||||
.on('error', function (message) {
|
||||
if (self.finalized) return;
|
||||
|
||||
if ('remoteError' === message.error
|
||||
&& 'transactionNotFound' === message.remote.error) {
|
||||
if (self.submit_index + SUBMIT_LOST < ledger_index) {
|
||||
self.set_state('client_lost'); // Gave up.
|
||||
self.emit('lost');
|
||||
|
||||
if (self.callback)
|
||||
self.callback('tejLost', message);
|
||||
|
||||
self.remote.removeListener('ledger_closed', on_ledger_closed);
|
||||
self.emit('final', message);
|
||||
self.finalized = true;
|
||||
}
|
||||
else if (self.submit_index + SUBMIT_MISSING < ledger_index) {
|
||||
self.set_state('client_missing'); // We don't know what happened to transaction, still might find.
|
||||
self.emit('pending');
|
||||
}
|
||||
else {
|
||||
self.emit('pending');
|
||||
}
|
||||
}
|
||||
// XXX Could log other unexpectedness.
|
||||
})
|
||||
.request();
|
||||
};
|
||||
|
||||
this.remote.on('ledger_closed', on_ledger_closed);
|
||||
|
||||
if (this.callback) {
|
||||
this.on('error', function (message) {
|
||||
self.callback(message.error, message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.set_state('client_submitted');
|
||||
|
||||
if (self.remote.local_sequence && !self.tx_json.Sequence) {
|
||||
self.tx_json.Sequence = this.remote.account_seq(self.tx_json.Account, 'ADVANCE');
|
||||
// console.log("Sequence: %s", self.tx_json.Sequence);
|
||||
|
||||
if (!self.tx_json.Sequence) {
|
||||
// Look in the last closed ledger.
|
||||
this.remote.account_seq_cache(self.tx_json.Account, false)
|
||||
.on('success_account_seq_cache', function () {
|
||||
// Try again.
|
||||
self.submit();
|
||||
})
|
||||
.on('error_account_seq_cache', function (message) {
|
||||
// XXX Maybe be smarter about this. Don't want to trust an untrusted server for this seq number.
|
||||
|
||||
// Look in the current ledger.
|
||||
self.remote.account_seq_cache(self.tx_json.Account, 'CURRENT')
|
||||
.on('success_account_seq_cache', function () {
|
||||
// Try again.
|
||||
self.submit();
|
||||
})
|
||||
.on('error_account_seq_cache', function (message) {
|
||||
// Forward errors.
|
||||
self.emit('error', message);
|
||||
})
|
||||
.request();
|
||||
})
|
||||
.request();
|
||||
return this;
|
||||
}
|
||||
|
||||
// If the transaction fails we want to either undo incrementing the sequence
|
||||
// or submit a noop transaction to consume the sequence remotely.
|
||||
this.on('success', function (res) {
|
||||
if (!res || "string" !== typeof res.engine_result) return;
|
||||
|
||||
switch (res.engine_result.slice(0, 3)) {
|
||||
// Synchronous local error
|
||||
case 'tej':
|
||||
self.remote.account_seq(self.tx_json.Account, 'REWIND');
|
||||
break;
|
||||
// XXX: What do we do in case of ter?
|
||||
case 'tel':
|
||||
case 'tem':
|
||||
case 'tef':
|
||||
// XXX Once we have a transaction submission manager class, we can
|
||||
// check if there are any other transactions pending. If there are,
|
||||
// we should submit a dummy transaction to ensure those
|
||||
// transactions are still valid.
|
||||
//var noop = self.remote.transaction().account_set(self.tx_json.Account);
|
||||
//noop.submit();
|
||||
|
||||
// XXX Hotfix. This only works if no other transactions are pending.
|
||||
self.remote.account_seq(self.tx_json.Account, 'REWIND');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare request
|
||||
|
||||
var request = this.remote.request_submit();
|
||||
|
||||
// Forward successes and errors.
|
||||
request.on('success', function (message) {
|
||||
self.emit('success', message);
|
||||
});
|
||||
request.on('error', function (message) {
|
||||
self.emit('error', message);
|
||||
});
|
||||
|
||||
if (!this._secret && !this.tx_json.Signature) {
|
||||
this.emit('error', {
|
||||
'result' : 'tejSecretUnknown',
|
||||
'result_message' : "Could not sign transactions because we."
|
||||
});
|
||||
return this;
|
||||
} else if (this.remote.local_signing) {
|
||||
this.sign();
|
||||
request.tx_blob(this.serialize().to_hex());
|
||||
} else {
|
||||
if (!this.remote.trusted) {
|
||||
this.emit('error', {
|
||||
'result' : 'tejServerUntrusted',
|
||||
'result_message' : "Attempt to give a secret to an untrusted server."
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
request.secret(this._secret);
|
||||
request.build_path(this._build_path);
|
||||
request.tx_json(this.tx_json);
|
||||
}
|
||||
|
||||
request.request();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//
|
||||
// Set options for Transactions
|
||||
//
|
||||
|
||||
// --> build: true, to have server blindly construct a path.
|
||||
//
|
||||
// "blindly" because the sender has no idea of the actual cost except that is must be less than send max.
|
||||
Transaction.prototype.build_path = function (build) {
|
||||
this._build_path = build;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// tag should be undefined or a 32 bit integer.
|
||||
// YYY Add range checking for tag.
|
||||
Transaction.prototype.destination_tag = function (tag) {
|
||||
if (undefined !== tag)
|
||||
this.tx_json.DestinationTag = tag;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
Transaction._path_rewrite = function (path) {
|
||||
var path_new = [];
|
||||
|
||||
for (var i = 0, l = path.length; i < l; i++) {
|
||||
var node = path[i];
|
||||
var node_new = {};
|
||||
|
||||
if ('account' in node)
|
||||
node_new.account = UInt160.json_rewrite(node.account);
|
||||
|
||||
if ('issuer' in node)
|
||||
node_new.issuer = UInt160.json_rewrite(node.issuer);
|
||||
|
||||
if ('currency' in node)
|
||||
node_new.currency = Currency.json_rewrite(node.currency);
|
||||
|
||||
path_new.push(node_new);
|
||||
}
|
||||
|
||||
return path_new;
|
||||
}
|
||||
|
||||
Transaction.prototype.path_add = function (path) {
|
||||
this.tx_json.Paths = this.tx_json.Paths || [];
|
||||
this.tx_json.Paths.push(Transaction._path_rewrite(path));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// --> paths: undefined or array of path
|
||||
// A path is an array of objects containing some combination of: account, currency, issuer
|
||||
Transaction.prototype.paths = function (paths) {
|
||||
for (var i = 0, l = paths.length; i < l; i++) {
|
||||
this.path_add(paths[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// If the secret is in the config object, it does not need to be provided.
|
||||
Transaction.prototype.secret = function (secret) {
|
||||
this._secret = secret;
|
||||
}
|
||||
|
||||
Transaction.prototype.send_max = function (send_max) {
|
||||
if (send_max)
|
||||
this.tx_json.SendMax = Amount.json_rewrite(send_max);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// tag should be undefined or a 32 bit integer.
|
||||
// YYY Add range checking for tag.
|
||||
Transaction.prototype.source_tag = function (tag) {
|
||||
if (undefined !== tag)
|
||||
this.tx_json.SourceTag = tag;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// --> rate: In billionths.
|
||||
Transaction.prototype.transfer_rate = function (rate) {
|
||||
this.tx_json.TransferRate = Number(rate);
|
||||
|
||||
if (this.tx_json.TransferRate < 1e9)
|
||||
throw 'invalidTransferRate';
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Add flags to a transaction.
|
||||
// --> flags: undefined, _flag_, or [ _flags_ ]
|
||||
Transaction.prototype.set_flags = function (flags) {
|
||||
if (flags) {
|
||||
var transaction_flags = Transaction.flags[this.tx_json.TransactionType];
|
||||
|
||||
if (undefined == this.tx_json.Flags) // We plan to not define this field on new Transaction.
|
||||
this.tx_json.Flags = 0;
|
||||
|
||||
var flag_set = 'object' === typeof flags ? flags : [ flags ];
|
||||
|
||||
for (var index in flag_set) {
|
||||
if (!flag_set.hasOwnProperty(index)) continue;
|
||||
|
||||
var flag = flag_set[index];
|
||||
|
||||
if (flag in transaction_flags) {
|
||||
this.tx_json.Flags += transaction_flags[flag];
|
||||
} else {
|
||||
// XXX Immediately report an error or mark it.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//
|
||||
// Transactions
|
||||
//
|
||||
|
||||
Transaction.prototype._account_secret = function (account) {
|
||||
// Fill in secret from remote, if available.
|
||||
return this.remote.secrets[account];
|
||||
};
|
||||
|
||||
// Options:
|
||||
// .domain() NYI
|
||||
// .message_key() NYI
|
||||
// .transfer_rate()
|
||||
// .wallet_locator() NYI
|
||||
// .wallet_size() NYI
|
||||
Transaction.prototype.account_set = function (src) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'AccountSet';
|
||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Transaction.prototype.claim = function (src, generator, public_key, signature) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'Claim';
|
||||
this.tx_json.Generator = generator;
|
||||
this.tx_json.PublicKey = public_key;
|
||||
this.tx_json.Signature = signature;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Transaction.prototype.offer_cancel = function (src, sequence) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'OfferCancel';
|
||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||
this.tx_json.OfferSequence = Number(sequence);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Options:
|
||||
// .set_flags()
|
||||
// --> expiration : Date or Number
|
||||
Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expiration) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'OfferCreate';
|
||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||
this.tx_json.TakerPays = Amount.json_rewrite(taker_pays);
|
||||
this.tx_json.TakerGets = Amount.json_rewrite(taker_gets);
|
||||
|
||||
if (this.remote.local_fee) {
|
||||
this.tx_json.Fee = Transaction.fees.offer.to_json();
|
||||
}
|
||||
|
||||
if (expiration)
|
||||
this.tx_json.Expiration = Date === expiration.constructor
|
||||
? expiration.getTime()
|
||||
: Number(expiration);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Transaction.prototype.password_fund = function (src, dst) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'PasswordFund';
|
||||
this.tx_json.Destination = UInt160.json_rewrite(dst);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
Transaction.prototype.password_set = function (src, authorized_key, generator, public_key, signature) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'PasswordSet';
|
||||
this.tx_json.RegularKey = authorized_key;
|
||||
this.tx_json.Generator = generator;
|
||||
this.tx_json.PublicKey = public_key;
|
||||
this.tx_json.Signature = signature;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Construct a 'payment' transaction.
|
||||
//
|
||||
// When a transaction is submitted:
|
||||
// - If the connection is reliable and the server is not merely forwarding and is not malicious,
|
||||
// --> src : UInt160 or String
|
||||
// --> dst : UInt160 or String
|
||||
// --> deliver_amount : Amount or String.
|
||||
//
|
||||
// Options:
|
||||
// .paths()
|
||||
// .build_path()
|
||||
// .destination_tag()
|
||||
// .path_add()
|
||||
// .secret()
|
||||
// .send_max()
|
||||
// .set_flags()
|
||||
// .source_tag()
|
||||
Transaction.prototype.payment = function (src, dst, deliver_amount) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'Payment';
|
||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||
this.tx_json.Amount = Amount.json_rewrite(deliver_amount);
|
||||
this.tx_json.Destination = UInt160.json_rewrite(dst);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
Transaction.prototype.ripple_line_set = function (src, limit, quality_in, quality_out) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'TrustSet';
|
||||
this.tx_json.Account = UInt160.json_rewrite(src);
|
||||
|
||||
// Allow limit of 0 through.
|
||||
if (undefined !== limit)
|
||||
this.tx_json.LimitAmount = Amount.json_rewrite(limit);
|
||||
|
||||
if (quality_in)
|
||||
this.tx_json.QualityIn = quality_in;
|
||||
|
||||
if (quality_out)
|
||||
this.tx_json.QualityOut = quality_out;
|
||||
|
||||
// XXX Throw an error if nothing is set.
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Transaction.prototype.wallet_add = function (src, amount, authorized_key, public_key, signature) {
|
||||
this._secret = this._account_secret(src);
|
||||
this.tx_json.TransactionType = 'WalletAdd';
|
||||
this.tx_json.Amount = Amount.json_rewrite(amount);
|
||||
this.tx_json.RegularKey = authorized_key;
|
||||
this.tx_json.PublicKey = public_key;
|
||||
this.tx_json.Signature = signature;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
exports.Transaction = Transaction;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
227
src/js/uint.js
227
src/js/uint.js
@@ -1,227 +0,0 @@
|
||||
|
||||
var sjcl = require('../../build/sjcl');
|
||||
var utils = require('./utils');
|
||||
var config = require('./config');
|
||||
var jsbn = require('./jsbn');
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var nbi = jsbn.nbi;
|
||||
|
||||
var Base = require('./base').Base;
|
||||
|
||||
//
|
||||
// Abstract UInt class
|
||||
//
|
||||
// Base class for UInt??? classes
|
||||
//
|
||||
|
||||
var UInt = function () {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._value = NaN;
|
||||
};
|
||||
|
||||
UInt.json_rewrite = function (j, opts) {
|
||||
return this.from_json(j).to_json(opts);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_generic = function (j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_generic(j);
|
||||
}
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_hex = function (j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_hex(j);
|
||||
}
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_json = function (j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_json(j);
|
||||
}
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bits = function (j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_bits(j);
|
||||
}
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bn = function (j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_bn(j);
|
||||
}
|
||||
};
|
||||
|
||||
UInt.is_valid = function (j) {
|
||||
return this.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
UInt.prototype.clone = function () {
|
||||
return this.copyTo(new this.constructor());
|
||||
};
|
||||
|
||||
// Returns copy.
|
||||
UInt.prototype.copyTo = function (d) {
|
||||
d._value = this._value;
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
UInt.prototype.equals = function (d) {
|
||||
return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value);
|
||||
};
|
||||
|
||||
UInt.prototype.is_valid = function () {
|
||||
return this._value instanceof BigInteger;
|
||||
};
|
||||
|
||||
UInt.prototype.is_zero = function () {
|
||||
return this._value.equals(BigInteger.ZERO);
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt.prototype.parse_generic = function (j) {
|
||||
// Canonicalize and validate
|
||||
if (config.accounts && j in config.accounts)
|
||||
j = config.accounts[j].account;
|
||||
|
||||
switch (j) {
|
||||
case undefined:
|
||||
case "0":
|
||||
case this.constructor.STR_ZERO:
|
||||
case this.constructor.ACCOUNT_ZERO:
|
||||
case this.constructor.HEX_ZERO:
|
||||
this._value = nbi();
|
||||
break;
|
||||
|
||||
case "1":
|
||||
case this.constructor.STR_ONE:
|
||||
case this.constructor.ACCOUNT_ONE:
|
||||
case this.constructor.HEX_ONE:
|
||||
this._value = new BigInteger([1]);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
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);
|
||||
}
|
||||
else if ((this.constructor.width*2) === j.length) {
|
||||
// XXX Check char set!
|
||||
this._value = new BigInteger(j, 16);
|
||||
}
|
||||
else {
|
||||
this._value = NaN;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_hex = function (j) {
|
||||
if ('string' === typeof j &&
|
||||
j.length === (this.constructor.width * 2)) {
|
||||
this._value = new BigInteger(j, 16);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_bits = function (j) {
|
||||
if (sjcl.bitArray.bitLength(j) !== this.constructor.width * 8) {
|
||||
this._value = NaN;
|
||||
} else {
|
||||
var bytes = sjcl.codec.bytes.fromBits(j);
|
||||
this._value = new BigInteger(bytes, 256);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_json = UInt.prototype.parse_hex;
|
||||
|
||||
UInt.prototype.parse_bn = function (j) {
|
||||
if (j instanceof sjcl.bn &&
|
||||
j.bitLength() <= this.constructor.width * 8) {
|
||||
var bytes = sjcl.codec.bytes.fromBits(j.toBits());
|
||||
this._value = new BigInteger(bytes, 256);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Convert from internal form.
|
||||
UInt.prototype.to_bytes = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
return null;
|
||||
|
||||
var bytes = this._value.toByteArray();
|
||||
bytes = bytes.map(function (b) { return (b+256) % 256; });
|
||||
var target = this.constructor.width;
|
||||
|
||||
// XXX Make sure only trim off leading zeros.
|
||||
bytes = bytes.slice(-target);
|
||||
while (bytes.length < target) bytes.unshift(0);
|
||||
|
||||
return bytes;
|
||||
};
|
||||
|
||||
UInt.prototype.to_hex = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
return null;
|
||||
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(bytes)).toUpperCase();
|
||||
};
|
||||
|
||||
UInt.prototype.to_json = UInt.prototype.to_hex;
|
||||
|
||||
UInt.prototype.to_bits = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
return null;
|
||||
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
return sjcl.codec.bytes.toBits(bytes);
|
||||
};
|
||||
|
||||
UInt.prototype.to_bn = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
return null;
|
||||
|
||||
var bits = this.to_bits();
|
||||
|
||||
return sjcl.bn.fromBits(bits);
|
||||
};
|
||||
|
||||
exports.UInt = UInt;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,73 +0,0 @@
|
||||
|
||||
var sjcl = require('../../build/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 UInt = require('./uint').UInt,
|
||||
Base = require('./base').Base;
|
||||
|
||||
//
|
||||
// UInt160 support
|
||||
//
|
||||
|
||||
var UInt160 = extend(function () {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._value = NaN;
|
||||
}, 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);
|
||||
|
||||
// value = NaN on error.
|
||||
UInt160.prototype.parse_json = function (j) {
|
||||
// Canonicalize and validate
|
||||
if (config.accounts && j in config.accounts)
|
||||
j = config.accounts[j].account;
|
||||
|
||||
if ('number' === typeof j) {
|
||||
this._value = new BigInteger(String(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;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// XXX Json form should allow 0 and 1, C++ doesn't currently allow it.
|
||||
UInt160.prototype.to_json = function (opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (!(this._value instanceof BigInteger))
|
||||
return NaN;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
exports.UInt160 = UInt160;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,34 +0,0 @@
|
||||
|
||||
var sjcl = require('../../build/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 UInt = require('./uint').UInt,
|
||||
Base = require('./base').Base;
|
||||
|
||||
//
|
||||
// UInt256 support
|
||||
//
|
||||
|
||||
var UInt256 = extend(function () {
|
||||
// Internal form: NaN or BigInteger
|
||||
this._value = NaN;
|
||||
}, UInt);
|
||||
|
||||
UInt256.width = 32;
|
||||
UInt256.prototype = extend({}, UInt.prototype);
|
||||
UInt256.prototype.constructor = UInt256;
|
||||
|
||||
var HEX_ZERO = UInt256.HEX_ZERO = "00000000000000000000000000000000" +
|
||||
"00000000000000000000000000000000";
|
||||
var HEX_ONE = UInt256.HEX_ONE = "00000000000000000000000000000000" +
|
||||
"00000000000000000000000000000001";
|
||||
var STR_ZERO = UInt256.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
var STR_ONE = UInt256.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
exports.UInt256 = UInt256;
|
||||
136
src/js/utils.js
136
src/js/utils.js
@@ -1,136 +0,0 @@
|
||||
Function.prototype.method = function(name,func) {
|
||||
this.prototype[name] = func;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
var filterErr = function(code, done) {
|
||||
return function(e) {
|
||||
done(e.code !== code ? e : undefined);
|
||||
};
|
||||
};
|
||||
|
||||
var throwErr = function(done) {
|
||||
return function(e) {
|
||||
if (e)
|
||||
throw e;
|
||||
|
||||
done();
|
||||
};
|
||||
};
|
||||
|
||||
var trace = function(comment, func) {
|
||||
return function() {
|
||||
console.log("%s: %s", trace, arguments.toString);
|
||||
func(arguments);
|
||||
};
|
||||
};
|
||||
|
||||
var arraySet = function (count, value) {
|
||||
var i, a = new Array(count);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
a[i] = value;
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
var hexToString = function (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) {
|
||||
a.push(String.fromCharCode(parseInt(h.substring(i, i+2), 16)));
|
||||
}
|
||||
|
||||
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("");
|
||||
};
|
||||
|
||||
var stringToArray = function (s) {
|
||||
var a = new Array(s.length);
|
||||
var i;
|
||||
|
||||
for (i = 0; i != a.length; i += 1)
|
||||
a[i] = s.charCodeAt(i);
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
var hexToArray = function (h) {
|
||||
return stringToArray(hexToString(h));
|
||||
}
|
||||
|
||||
var chunkString = function (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));
|
||||
}
|
||||
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) {
|
||||
if (!assertion) {
|
||||
throw new Error("Assertion failed" + (msg ? ": "+msg : "."));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return unique values in array.
|
||||
*/
|
||||
var arrayUnique = function (arr) {
|
||||
var u = {}, a = [];
|
||||
for (var i = 0, l = arr.length; i < l; ++i){
|
||||
if (u.hasOwnProperty(arr[i])) {
|
||||
continue;
|
||||
}
|
||||
a.push(arr[i]);
|
||||
u[arr[i]] = 1;
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a ripple epoch to a JavaScript timestamp.
|
||||
*
|
||||
* JavaScript timestamps are unix epoch in milliseconds.
|
||||
*/
|
||||
var toTimestamp = function (rpepoch) {
|
||||
return (rpepoch + 0x386D4380) * 1000;
|
||||
};
|
||||
|
||||
exports.trace = trace;
|
||||
exports.arraySet = arraySet;
|
||||
exports.hexToString = hexToString;
|
||||
exports.hexToArray = hexToArray;
|
||||
exports.stringToArray = stringToArray;
|
||||
exports.stringToHex = stringToHex;
|
||||
exports.chunkString = chunkString;
|
||||
exports.logObject = logObject;
|
||||
exports.assert = assert;
|
||||
exports.arrayUnique = arrayUnique;
|
||||
exports.toTimestamp = toTimestamp;
|
||||
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
102
src/keypair.js
Normal file
102
src/keypair.js
Normal file
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
/*eslint new-cap: 1*/
|
||||
|
||||
var sjcl = require('./utils').sjcl;
|
||||
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
function KeyPair() {
|
||||
this._curve = sjcl.ecc.curves.k256;
|
||||
this._secret = null;
|
||||
this._pubkey = null;
|
||||
}
|
||||
|
||||
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) {
|
||||
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves.k256, j);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return {sjcl.ecc.ecdsa.publicKey} public key
|
||||
*/
|
||||
KeyPair.prototype._pub = function() {
|
||||
var curve = this._curve;
|
||||
|
||||
if (!this._pubkey && this._secret) {
|
||||
var exponent = this._secret._exponent;
|
||||
|
||||
this._pubkey = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(exponent));
|
||||
}
|
||||
|
||||
return this._pubkey;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return {sjcl.bitArray} public key bits in compressed form
|
||||
*/
|
||||
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())
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {String} public key bytes in compressed form, hex encoded.
|
||||
*/
|
||||
KeyPair.prototype.to_hex_pub = function() {
|
||||
var bits = this._pub_bits();
|
||||
|
||||
if (!bits) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sjcl.codec.hex.fromBits(bits).toUpperCase();
|
||||
};
|
||||
|
||||
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 PARANOIA_256_BITS = 6; // sjcl constant for ensuring 256 bits of entropy
|
||||
hash = UInt256.from_json(hash);
|
||||
var sig = this._secret.sign(hash.to_bits(), PARANOIA_256_BITS);
|
||||
sig = this._secret.canonicalizeSignature(sig);
|
||||
return this._secret.encodeDER(sig);
|
||||
};
|
||||
|
||||
exports.KeyPair = KeyPair;
|
||||
166
src/ledger.js
Normal file
166
src/ledger.js
Normal file
@@ -0,0 +1,166 @@
|
||||
/* eslint-disable valid-jsdoc */
|
||||
'use strict';
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
var SHAMap = require('./shamap').SHAMap;
|
||||
var SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
var stypes = require('./serializedtypes');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Currency = require('./currency').Currency;
|
||||
|
||||
function Ledger() {
|
||||
this.ledger_json = {};
|
||||
}
|
||||
|
||||
Ledger.from_json = function(v) {
|
||||
var ledger = new Ledger();
|
||||
ledger.parse_json(v);
|
||||
return ledger;
|
||||
};
|
||||
|
||||
Ledger.space = require('./ledgerspaces');
|
||||
|
||||
/**
|
||||
* Generate the key for an AccountRoot entry.
|
||||
*
|
||||
* @param {String|UInt160} account Ripple Account
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcAccountRootEntryHash =
|
||||
Ledger.prototype.calcAccountRootEntryHash = function(account) {
|
||||
account = UInt160.from_json(account);
|
||||
|
||||
var index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.account.charCodeAt(0)]);
|
||||
index.append(account.to_bytes());
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the key for an Offer entry.
|
||||
*
|
||||
* @param {String|UInt160} account Ripple Account
|
||||
* @param {Number} sequence Sequence number of the OfferCreate transaction
|
||||
* that instantiated this offer.
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcOfferEntryHash =
|
||||
Ledger.prototype.calcOfferEntryHash = function(account, sequence) {
|
||||
account = UInt160.from_json(account);
|
||||
sequence = parseInt(sequence, 10);
|
||||
|
||||
var index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.offer.charCodeAt(0)]);
|
||||
index.append(account.to_bytes());
|
||||
stypes.Int32.serialize(index, sequence);
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the key for a RippleState entry.
|
||||
*
|
||||
* The ordering of the two account parameters does not matter.
|
||||
*
|
||||
* @param {String|UInt160} account1 First Ripple Account
|
||||
* @param {String|UInt160} account2 Second Ripple Account
|
||||
* @param {String|Currency} currency The currency code
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcRippleStateEntryHash =
|
||||
Ledger.prototype.calcRippleStateEntryHash = function(
|
||||
account1, account2, currency) {
|
||||
currency = Currency.from_json(currency);
|
||||
account1 = UInt160.from_json(account1);
|
||||
account2 = UInt160.from_json(account2);
|
||||
|
||||
if (!account1.is_valid()) {
|
||||
throw new Error('Invalid first account');
|
||||
}
|
||||
if (!account2.is_valid()) {
|
||||
throw new Error('Invalid second account');
|
||||
}
|
||||
if (!currency.is_valid()) {
|
||||
throw new Error('Invalid currency');
|
||||
}
|
||||
|
||||
// The lower ID has to come first
|
||||
if (account1.to_bn().greaterEquals(account2.to_bn())) {
|
||||
var tmp = account2;
|
||||
account2 = account1;
|
||||
account1 = tmp;
|
||||
}
|
||||
|
||||
var index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.rippleState.charCodeAt(0)]);
|
||||
index.append(account1.to_bytes());
|
||||
index.append(account2.to_bytes());
|
||||
index.append(currency.to_bytes());
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
Ledger.prototype.parse_json = function(v) {
|
||||
this.ledger_json = v;
|
||||
};
|
||||
|
||||
Ledger.prototype.calc_tx_hash = function() {
|
||||
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();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param options .sanity_test {Boolean}
|
||||
* @return hash of shamap
|
||||
*
|
||||
* If `true`, will serialize each accountState item to binary and then back to
|
||||
* json before finally serializing for hashing. This is mostly to expose any
|
||||
* issues with ripple-lib's binary <--> json codecs.
|
||||
*
|
||||
*/
|
||||
Ledger.prototype.calc_account_hash = function(options) {
|
||||
var account_map = new SHAMap();
|
||||
var erred;
|
||||
|
||||
this.ledger_json.accountState.forEach(function(le) {
|
||||
var data = SerializedObject.from_json(le);
|
||||
|
||||
var json;
|
||||
if (options && options.sanity_test) {
|
||||
try {
|
||||
json = data.to_json();
|
||||
data = SerializedObject.from_json(json);
|
||||
} catch (e) {
|
||||
console.log('account state item: ', le);
|
||||
console.log('to_json() ', json);
|
||||
console.log('exception: ', e);
|
||||
erred = true;
|
||||
}
|
||||
}
|
||||
|
||||
account_map.add_item(le.index, data, SHAMapTreeNode.TYPE_ACCOUNT_STATE);
|
||||
});
|
||||
|
||||
if (erred) {
|
||||
throw new Error('There were errors with sanity_test'); // all logged above
|
||||
}
|
||||
|
||||
return account_map.hash();
|
||||
};
|
||||
|
||||
exports.Ledger = Ledger;
|
||||
22
src/ledgerspaces.js
Normal file
22
src/ledgerspaces.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Ripple ledger namespace prefixes.
|
||||
*
|
||||
* The Ripple ledger is a key-value store. In order to avoid name collisions,
|
||||
* names are partitioned into namespaces.
|
||||
*
|
||||
* Each namespace is just a single character prefix.
|
||||
*/
|
||||
module.exports = {
|
||||
account : 'a',
|
||||
dirNode : 'd',
|
||||
generatorMap : 'g',
|
||||
nickname : 'n',
|
||||
rippleState : 'r',
|
||||
offer : 'o', // Entry for an offer.
|
||||
ownerDir : 'O', // Directory of things owned by an account.
|
||||
bookDir : 'B', // Directory of order books.
|
||||
contract : 'c',
|
||||
skipList : 's',
|
||||
amendment : 'f',
|
||||
feeSettings : 'e'
|
||||
};
|
||||
193
src/log.js
Normal file
193
src/log.js
Normal file
@@ -0,0 +1,193 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
/**
|
||||
* Logging functionality for ripple-lib and any applications built on it.
|
||||
*
|
||||
* @param {String} namespace logging prefix
|
||||
* @return {Void} this function does not return...
|
||||
*/
|
||||
function Log(namespace) {
|
||||
if (!namespace) {
|
||||
this._namespace = [];
|
||||
} else if (Array.isArray(namespace)) {
|
||||
this._namespace = namespace;
|
||||
} else {
|
||||
this._namespace = [String(namespace)];
|
||||
}
|
||||
|
||||
this._prefix = this._namespace.concat(['']).join(': ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sub-logger.
|
||||
*
|
||||
* You can have a hierarchy of loggers.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var log = require('ripple').log.sub('server');
|
||||
*
|
||||
* log.info('connection successful');
|
||||
* // prints: 'server: connection successful'
|
||||
*
|
||||
* @param {String} namespace logging prefix
|
||||
* @return {Log} sub logger
|
||||
*/
|
||||
Log.prototype.sub = function(namespace) {
|
||||
const subNamespace = this._namespace.slice();
|
||||
|
||||
if (namespace && typeof namespace === 'string') {
|
||||
subNamespace.push(namespace);
|
||||
}
|
||||
|
||||
const subLogger = new Log(subNamespace);
|
||||
subLogger._setParent(this);
|
||||
return subLogger;
|
||||
};
|
||||
|
||||
Log.prototype._setParent = function(parentLogger) {
|
||||
this._parent = parentLogger;
|
||||
};
|
||||
|
||||
Log.makeLevel = function(level) {
|
||||
return function() {
|
||||
const args = Array.prototype.slice.apply(arguments);
|
||||
args[0] = this._prefix + args[0];
|
||||
Log.engine.logObject.apply(Log, [level].concat(args[0], [args.slice(2)]));
|
||||
};
|
||||
};
|
||||
|
||||
Log.prototype.debug = Log.makeLevel(1);
|
||||
Log.prototype.info = Log.makeLevel(2);
|
||||
Log.prototype.warn = Log.makeLevel(3);
|
||||
Log.prototype.error = Log.makeLevel(4);
|
||||
|
||||
/**
|
||||
* @param {String} message
|
||||
* @param {Array} details
|
||||
* @return {Array} prepared log info
|
||||
*/
|
||||
|
||||
function getLogInfo(message, args) {
|
||||
const stack = new Error().stack;
|
||||
|
||||
return [
|
||||
// Timestamp
|
||||
'[' + new Date().toISOString() + ']',
|
||||
message,
|
||||
'--',
|
||||
// Location
|
||||
(typeof stack === 'string') ? stack.split('\n')[4].replace(/^\s+/, '') : '',
|
||||
'\n'
|
||||
].concat(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} log level
|
||||
* @param {Array} log info
|
||||
*/
|
||||
|
||||
function logMessage(logLevel, args) {
|
||||
switch (logLevel) {
|
||||
case 1:
|
||||
case 2:
|
||||
console.log.apply(console, args);
|
||||
break;
|
||||
case 3:
|
||||
console.warn.apply(console, args);
|
||||
break;
|
||||
case 4:
|
||||
console.error.apply(console, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const engines = {};
|
||||
|
||||
/**
|
||||
* Basic logging connector.
|
||||
*
|
||||
* This engine has no formatting and works with the most basic of 'console.log'
|
||||
* implementations. This is the logging engine used in Node.js.
|
||||
*/
|
||||
engines.basic = {
|
||||
logObject: function logObject(level, message, args_) {
|
||||
const args = args_.map(function(arg) {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
});
|
||||
|
||||
logMessage(level, getLogInfo(message, args));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log engine for browser consoles.
|
||||
*
|
||||
* Browsers tend to have better consoles that support nicely formatted
|
||||
* JavaScript objects. This connector passes objects through to the logging
|
||||
* function without any stringification.
|
||||
*/
|
||||
engines.interactive = {
|
||||
logObject: function(level, message, args_) {
|
||||
const args = args_.map(function(arg) {
|
||||
return /MSIE/.test(navigator.userAgent)
|
||||
? JSON.stringify(arg, null, 2)
|
||||
: arg;
|
||||
});
|
||||
|
||||
logMessage(level, getLogInfo(message, args));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Null logging connector.
|
||||
*
|
||||
* This engine simply swallows all messages. Used when console.log is not
|
||||
* available.
|
||||
*/
|
||||
engines.none = {
|
||||
logObject: function() {}
|
||||
};
|
||||
|
||||
Log.getEngine = Log.prototype.getEngine = function() {
|
||||
return Log.engine;
|
||||
};
|
||||
|
||||
Log.setEngine = Log.prototype.setEngine = function(engine) {
|
||||
assert.strictEqual(typeof engine, 'object');
|
||||
assert.strictEqual(typeof engine.logObject, 'function');
|
||||
Log.engine = engine;
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined' && typeof console !== 'undefined') {
|
||||
Log.setEngine(engines.interactive);
|
||||
} else if (typeof console !== 'undefined' && console.log) {
|
||||
Log.setEngine(engines.basic);
|
||||
} else {
|
||||
Log.setEngine(engines.none);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a root logger as our main export.
|
||||
*
|
||||
* This means you can use the logger easily on the fly:
|
||||
* ripple.log.debug('My object is', myObj);
|
||||
*/
|
||||
module.exports = new Log();
|
||||
|
||||
/**
|
||||
* This is the logger for ripple-lib internally.
|
||||
*/
|
||||
module.exports.internal = module.exports.sub();
|
||||
|
||||
/**
|
||||
* Expose the class as well.
|
||||
*/
|
||||
module.exports.Log = Log;
|
||||
|
||||
/**
|
||||
* Expose log engines
|
||||
*/
|
||||
module.exports.engines = engines;
|
||||
209
src/message.js
Normal file
209
src/message.js
Normal file
@@ -0,0 +1,209 @@
|
||||
/* eslint-disable valid-jsdoc */
|
||||
'use strict';
|
||||
const async = require('async');
|
||||
const sjcl = require('./utils').sjcl;
|
||||
const Remote = require('./remote').Remote;
|
||||
const Seed = require('./seed').Seed;
|
||||
const KeyPair = require('./keypair').KeyPair;
|
||||
const Account = require('./account').Account;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
|
||||
// Message class (static)
|
||||
const Message = {};
|
||||
|
||||
Message.hashFunction = sjcl.hash.sha512.hash;
|
||||
Message.MAGIC_BYTES = 'Ripple Signed Message:\n';
|
||||
|
||||
const REGEX_HEX = /^[0-9a-fA-F]+$/;
|
||||
const REGEX_BASE64 =
|
||||
/^([A-Za-z0-9\+]{4})*([A-Za-z0-9\+]{2}==)|([A-Za-z0-9\+]{3}=)?$/;
|
||||
|
||||
/**
|
||||
* Produce a Base64-encoded signature on the given message with
|
||||
* the string 'Ripple Signed Message:\n' prepended.
|
||||
*
|
||||
* Note that this signature uses the signing function that includes
|
||||
* a recovery_factor to be able to extract the public key from the signature
|
||||
* without having to pass the public key along with the signature.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {String} message
|
||||
* @param {sjcl.ecc.ecdsa.secretKey|Any format accepted by Seed.from_json}
|
||||
* secret_key
|
||||
* @param {RippleAddress} [The first key] account Field to specify the signing
|
||||
* account. If this is omitted the first account produced by the secret
|
||||
* generator will be used.
|
||||
* @return {Base64-encoded String} signature
|
||||
*/
|
||||
Message.signMessage = function(message, secret_key, account) {
|
||||
return Message.signHash(Message.hashFunction(Message.MAGIC_BYTES + message),
|
||||
secret_key, account);
|
||||
};
|
||||
|
||||
/**
|
||||
* Produce a Base64-encoded signature on the given hex-encoded hash.
|
||||
*
|
||||
* Note that this signature uses the signing function that includes
|
||||
* a recovery_factor to be able to extract the public key from the signature
|
||||
* without having to pass the public key along with the signature.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {bitArray|Hex-encoded String} hash
|
||||
* @param {sjcl.ecc.ecdsa.secretKey|Any format accepted by Seed.from_json}
|
||||
* secret_key
|
||||
* @param {RippleAddress} [The first key] account Field to specify the
|
||||
* signing account. If this is omitted the first account produced by
|
||||
* the secret generator will be used.
|
||||
* @returns {Base64-encoded String} signature
|
||||
*/
|
||||
Message.signHash = function(_hash, secret_key_, account) {
|
||||
|
||||
const hash = typeof _hash === 'string' && /^[0-9a-fA-F]+$/.test(_hash) ?
|
||||
sjcl.codec.hex.toBits(_hash) : _hash;
|
||||
|
||||
if (typeof hash !== 'object' ||
|
||||
typeof hash[0] !== 'number' ||
|
||||
hash.length <= 0) {
|
||||
throw new Error('Hash must be a bitArray or hex-encoded string');
|
||||
}
|
||||
|
||||
const secret_key = !(secret_key_ instanceof sjcl.ecc.ecdsa.secretKey) ?
|
||||
Seed.from_json(secret_key_).get_key(account)._secret : secret_key_;
|
||||
|
||||
const PARANOIA_256_BITS = 6; // sjcl constant for ensuring 256 bits of entropy
|
||||
const signature_bits = secret_key.signWithRecoverablePublicKey(hash,
|
||||
PARANOIA_256_BITS);
|
||||
const signature_base64 = sjcl.codec.base64.fromBits(signature_bits);
|
||||
|
||||
return signature_base64;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify the signature on a given message.
|
||||
*
|
||||
* Note that this function is asynchronous.
|
||||
* The ripple-lib remote is used to check that the public
|
||||
* key extracted from the signature corresponds to one that is currently
|
||||
* active for the given account.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {String} data.message
|
||||
* @param {RippleAddress} data.account
|
||||
* @param {Base64-encoded String} data.signature
|
||||
* @param {ripple-lib Remote} remote
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @callback callback
|
||||
* @param {Error} error
|
||||
* @param {boolean} is_valid true if the signature is valid, false otherwise
|
||||
*/
|
||||
Message.verifyMessageSignature = function(data, remote, callback) {
|
||||
|
||||
if (typeof data.message === 'string') {
|
||||
data.hash = Message.hashFunction(Message.MAGIC_BYTES + data.message);
|
||||
} else {
|
||||
return callback(new Error(
|
||||
'Data object must contain message field to verify signature'));
|
||||
}
|
||||
|
||||
return Message.verifyHashSignature(data, remote, callback);
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify the signature on a given hash.
|
||||
*
|
||||
* Note that this function is asynchronous.
|
||||
* The ripple-lib remote is used to check that the public
|
||||
* key extracted from the signature corresponds to one that is currently
|
||||
* active for the given account.
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {bitArray|Hex-encoded String} data.hash
|
||||
* @param {RippleAddress} data.account
|
||||
* @param {Base64-encoded String} data.signature
|
||||
* @param {ripple-lib Remote} remote
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @callback callback
|
||||
* @param {Error} error
|
||||
* @param {boolean} is_valid true if the signature is valid, false otherwise
|
||||
*/
|
||||
Message.verifyHashSignature = function(data, remote, callback) {
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error('Must supply callback function');
|
||||
}
|
||||
|
||||
const hash = REGEX_HEX.test(data.hash) ?
|
||||
sjcl.codec.hex.toBits(data.hash) :
|
||||
data.hash;
|
||||
|
||||
if (typeof hash !== 'object' || hash.length <= 0
|
||||
|| typeof hash[0] !== 'number') {
|
||||
return callback(new Error('Hash must be a bitArray or hex-encoded string'));
|
||||
}
|
||||
|
||||
const account = data.account || data.address;
|
||||
if (!account || !UInt160.from_json(account).is_valid()) {
|
||||
return callback(new Error('Account must be a valid ripple address'));
|
||||
}
|
||||
|
||||
const signature = data.signature;
|
||||
if (typeof signature !== 'string' || !REGEX_BASE64.test(signature)) {
|
||||
return callback(new Error('Signature must be a Base64-encoded string'));
|
||||
}
|
||||
|
||||
const signature_bits = sjcl.codec.base64.toBits(signature);
|
||||
|
||||
if (!(remote instanceof Remote) || remote.state !== 'online') {
|
||||
return callback(new Error(
|
||||
'Must supply connected Remote to verify signature'));
|
||||
}
|
||||
|
||||
function recoverPublicKey(async_callback) {
|
||||
let public_key;
|
||||
try {
|
||||
public_key =
|
||||
sjcl.ecc.ecdsa.publicKey.recoverFromSignature(hash, signature_bits);
|
||||
} catch (err) {
|
||||
return async_callback(err);
|
||||
}
|
||||
|
||||
if (public_key) {
|
||||
async_callback(null, public_key);
|
||||
} else {
|
||||
async_callback(new Error('Could not recover public key from signature'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkPublicKeyIsValid(public_key, async_callback) {
|
||||
|
||||
// Get hex-encoded public key
|
||||
const key_pair = new KeyPair();
|
||||
key_pair._pubkey = public_key;
|
||||
const public_key_hex = key_pair.to_hex_pub();
|
||||
|
||||
const account_class_instance = new Account(remote, account);
|
||||
account_class_instance.publicKeyIsActive(public_key_hex, async_callback);
|
||||
|
||||
}
|
||||
|
||||
const steps = [
|
||||
recoverPublicKey,
|
||||
checkPublicKeyIsValid
|
||||
];
|
||||
|
||||
async.waterfall(steps, callback);
|
||||
|
||||
};
|
||||
|
||||
exports.Message = Message;
|
||||
260
src/meta.js
Normal file
260
src/meta.js
Normal file
@@ -0,0 +1,260 @@
|
||||
var extend = require('extend');
|
||||
var utils = require('./utils');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var Amount = require('./amount').Amount;
|
||||
|
||||
/**
|
||||
* Meta data processing facility
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} transaction metadata
|
||||
*/
|
||||
|
||||
function Meta(data) {
|
||||
var self = this;
|
||||
|
||||
this.nodes = [ ];
|
||||
|
||||
if (typeof data !== 'object') {
|
||||
throw new TypeError('Missing metadata');
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.AffectedNodes)) {
|
||||
throw new TypeError('Metadata missing AffectedNodes');
|
||||
}
|
||||
|
||||
data.AffectedNodes.forEach(this.addNode, this);
|
||||
};
|
||||
|
||||
Meta.NODE_TYPES = [
|
||||
'CreatedNode',
|
||||
'ModifiedNode',
|
||||
'DeletedNode'
|
||||
];
|
||||
|
||||
Meta.AMOUNT_FIELDS_AFFECTING_ISSUER = [
|
||||
'LowLimit',
|
||||
'HighLimit',
|
||||
'TakerPays',
|
||||
'TakerGets'
|
||||
];
|
||||
|
||||
Meta.ACCOUNT_FIELDS = [
|
||||
'Account',
|
||||
'Owner',
|
||||
'Destination',
|
||||
'Issuer',
|
||||
'Target'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {Object} node
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Meta.prototype.getNodeType = function(node) {
|
||||
var result = null;
|
||||
|
||||
for (var i=0; i<Meta.NODE_TYPES.length; i++) {
|
||||
var type = Meta.NODE_TYPES[i];
|
||||
if (node.hasOwnProperty(type)) {
|
||||
result = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} field
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Meta.prototype.isAccountField = function(field) {
|
||||
return Meta.ACCOUNT_FIELDS.indexOf(field) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add node to metadata
|
||||
*
|
||||
* @param {Object} node
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Meta.prototype.addNode = function(node) {
|
||||
this._affectedAccounts = void(0);
|
||||
this._affectedBooks = void(0);
|
||||
|
||||
var result = { };
|
||||
|
||||
if ((result.nodeType = this.getNodeType(node))) {
|
||||
node = node[result.nodeType];
|
||||
result.diffType = result.nodeType;
|
||||
result.entryType = node.LedgerEntryType;
|
||||
result.ledgerIndex = node.LedgerIndex;
|
||||
result.fields = extend({ }, node.PreviousFields, node.NewFields, node.FinalFields);
|
||||
result.fieldsPrev = node.PreviousFields || { };
|
||||
result.fieldsNew = node.NewFields || { };
|
||||
result.fieldsFinal = node.FinalFields || { };
|
||||
|
||||
// getAffectedBooks will set this
|
||||
// result.bookKey = undefined;
|
||||
|
||||
this.nodes.push(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get affected nodes array
|
||||
*
|
||||
* @param {Object} filter options
|
||||
* @return {Array} nodes
|
||||
*/
|
||||
|
||||
Meta.prototype.getNodes = function(options) {
|
||||
if (typeof options === 'object') {
|
||||
return this.nodes.filter(function(node) {
|
||||
if (options.nodeType && options.nodeType !== node.nodeType) {
|
||||
return false;
|
||||
}
|
||||
if (options.entryType && options.entryType !== node.entryType) {
|
||||
return false;
|
||||
}
|
||||
if (options.bookKey && options.bookKey !== node.bookKey) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
return this.nodes;
|
||||
}
|
||||
};
|
||||
|
||||
Meta.prototype.getAffectedAccounts = function(from) {
|
||||
if (this._affectedAccounts) {
|
||||
return this._affectedAccounts;
|
||||
}
|
||||
|
||||
var accounts = [ ];
|
||||
|
||||
// This code should match the behavior of the C++ method:
|
||||
// TransactionMetaSet::getAffectedAccounts
|
||||
for (var i=0; i<this.nodes.length; i++) {
|
||||
var node = this.nodes[i];
|
||||
var fields = (node.nodeType === 'CreatedNode')
|
||||
? node.fieldsNew
|
||||
: node.fieldsFinal;
|
||||
|
||||
for (var fieldName in fields) {
|
||||
var field = fields[fieldName];
|
||||
|
||||
if (this.isAccountField(fieldName) && UInt160.is_valid(field)) {
|
||||
accounts.push(field);
|
||||
} else if (~Meta.AMOUNT_FIELDS_AFFECTING_ISSUER.indexOf(fieldName)) {
|
||||
var amount = Amount.from_json(field);
|
||||
var issuer = amount.issuer();
|
||||
if (issuer.is_valid() && !issuer.is_zero()) {
|
||||
accounts.push(issuer.to_json());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._affectedAccounts = utils.arrayUnique(accounts);
|
||||
|
||||
return this._affectedAccounts;
|
||||
};
|
||||
|
||||
Meta.prototype.getAffectedBooks = function() {
|
||||
if (this._affectedBooks) {
|
||||
return this._affectedBooks;
|
||||
}
|
||||
|
||||
var books = [ ];
|
||||
|
||||
for (var i=0; i<this.nodes.length; i++) {
|
||||
var node = this.nodes[i];
|
||||
|
||||
if (node.entryType !== 'Offer') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var gets = Amount.from_json(node.fields.TakerGets);
|
||||
var pays = Amount.from_json(node.fields.TakerPays);
|
||||
var getsKey = gets.currency().to_json();
|
||||
var paysKey = pays.currency().to_json();
|
||||
|
||||
if (getsKey !== 'XRP') {
|
||||
getsKey += '/' + gets.issuer().to_json();
|
||||
}
|
||||
|
||||
if (paysKey !== 'XRP') {
|
||||
paysKey += '/' + pays.issuer().to_json();
|
||||
}
|
||||
|
||||
var key = getsKey + ':' + paysKey;
|
||||
|
||||
// Hell of a lot of work, so we are going to cache this. We can use this
|
||||
// later to good effect in OrderBook.notify to make sure we only process
|
||||
// pertinent offers.
|
||||
node.bookKey = key;
|
||||
|
||||
books.push(key);
|
||||
}
|
||||
|
||||
this._affectedBooks = utils.arrayUnique(books);
|
||||
|
||||
return this._affectedBooks;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Execute a function on each affected node.
|
||||
*
|
||||
* The callback is passed two parameters. The first is a node object which looks
|
||||
* like this:
|
||||
*
|
||||
* {
|
||||
* // Type of diff, e.g. CreatedNode, ModifiedNode
|
||||
* nodeType: 'CreatedNode'
|
||||
*
|
||||
* // Type of node affected, e.g. RippleState, AccountRoot
|
||||
* entryType: 'RippleState',
|
||||
*
|
||||
* // Index of the ledger this change occurred in
|
||||
* ledgerIndex: '01AB01AB...',
|
||||
*
|
||||
* // Contains all fields with later versions taking precedence
|
||||
* //
|
||||
* // This is a shorthand for doing things like checking which account
|
||||
* // this affected without having to check the nodeType.
|
||||
* fields: {...},
|
||||
*
|
||||
* // Old fields (before the change)
|
||||
* fieldsPrev: {...},
|
||||
*
|
||||
* // New fields (that have been added)
|
||||
* fieldsNew: {...},
|
||||
*
|
||||
* // Changed fields
|
||||
* fieldsFinal: {...}
|
||||
* }
|
||||
*/
|
||||
|
||||
[
|
||||
'forEach',
|
||||
'map',
|
||||
'filter',
|
||||
'every',
|
||||
'some',
|
||||
'reduce'
|
||||
].forEach(function(fn) {
|
||||
Meta.prototype[fn] = function() {
|
||||
return Array.prototype[fn].apply(this.nodes, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Meta.prototype.each = Meta.prototype.forEach;
|
||||
|
||||
exports.Meta = Meta;
|
||||
1406
src/orderbook.js
Normal file
1406
src/orderbook.js
Normal file
File diff suppressed because it is too large
Load Diff
136
src/orderbookutils.js
Normal file
136
src/orderbookutils.js
Normal file
@@ -0,0 +1,136 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const SerializedObject = require('./serializedobject').SerializedObject;
|
||||
const Types = require('./serializedtypes');
|
||||
const Amount = require('./amount').Amount;
|
||||
const Currency = require('./currency').Currency;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
|
||||
// const IOU_SUFFIX = '/000/rrrrrrrrrrrrrrrrrrrrrhoLvTp';
|
||||
const IOU_SUFFIX_CURRENCY = Currency.from_json('000');
|
||||
const IOU_SUFFIX_ISSUER = UInt160.from_json('rrrrrrrrrrrrrrrrrrrrrhoLvTp');
|
||||
const OrderBookUtils = {};
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
assert(!_.isNull(number) && !isNaN(number), message);
|
||||
}
|
||||
|
||||
function createAmount(number) {
|
||||
const amount = new Amount();
|
||||
amount.set_issuer(IOU_SUFFIX_ISSUER);
|
||||
amount._currency = IOU_SUFFIX_CURRENCY;
|
||||
amount.parse_value(number);
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts and returns offer's taker gets funded amount as a default IOU amount
|
||||
*
|
||||
* @param {Object} offer
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferTakerGetsFunded = function(offer) {
|
||||
assertValidNumber(offer.taker_gets_funded, 'Taker gets funded is invalid');
|
||||
|
||||
return createAmount(offer.taker_gets_funded);
|
||||
};
|
||||
|
||||
/**
|
||||
* Casts and returns offer's taker pays funded amount as a default IOU amount
|
||||
*
|
||||
* @param {Object} offer
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferTakerPaysFunded = function(offer) {
|
||||
assertValidNumber(offer.taker_pays_funded, 'Taker gets funded is invalid');
|
||||
|
||||
return createAmount(offer.taker_pays_funded);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get offer taker gets amount
|
||||
*
|
||||
* @param {Object} offer
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferTakerGets = function(offer) {
|
||||
assert(typeof offer, 'object', 'Offer is invalid');
|
||||
|
||||
return createAmount(offer.TakerGets);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve offer quality
|
||||
*
|
||||
* @param {Object} offer
|
||||
* @param {Currency} currencyGets
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferQuality = function(offer, currencyGets) {
|
||||
let amount;
|
||||
|
||||
if (currencyGets.has_interest()) {
|
||||
// XXX Should use Amount#from_quality
|
||||
amount = Amount.from_json(
|
||||
offer.TakerPays
|
||||
).ratio_human(offer.TakerGets, {
|
||||
reference_date: new Date()
|
||||
});
|
||||
} else {
|
||||
amount = createAmount(offer.quality);
|
||||
}
|
||||
|
||||
return amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats an offer quality amount to a hex that can be parsed by
|
||||
* Amount.parse_quality
|
||||
*
|
||||
* @param {Amount} quality
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
OrderBookUtils.convertOfferQualityToHex = function(quality) {
|
||||
assert(quality instanceof Amount, 'Quality is not an amount');
|
||||
|
||||
const so = new SerializedObject();
|
||||
Types.Quality.serialize(so, createAmount(quality.to_text()));
|
||||
|
||||
return so.to_hex();
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats an offer quality amount to a hex that can be parsed by
|
||||
* Amount.parse_quality
|
||||
*
|
||||
* @param {String} quality
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
OrderBookUtils.convertOfferQualityToHexFromText = function(quality) {
|
||||
|
||||
const so = new SerializedObject();
|
||||
Types.Quality.serialize(so, createAmount(quality));
|
||||
|
||||
return so.to_hex();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
OrderBookUtils.normalizeAmount = function(value) {
|
||||
return createAmount(value);
|
||||
};
|
||||
|
||||
module.exports = OrderBookUtils;
|
||||
85
src/pathfind.js
Normal file
85
src/pathfind.js
Normal file
@@ -0,0 +1,85 @@
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
var Amount = require('./amount').Amount;
|
||||
var extend = require('extend');
|
||||
|
||||
/**
|
||||
* Represents a persistent path finding request.
|
||||
*
|
||||
* Only one path find request is allowed per connection, so when another path
|
||||
* find request is triggered it will supercede the existing one, making it emit
|
||||
* the 'end' and 'superceded' events.
|
||||
*/
|
||||
function PathFind(remote, src_account, dst_account, dst_amount, src_currencies) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.remote = remote;
|
||||
|
||||
this.src_account = src_account;
|
||||
this.dst_account = dst_account;
|
||||
this.dst_amount = dst_amount;
|
||||
this.src_currencies = src_currencies;
|
||||
};
|
||||
|
||||
util.inherits(PathFind, EventEmitter);
|
||||
|
||||
/**
|
||||
* Submits a path_find_create request to the network.
|
||||
*
|
||||
* This starts a path find request, superceding all previous path finds.
|
||||
*
|
||||
* This will be called automatically by Remote when this object is instantiated,
|
||||
* so you should only have to call it if the path find was closed or superceded
|
||||
* and you wish to restart it.
|
||||
*/
|
||||
PathFind.prototype.create = function () {
|
||||
var self = this;
|
||||
|
||||
var req = this.remote.request_path_find_create(
|
||||
this.src_account,
|
||||
this.dst_account,
|
||||
this.dst_amount,
|
||||
this.src_currencies);
|
||||
|
||||
req.once('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
req.once('success', function(msg) {
|
||||
self.notify_update(msg);
|
||||
});
|
||||
|
||||
// XXX We should add ourselves to prepare_subscribe or a similar mechanism so
|
||||
// that we can resubscribe after a reconnection.
|
||||
|
||||
req.broadcast().request();
|
||||
};
|
||||
|
||||
PathFind.prototype.close = function () {
|
||||
this.remote.request_path_find_close().broadcast().request();
|
||||
this.emit('end');
|
||||
this.emit('close');
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Only pass the event along if this path find response matches what we were
|
||||
// looking for.
|
||||
if (this.src_account === src_account &&
|
||||
this.dst_account === dst_account &&
|
||||
this.dst_amount.equals(dst_amount)) {
|
||||
this.emit('update', message);
|
||||
}
|
||||
};
|
||||
|
||||
PathFind.prototype.notify_superceded = function () {
|
||||
// XXX If we're set to re-subscribe whenever we connect to a new server, then
|
||||
// we should cancel that behavior here. See PathFind#create.
|
||||
|
||||
this.emit('end');
|
||||
this.emit('superceded');
|
||||
};
|
||||
|
||||
exports.PathFind = PathFind;
|
||||
67
src/rangeset.js
Normal file
67
src/rangeset.js
Normal file
@@ -0,0 +1,67 @@
|
||||
var assert = require('assert');
|
||||
var lodash = require('lodash');
|
||||
|
||||
function RangeSet() {
|
||||
this._ranges = [ ];
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a ledger range
|
||||
*
|
||||
* @param {Number|String} range string (n-n2,n3-n4)
|
||||
*/
|
||||
|
||||
RangeSet.prototype.add = function(range) {
|
||||
assert(typeof range !== 'number' || !isNaN(range), 'Ledger range malformed');
|
||||
|
||||
range = String(range).split(',');
|
||||
|
||||
if (range.length > 1) {
|
||||
return range.forEach(this.add, this);
|
||||
}
|
||||
|
||||
range = range[0].split('-').map(Number);
|
||||
|
||||
var lRange = {
|
||||
start: range[0],
|
||||
end: range[range.length === 1 ? 0 : 1]
|
||||
};
|
||||
|
||||
// Comparisons on NaN should be falsy
|
||||
assert(lRange.start <= lRange.end, 'Ledger range malformed');
|
||||
|
||||
var insertionPoint = lodash.sortedIndex(this._ranges, lRange, function(r) {
|
||||
return r.start;
|
||||
});
|
||||
|
||||
this._ranges.splice(insertionPoint, 0, lRange);
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Check presence of ledger in range
|
||||
*
|
||||
* @param {Number|String} ledger
|
||||
* @return Boolean
|
||||
*/
|
||||
|
||||
RangeSet.prototype.has =
|
||||
RangeSet.prototype.contains = function(ledger) {
|
||||
assert(ledger != null && !isNaN(ledger), 'Ledger must be a number');
|
||||
|
||||
ledger = Number(ledger);
|
||||
|
||||
return this._ranges.some(function(r) {
|
||||
return ledger >= r.start && ledger <= r.end;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset ledger ranges
|
||||
*/
|
||||
|
||||
RangeSet.prototype.reset = function() {
|
||||
this._ranges = [ ];
|
||||
};
|
||||
|
||||
exports.RangeSet = RangeSet;
|
||||
2614
src/remote.js
Normal file
2614
src/remote.js
Normal file
File diff suppressed because it is too large
Load Diff
555
src/request.js
Normal file
555
src/request.js
Normal file
@@ -0,0 +1,555 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const util = require('util');
|
||||
const async = require('async');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Currency = require('./currency').Currency;
|
||||
const RippleError = require('./rippleerror').RippleError;
|
||||
|
||||
// Request events emitted:
|
||||
// 'success' : Request successful.
|
||||
// 'error' : Request failed.
|
||||
// 'remoteError'
|
||||
// 'remoteUnexpected'
|
||||
// 'remoteDisconnected'
|
||||
|
||||
/**
|
||||
* Request
|
||||
*
|
||||
* @param {Remote} remote
|
||||
* @param {String} command
|
||||
*/
|
||||
|
||||
function Request(remote, command) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.remote = remote;
|
||||
this.requested = false;
|
||||
this.reconnectTimeout = 1000 * 3;
|
||||
this.successEvent = 'success';
|
||||
this.errorEvent = 'error';
|
||||
this.message = {
|
||||
command: command,
|
||||
id: undefined
|
||||
};
|
||||
}
|
||||
|
||||
util.inherits(Request, EventEmitter);
|
||||
|
||||
// Send the request to a remote.
|
||||
Request.prototype.request = function(servers, callback) {
|
||||
this.emit('before');
|
||||
this.callback(callback);
|
||||
|
||||
if (this.requested) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.requested = true;
|
||||
this.on('error', function() {});
|
||||
this.emit('request', this.remote);
|
||||
|
||||
if (Array.isArray(servers)) {
|
||||
servers.forEach(function(server) {
|
||||
this.setServer(server);
|
||||
this.remote.request(this);
|
||||
}, this);
|
||||
} else {
|
||||
this.remote.request(this);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcast request to all servers, filter responses if a function is
|
||||
* provided. Return first response that satisfies the filter. Pre-filter
|
||||
* requests by ledger_index (if a ledger_index is set on the request), and
|
||||
* automatically retry servers when they reconnect--if they are expected to
|
||||
*
|
||||
* Whew
|
||||
*
|
||||
* @param [Function] fn
|
||||
*/
|
||||
|
||||
Request.prototype.filter =
|
||||
Request.prototype.addFilter =
|
||||
Request.prototype.broadcast = function(filterFn=Boolean) {
|
||||
const self = this;
|
||||
|
||||
if (!this.requested) {
|
||||
// Defer until requested, and prevent the normal request() from executing
|
||||
this.once('before', function() {
|
||||
self.requested = true;
|
||||
self.broadcast(filterFn);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
let lastResponse = new Error('No servers available');
|
||||
let connectTimeouts = { };
|
||||
const emit = this.emit;
|
||||
|
||||
this.emit = function(event, a, b) {
|
||||
// Proxy success/error events
|
||||
switch (event) {
|
||||
case 'success':
|
||||
case 'error':
|
||||
emit.call(self, 'proposed', a, b);
|
||||
break;
|
||||
default:
|
||||
emit.apply(self, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
function iterator(server, callback) {
|
||||
// Iterator is called in parallel
|
||||
|
||||
if (server.isConnected()) {
|
||||
// Listen for proxied success/error event and apply filter
|
||||
self.once('proposed', function(res) {
|
||||
lastResponse = res;
|
||||
callback(filterFn(res));
|
||||
});
|
||||
|
||||
return server._request(self);
|
||||
}
|
||||
|
||||
// Server is disconnected but should reconnect. Wait for it to reconnect,
|
||||
// and abort after a timeout
|
||||
const serverID = server.getServerID();
|
||||
|
||||
function serverReconnected() {
|
||||
clearTimeout(connectTimeouts[serverID]);
|
||||
connectTimeouts[serverID] = null;
|
||||
iterator(server, callback);
|
||||
}
|
||||
|
||||
connectTimeouts[serverID] = setTimeout(function() {
|
||||
server.removeListener('connect', serverReconnected);
|
||||
callback(false);
|
||||
}, self.reconnectTimeout);
|
||||
|
||||
server.once('connect', serverReconnected);
|
||||
}
|
||||
|
||||
function complete(success) {
|
||||
// Emit success if the filter is satisfied by any server
|
||||
// Emit error if the filter is not satisfied by any server
|
||||
// Include the last response
|
||||
emit.call(self, success ? 'success' : 'error', lastResponse);
|
||||
}
|
||||
|
||||
const servers = this.remote._servers.filter(function(server) {
|
||||
// Pre-filter servers that are disconnected and should not reconnect
|
||||
return (server.isConnected() || server._shouldConnect)
|
||||
// Pre-filter servers that do not contain the ledger in request
|
||||
&& (!self.message.hasOwnProperty('ledger_index')
|
||||
|| server.hasLedger(self.message.ledger_index))
|
||||
&& (!self.message.hasOwnProperty('ledger_index_min')
|
||||
|| self.message.ledger_index_min === -1
|
||||
|| server.hasLedger(self.message.ledger_index_min))
|
||||
&& (!self.message.hasOwnProperty('ledger_index_max')
|
||||
|| self.message.ledger_index_max === -1
|
||||
|| server.hasLedger(self.message.ledger_index_max));
|
||||
});
|
||||
|
||||
// Apply iterator in parallel to connected servers, complete when the
|
||||
// supplied filter function is satisfied once by a server's response
|
||||
async.some(servers, iterator, complete);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.cancel = function() {
|
||||
this.removeAllListeners();
|
||||
this.on('error', function() {});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setCallback = function(fn) {
|
||||
if (typeof fn === 'function') {
|
||||
this.callback(fn);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setReconnectTimeout = function(timeout) {
|
||||
if (typeof timeout === 'number' && !isNaN(timeout)) {
|
||||
this.reconnectTimeout = timeout;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.callback = function(callback, successEvent, errorEvent) {
|
||||
const self = this;
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (typeof successEvent === 'string') {
|
||||
this.successEvent = successEvent;
|
||||
}
|
||||
if (typeof errorEvent === 'string') {
|
||||
this.errorEvent = errorEvent;
|
||||
}
|
||||
|
||||
let called = false;
|
||||
|
||||
function requestSuccess(message) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
callback.call(self, null, message);
|
||||
}
|
||||
}
|
||||
|
||||
function requestError(error) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
|
||||
if (!(error instanceof RippleError)) {
|
||||
callback.call(self, new RippleError(error));
|
||||
} else {
|
||||
callback.call(self, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.once(this.successEvent, requestSuccess);
|
||||
this.once(this.errorEvent, requestError);
|
||||
this.request();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.timeout = function(duration, callback) {
|
||||
const self = this;
|
||||
|
||||
function requested() {
|
||||
self.timeout(duration, callback);
|
||||
}
|
||||
|
||||
if (!this.requested) {
|
||||
// Defer until requested
|
||||
return this.once('request', requested);
|
||||
}
|
||||
|
||||
const emit = this.emit;
|
||||
let timed_out = false;
|
||||
|
||||
const timeout = setTimeout(function() {
|
||||
timed_out = true;
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
|
||||
emit.call(self, 'timeout');
|
||||
self.cancel();
|
||||
}, duration);
|
||||
|
||||
this.emit = function() {
|
||||
if (!timed_out) {
|
||||
clearTimeout(timeout);
|
||||
emit.apply(self, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setServer = function(server) {
|
||||
let selected = null;
|
||||
|
||||
switch (typeof server) {
|
||||
case 'object':
|
||||
selected = server;
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
// Find server by URL
|
||||
const servers = this.remote._servers;
|
||||
|
||||
for (let i = 0, s; (s = servers[i]); i++) {
|
||||
if (s._url === 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set either ledger_index or ledger_hash based on heuristic
|
||||
*
|
||||
* @param {Number|String} ledger identifier
|
||||
*/
|
||||
|
||||
Request.prototype.selectLedger =
|
||||
Request.prototype.ledgerSelect = function(ledger) {
|
||||
switch (ledger) {
|
||||
case 'current':
|
||||
case 'closed':
|
||||
case 'validated':
|
||||
this.message.ledger_index = ledger;
|
||||
break;
|
||||
default:
|
||||
if (Number(ledger) && isFinite(Number(ledger))) {
|
||||
this.message.ledger_index = Number(ledger);
|
||||
} else if (/^[A-F0-9]{64}$/.test(ledger)) {
|
||||
this.message.ledger_hash = ledger;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.accountRoot = function(account) {
|
||||
this.message.account_root = UInt160.json_rewrite(account);
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.index = function(index) {
|
||||
this.message.index = index;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Provide the information id an offer.
|
||||
// --> account
|
||||
// --> seq : sequence number of transaction creating offer (integer)
|
||||
Request.prototype.offerId = function(account, sequence) {
|
||||
this.message.offer = {
|
||||
account: 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.setAccounts =
|
||||
Request.prototype.accounts = function(accountsIn, proposed) {
|
||||
const accounts = Array.isArray(accountsIn) ? accountsIn : [accountsIn];
|
||||
|
||||
// Process accounts parameters
|
||||
const 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) {
|
||||
if (Array.isArray(account)) {
|
||||
account.forEach(this.addAccount, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
const processedAccount = UInt160.json_rewrite(account);
|
||||
const prop = proposed === true ? 'accounts_proposed' : 'accounts';
|
||||
this.message[prop] = (this.message[prop] || []).concat(processedAccount);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setAccountsProposed =
|
||||
Request.prototype.rtAccounts =
|
||||
Request.prototype.accountsProposed = function(accounts) {
|
||||
return this.accounts(accounts, true);
|
||||
};
|
||||
|
||||
Request.prototype.addAccountProposed = function(account) {
|
||||
if (Array.isArray(account)) {
|
||||
account.forEach(this.addAccountProposed, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.addAccount(account, true);
|
||||
};
|
||||
|
||||
Request.prototype.setBooks =
|
||||
Request.prototype.books = function(books, snapshot) {
|
||||
// Reset list of books (this method overwrites the current list)
|
||||
this.message.books = [];
|
||||
|
||||
for (let i = 0, l = books.length; i < l; i++) {
|
||||
const book = books[i];
|
||||
this.addBook(book, snapshot);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addBook = function(book, snapshot) {
|
||||
if (Array.isArray(book)) {
|
||||
book.forEach(this.addBook, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
const json = { };
|
||||
|
||||
function processSide(side) {
|
||||
if (!book[side]) {
|
||||
throw new Error('Missing ' + side);
|
||||
}
|
||||
|
||||
const obj = json[side] = {
|
||||
currency: Currency.json_rewrite(book[side].currency, {
|
||||
force_hex: true
|
||||
})
|
||||
};
|
||||
|
||||
if (!Currency.from_json(obj.currency).is_native()) {
|
||||
obj.issuer = UInt160.json_rewrite(book[side].issuer);
|
||||
}
|
||||
}
|
||||
|
||||
['taker_gets', 'taker_pays'].forEach(processSide);
|
||||
|
||||
if (typeof snapshot !== 'boolean') {
|
||||
json.snapshot = true;
|
||||
} else if (snapshot) {
|
||||
json.snapshot = true;
|
||||
} else {
|
||||
delete json.snapshot;
|
||||
}
|
||||
|
||||
if (book.both) {
|
||||
json.both = true;
|
||||
}
|
||||
|
||||
this.message.books = (this.message.books || []).concat(json);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addStream = function(stream, values) {
|
||||
if (Array.isArray(values)) {
|
||||
switch (stream) {
|
||||
case 'accounts':
|
||||
this.addAccount(values);
|
||||
break;
|
||||
case 'accounts_proposed':
|
||||
this.addAccountProposed(values);
|
||||
break;
|
||||
case 'books':
|
||||
this.addBook(values);
|
||||
break;
|
||||
}
|
||||
} else if (arguments.length > 1) {
|
||||
for (let arg in arguments) {
|
||||
this.addStream(arguments[arg]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.message.streams)) {
|
||||
this.message.streams = [];
|
||||
}
|
||||
|
||||
if (this.message.streams.indexOf(stream) === -1) {
|
||||
this.message.streams.push(stream);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
exports.Request = Request;
|
||||
33
src/rippleerror.js
Normal file
33
src/rippleerror.js
Normal file
@@ -0,0 +1,33 @@
|
||||
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;
|
||||
160
src/seed.js
Normal file
160
src/seed.js
Normal file
@@ -0,0 +1,160 @@
|
||||
'use strict';
|
||||
|
||||
//
|
||||
// Seed support
|
||||
//
|
||||
|
||||
const extend = require('extend');
|
||||
const utils = require('./utils');
|
||||
const sjcl = utils.sjcl;
|
||||
|
||||
const Base = require('./base').Base;
|
||||
const UInt = require('./uint').UInt;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const KeyPair = require('./keypair').KeyPair;
|
||||
|
||||
const Seed = extend(function() {
|
||||
this._curve = sjcl.ecc.curves.k256;
|
||||
this._value = NaN;
|
||||
}, UInt);
|
||||
|
||||
Seed.width = 16;
|
||||
Seed.prototype = Object.create(extend({}, UInt.prototype));
|
||||
Seed.prototype.constructor = Seed;
|
||||
|
||||
// value = NaN on error.
|
||||
// One day this will support rfc1751 too.
|
||||
Seed.prototype.parse_json = function(j) {
|
||||
if (typeof j === 'string') {
|
||||
if (!j.length) {
|
||||
this._value = NaN;
|
||||
// XXX Should actually always try and continue if it failed.
|
||||
} else if (j[0] === 's') {
|
||||
this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
|
||||
} else if (/^[0-9a-fA-f]{32}$/.test(j)) {
|
||||
this.parse_hex(j);
|
||||
// XXX Should also try 1751
|
||||
} else {
|
||||
this.parse_passphrase(j);
|
||||
}
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Seed.prototype.parse_passphrase = function(j) {
|
||||
if (typeof j !== 'string') {
|
||||
throw new Error('Passphrase must be a string');
|
||||
}
|
||||
|
||||
const hash = sjcl.hash.sha512.hash(sjcl.codec.utf8String.toBits(j));
|
||||
const bits = sjcl.bitArray.bitSlice(hash, 0, 128);
|
||||
|
||||
this.parse_bits(bits);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Seed.prototype.to_json = function() {
|
||||
if (!(this.is_valid())) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// Removed a `*` so this JSDoc-ish syntax is ignored.
|
||||
// This will soon all change anyway.
|
||||
/*
|
||||
* @param account
|
||||
* {undefined} take first, default, KeyPair
|
||||
*
|
||||
* {Number} specifies the account number of the KeyPair
|
||||
* desired.
|
||||
*
|
||||
* {Uint160} (from_json able), specifies the address matching the KeyPair
|
||||
* that is desired.
|
||||
*
|
||||
* @param maxLoops (optional)
|
||||
* {Number} specifies the amount of attempts taken
|
||||
* to generate a matching KeyPair
|
||||
*
|
||||
*/
|
||||
Seed.prototype.get_key = function(account, maxLoops) {
|
||||
let account_number = 0, address;
|
||||
let max_loops = maxLoops || 1;
|
||||
|
||||
if (!this.is_valid()) {
|
||||
throw new Error('Cannot generate keys from invalid seed!');
|
||||
}
|
||||
if (account) {
|
||||
if (typeof account === 'number') {
|
||||
account_number = account;
|
||||
max_loops = account_number + 1;
|
||||
} else {
|
||||
address = UInt160.from_json(account);
|
||||
}
|
||||
}
|
||||
|
||||
let private_gen, public_gen;
|
||||
const curve = this._curve;
|
||||
let i = 0;
|
||||
|
||||
do {
|
||||
private_gen = sjcl.bn.fromBits(
|
||||
firstHalfOfSHA512(append_int(this.to_bytes(), i)));
|
||||
i++;
|
||||
} while (!curve.r.greaterEquals(private_gen));
|
||||
|
||||
public_gen = curve.G.mult(private_gen);
|
||||
|
||||
let sec;
|
||||
let key_pair;
|
||||
|
||||
do {
|
||||
|
||||
i = 0;
|
||||
|
||||
do {
|
||||
sec = sjcl.bn.fromBits(
|
||||
firstHalfOfSHA512(
|
||||
append_int(
|
||||
append_int(public_gen.toBytesCompressed(), account_number)
|
||||
,
|
||||
i
|
||||
)));
|
||||
i++;
|
||||
} while (!curve.r.greaterEquals(sec));
|
||||
|
||||
account_number++;
|
||||
sec = sec.add(private_gen).mod(curve.r);
|
||||
key_pair = KeyPair.from_bn_secret(sec);
|
||||
|
||||
if (max_loops-- <= 0) {
|
||||
// We are almost certainly looking for an account that would take same
|
||||
// value of $too_long {forever, ...}
|
||||
throw new Error('Too many loops looking for KeyPair yielding ' +
|
||||
address.to_json() + ' from ' + this.to_json());
|
||||
}
|
||||
|
||||
} while (address && !key_pair.get_address().equals(address));
|
||||
|
||||
return key_pair;
|
||||
};
|
||||
|
||||
exports.Seed = Seed;
|
||||
375
src/serializedobject.js
Normal file
375
src/serializedobject.js
Normal file
@@ -0,0 +1,375 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const extend = require('extend');
|
||||
const binformat = require('./binformat');
|
||||
const stypes = require('./serializedtypes');
|
||||
const utils = require('./utils');
|
||||
const UInt256 = require('./uint256').UInt256;
|
||||
|
||||
const sjcl = utils.sjcl;
|
||||
|
||||
const TRANSACTION_TYPES = { };
|
||||
|
||||
Object.keys(binformat.tx).forEach(function(key) {
|
||||
TRANSACTION_TYPES[binformat.tx[key][0]] = key;
|
||||
});
|
||||
|
||||
const LEDGER_ENTRY_TYPES = {};
|
||||
|
||||
Object.keys(binformat.ledger).forEach(function(key) {
|
||||
LEDGER_ENTRY_TYPES[binformat.ledger[key][0]] = key;
|
||||
});
|
||||
|
||||
const TRANSACTION_RESULTS = {};
|
||||
|
||||
Object.keys(binformat.ter).forEach(function(key) {
|
||||
TRANSACTION_RESULTS[binformat.ter[key]] = key;
|
||||
});
|
||||
|
||||
function normalize_sjcl_bn_hex(string) {
|
||||
const hex = string.slice(2); // remove '0x' prefix
|
||||
// now strip leading zeros
|
||||
const i = _.findIndex(hex, function(c) {
|
||||
return c !== '0';
|
||||
});
|
||||
return i >= 0 ? hex.slice(i) : '0';
|
||||
}
|
||||
|
||||
function SerializedObject(buf) {
|
||||
if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf))) {
|
||||
this.buffer = 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.');
|
||||
}
|
||||
this.pointer = 0;
|
||||
}
|
||||
|
||||
SerializedObject.from_json = function(obj_) {
|
||||
// Create a copy of the object so we don't modify it
|
||||
const obj = extend(true, {}, obj_);
|
||||
|
||||
let so = new SerializedObject();
|
||||
let typedef;
|
||||
|
||||
if (typeof obj.TransactionType === 'number') {
|
||||
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
if (!obj.TransactionType) {
|
||||
throw new Error('Transaction type ID is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj.LedgerEntryType === 'number') {
|
||||
obj.LedgerEntryType = SerializedObject.lookup_type_le(obj.LedgerEntryType);
|
||||
|
||||
if (!obj.LedgerEntryType) {
|
||||
throw new Error('LedgerEntryType ID is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj.TransactionType === 'string') {
|
||||
typedef = binformat.tx[obj.TransactionType];
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('Transaction type is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.TransactionType = typedef.shift();
|
||||
} else if (typeof obj.LedgerEntryType === 'string') {
|
||||
typedef = binformat.ledger[obj.LedgerEntryType];
|
||||
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('LedgerEntryType is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.LedgerEntryType = typedef.shift();
|
||||
|
||||
} else if (typeof obj.AffectedNodes === 'object') {
|
||||
typedef = binformat.metadata;
|
||||
} else {
|
||||
throw new Error('Object to be serialized must contain either' +
|
||||
' TransactionType, LedgerEntryType or AffectedNodes.');
|
||||
}
|
||||
|
||||
// ND: This from_*json* seems a reasonable place to put validation of `json`
|
||||
SerializedObject.check_fields(typedef, obj);
|
||||
so.serialize(typedef, obj);
|
||||
|
||||
return so;
|
||||
};
|
||||
|
||||
SerializedObject.check_fields = function(typedef, obj) {
|
||||
const missingFields = [];
|
||||
const unknownFields = [];
|
||||
const fieldsMap = {};
|
||||
|
||||
// Get missing required fields
|
||||
typedef.forEach(function(field) {
|
||||
const fieldName = field[0];
|
||||
const isRequired = field[1] === binformat.REQUIRED;
|
||||
|
||||
if (isRequired && obj[fieldName] === undefined) {
|
||||
missingFields.push(fieldName);
|
||||
} else {
|
||||
fieldsMap[fieldName] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Get fields that are not specified in format
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
if (!fieldsMap[key] && /^[A-Z]/.test(key)) {
|
||||
unknownFields.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
if (!(missingFields.length || unknownFields.length)) {
|
||||
// No missing or unknown fields
|
||||
return;
|
||||
}
|
||||
|
||||
let errorMessage;
|
||||
|
||||
if (obj.TransactionType !== undefined) {
|
||||
errorMessage = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
} else if (obj.LedgerEntryType !== undefined) {
|
||||
errorMessage = SerializedObject.lookup_type_le(obj.LedgerEntryType);
|
||||
} else {
|
||||
errorMessage = 'TransactionMetaData';
|
||||
}
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
errorMessage += ' is missing fields: ' + JSON.stringify(missingFields);
|
||||
}
|
||||
if (unknownFields.length > 0) {
|
||||
errorMessage += (missingFields.length ? ' and' : '')
|
||||
+ ' has unknown fields: ' + JSON.stringify(unknownFields);
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
};
|
||||
|
||||
SerializedObject.prototype.append = function(bytes_) {
|
||||
const bytes = bytes_ instanceof SerializedObject ? bytes_.buffer : bytes_;
|
||||
|
||||
// Make sure both buffer and bytes are Array. Either could be a Buffer.
|
||||
if (Array.isArray(this.buffer) && Array.isArray(bytes)) {
|
||||
// `this.buffer = this.buffer.concat(bytes)` can be unbearably slow for
|
||||
// large bytes length and acceptable bytes length is limited for
|
||||
// `Array.prototype.push.apply(this.buffer, bytes)` as every element in the
|
||||
// bytes array is pushed onto the stack, potentially causing a RangeError
|
||||
// exception. Both of these solutions are known to be problematic for
|
||||
// ledger 7501326. KISS instead
|
||||
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
this.buffer.push(bytes[i]);
|
||||
}
|
||||
} else {
|
||||
this.buffer = this.buffer.concat(bytes);
|
||||
}
|
||||
|
||||
this.pointer += bytes.length;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.resetPointer = function() {
|
||||
this.pointer = 0;
|
||||
};
|
||||
|
||||
function readOrPeek(advance) {
|
||||
return function(bytes) {
|
||||
const start = this.pointer;
|
||||
const end = start + bytes;
|
||||
|
||||
if (end > this.buffer.length) {
|
||||
throw new Error('Buffer length exceeded');
|
||||
}
|
||||
|
||||
const result = this.buffer.slice(start, end);
|
||||
|
||||
if (advance) {
|
||||
this.pointer = end;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
SerializedObject.prototype.read = readOrPeek(true);
|
||||
|
||||
SerializedObject.prototype.peek = readOrPeek(false);
|
||||
|
||||
SerializedObject.prototype.to_bits = function() {
|
||||
return sjcl.codec.bytes.toBits(this.buffer);
|
||||
};
|
||||
|
||||
SerializedObject.prototype.to_hex = function() {
|
||||
return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
|
||||
};
|
||||
|
||||
SerializedObject.prototype.to_json = function() {
|
||||
const old_pointer = this.pointer;
|
||||
|
||||
this.resetPointer();
|
||||
|
||||
let output = { };
|
||||
|
||||
while (this.pointer < this.buffer.length) {
|
||||
const key_and_value = stypes.parse(this);
|
||||
const key = key_and_value[0];
|
||||
const value = key_and_value[1];
|
||||
output[key] = SerializedObject.jsonify_structure(value, key);
|
||||
}
|
||||
|
||||
this.pointer = old_pointer;
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
SerializedObject.jsonify_structure = function(structure, field_name) {
|
||||
let 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 === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeof structure.to_json === 'function') {
|
||||
output = structure.to_json();
|
||||
} else if (structure instanceof sjcl.bn) {
|
||||
output = ('0000000000000000' +
|
||||
normalize_sjcl_bn_hex(structure.toString())
|
||||
.toUpperCase()
|
||||
).slice(-16);
|
||||
} else {
|
||||
// new Array or Object
|
||||
output = new structure.constructor();
|
||||
|
||||
let keys = Object.keys(structure);
|
||||
|
||||
for (let i = 0, l = keys.length; i < l; i++) {
|
||||
let key = keys[i];
|
||||
output[key] = SerializedObject.jsonify_structure(structure[key], key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
output = structure;
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.serialize = function(typedef, obj) {
|
||||
// Serialize object without end marker
|
||||
stypes.Object.serialize(this, obj, true);
|
||||
|
||||
// ST: Old serialization
|
||||
/*
|
||||
// Ensure canonical order
|
||||
typedef = SerializedObject.sort_typedef(typedef);
|
||||
|
||||
// Serialize fields
|
||||
for (let i=0, l=typedef.length; i<l; i++) {
|
||||
this.serialize_field(typedef[i], obj);
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
SerializedObject.prototype.hash = function(prefix) {
|
||||
let sign_buffer = new SerializedObject();
|
||||
|
||||
// Add hashing prefix
|
||||
if (typeof prefix !== 'undefined') {
|
||||
stypes.Int32.serialize(sign_buffer, prefix);
|
||||
}
|
||||
|
||||
// Copy buffer to temporary buffer
|
||||
sign_buffer.append(this.buffer);
|
||||
|
||||
const bits = sjcl.codec.bytes.toBits(sign_buffer.buffer);
|
||||
const sha512hex = sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(bits));
|
||||
|
||||
return UInt256.from_hex(sha512hex.substr(0, 64));
|
||||
};
|
||||
|
||||
// DEPRECATED
|
||||
SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash;
|
||||
|
||||
SerializedObject.prototype.serialize_field = function(spec, obj) {
|
||||
const name = spec[0];
|
||||
const presence = spec[1];
|
||||
|
||||
if (typeof obj[name] !== 'undefined') {
|
||||
try {
|
||||
stypes.serialize(this, name, obj[name]);
|
||||
} catch (e) {
|
||||
// Add field name to message and rethrow
|
||||
e.message = 'Error serializing "' + name + '": ' + e.message;
|
||||
throw e;
|
||||
}
|
||||
} else if (presence === binformat.REQUIRED) {
|
||||
throw new Error('Missing required field ' + name);
|
||||
}
|
||||
};
|
||||
|
||||
SerializedObject.get_field_header = function(type_id, field_id) {
|
||||
let buffer = [0];
|
||||
|
||||
if (type_id > 0xF) {
|
||||
buffer.push(type_id & 0xFF);
|
||||
} else {
|
||||
buffer[0] += (type_id & 0xF) << 4;
|
||||
}
|
||||
|
||||
if (field_id > 0xF) {
|
||||
buffer.push(field_id & 0xFF);
|
||||
} else {
|
||||
buffer[0] += field_id & 0xF;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
SerializedObject.sort_typedef = function(typedef) {
|
||||
assert(Array.isArray(typedef));
|
||||
|
||||
function sort_field_compare(a, b) {
|
||||
// Sort by type id first, then by field id
|
||||
return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2];
|
||||
}
|
||||
|
||||
return typedef.sort(sort_field_compare);
|
||||
};
|
||||
|
||||
SerializedObject.lookup_type_tx = function(id) {
|
||||
assert.strictEqual(typeof id, 'number');
|
||||
return TRANSACTION_TYPES[id];
|
||||
};
|
||||
|
||||
SerializedObject.lookup_type_le = function(id) {
|
||||
assert(typeof id === 'number');
|
||||
return LEDGER_ENTRY_TYPES[id];
|
||||
};
|
||||
|
||||
exports.SerializedObject = SerializedObject;
|
||||
924
src/serializedtypes.js
Normal file
924
src/serializedtypes.js
Normal file
@@ -0,0 +1,924 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Type definitions for binary format.
|
||||
*
|
||||
* This file should not be included directly. Instead, find the format you're
|
||||
* trying to parse or serialize in binformat.js and pass that to
|
||||
* SerializedObject.parse() or SerializedObject.serialize().
|
||||
*/
|
||||
|
||||
var assert = require('assert');
|
||||
var extend = require('extend');
|
||||
var GlobalBigNumber = require('bignumber.js');
|
||||
var Amount = require('./amount').Amount;
|
||||
var Currency = require('./currency').Currency;
|
||||
var binformat = require('./binformat');
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
var SJCL_BN = sjcl.bn;
|
||||
|
||||
var UInt128 = require('./uint128').UInt128;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
var BigNumber = GlobalBigNumber.another({
|
||||
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
|
||||
DECIMAL_PLACES: 40
|
||||
});
|
||||
|
||||
function SerializedType(methods) {
|
||||
extend(this, methods);
|
||||
}
|
||||
|
||||
function isNumber(val) {
|
||||
return typeof val === 'number' && isFinite(val);
|
||||
}
|
||||
|
||||
function isString(val) {
|
||||
return typeof val === 'string';
|
||||
}
|
||||
|
||||
function isHexInt64String(val) {
|
||||
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
|
||||
}
|
||||
|
||||
function serializeBits(so, bits, noLength) {
|
||||
var byteData = sjcl.codec.bytes.fromBits(bits);
|
||||
if (!noLength) {
|
||||
SerializedType.serialize_varint(so, byteData.length);
|
||||
}
|
||||
so.append(byteData);
|
||||
}
|
||||
|
||||
function serializeHex(so, hexData, noLength) {
|
||||
serializeBits(so, sjcl.codec.hex.toBits(hexData), noLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* parses bytes as hex
|
||||
*
|
||||
* @param {Array} byte_array bytes
|
||||
* @return {String} hex string
|
||||
*/
|
||||
function convertByteArrayToHex(byte_array) {
|
||||
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array))
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
function convertHexToString(hexString) {
|
||||
var bits = sjcl.codec.hex.toBits(hexString);
|
||||
return sjcl.codec.utf8String.fromBits(bits);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
SerializedType.serialize_varint = function(so, val) {
|
||||
if (val < 0) {
|
||||
throw new Error('Variable integers are unsigned.');
|
||||
}
|
||||
|
||||
if (val <= 192) {
|
||||
so.append([val]);
|
||||
} 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]);
|
||||
} else {
|
||||
throw new Error('Variable integer overflow.');
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
result = b1;
|
||||
} else if (b1 <= 240) {
|
||||
b2 = so.read(1)[0];
|
||||
result = 193 + (b1 - 193) * 256 + b2;
|
||||
} else if (b1 <= 254) {
|
||||
b2 = so.read(1)[0];
|
||||
b3 = so.read(1)[0];
|
||||
result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// In the following, we assume that the inputs are in the proper range. Is this
|
||||
// correct?
|
||||
// Helper functions for 1-, 2-, and 4-byte integers.
|
||||
|
||||
/**
|
||||
* Convert an integer value into an array of bytes.
|
||||
*
|
||||
* The result is appended to the serialized object ('so').
|
||||
*
|
||||
* @param {Number} val value
|
||||
* @param {Number} bytes byte size
|
||||
* @return {Array} byte array
|
||||
*/
|
||||
function convertIntegerToByteArray(val, bytes) {
|
||||
if (!isNumber(val)) {
|
||||
throw new Error('Value is not a number', bytes);
|
||||
}
|
||||
|
||||
if (val < 0 || val >= Math.pow(256, bytes)) {
|
||||
throw new Error('Value out of bounds ');
|
||||
}
|
||||
|
||||
var newBytes = [ ];
|
||||
|
||||
for (var i = 0; i < bytes; i++) {
|
||||
newBytes.unshift(val >>> (i * 8) & 0xff);
|
||||
}
|
||||
|
||||
return newBytes;
|
||||
}
|
||||
|
||||
// Convert a certain number of bytes from the serialized object ('so') into an
|
||||
// integer.
|
||||
function readAndSum(so, bytes) {
|
||||
var sum = 0;
|
||||
|
||||
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) {
|
||||
so.append(convertIntegerToByteArray(val, 1));
|
||||
},
|
||||
parse: function(so) {
|
||||
return readAndSum(so, 1);
|
||||
}
|
||||
});
|
||||
|
||||
STInt8.id = 16;
|
||||
|
||||
function serialize(so, field_name, value) {
|
||||
// so: a byte-stream to serialize into.
|
||||
// field_name: a string for the field name ('LedgerEntryType' etc.)
|
||||
// value: the value of that field.
|
||||
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' && typeof value === 'string') {
|
||||
value = binformat.ledger[value][0];
|
||||
}
|
||||
|
||||
if (field_name === 'TransactionResult' && typeof value === 'string') {
|
||||
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...)
|
||||
var serialized_object_type;
|
||||
|
||||
if (field_name === 'Memo' && typeof value === 'object') {
|
||||
// for Memo we override the default behavior with our STMemo serializer
|
||||
serialized_object_type = exports.STMemo;
|
||||
} else {
|
||||
// for a field based on the type bits.
|
||||
serialized_object_type = exports[binformat.types[type_bits]];
|
||||
}
|
||||
|
||||
try {
|
||||
serialized_object_type.serialize(so, value);
|
||||
} catch (e) {
|
||||
e.message += ' (' + field_name + ')';
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
exports.serialize = exports.serialize_whatever = serialize;
|
||||
|
||||
// Take the serialized object, figure out what type/field it is, and return the
|
||||
// parsing of that.
|
||||
|
||||
function parse(so) {
|
||||
var tag_byte = so.read(1)[0];
|
||||
var type_bits = tag_byte >> 4;
|
||||
|
||||
if (type_bits === 0) {
|
||||
type_bits = so.read(1)[0];
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// Get the parser class (ST...) for a field based on the type bits.
|
||||
var type = (field_name === 'Memo')
|
||||
? exports.STMemo
|
||||
: exports[binformat.types[type_bits]];
|
||||
|
||||
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
return [field_name, type.parse(so)]; // key, value
|
||||
}
|
||||
|
||||
exports.parse = exports.parse_whatever = parse;
|
||||
|
||||
var STInt16 = exports.Int16 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
so.append(convertIntegerToByteArray(val, 2));
|
||||
},
|
||||
parse: function(so) {
|
||||
return readAndSum(so, 2);
|
||||
}
|
||||
});
|
||||
|
||||
STInt16.id = 1;
|
||||
|
||||
var STInt32 = exports.Int32 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
so.append(convertIntegerToByteArray(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 (isNumber(val)) {
|
||||
val = Math.floor(val);
|
||||
if (val < 0) {
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = new SJCL_BN(val, 10);
|
||||
} else if (isString(val)) {
|
||||
if (!isHexInt64String(val)) {
|
||||
throw new Error('Not a valid hex Int64.');
|
||||
}
|
||||
bigNumObject = new SJCL_BN(val, 16);
|
||||
} else if (val instanceof SJCL_BN) {
|
||||
if (!val.greaterEquals(0)) {
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = val;
|
||||
} else {
|
||||
throw new Error('Invalid type for Int64');
|
||||
}
|
||||
serializeBits(so, bigNumObject.toBits(64), true); // noLength = true
|
||||
},
|
||||
parse: function(so) {
|
||||
var bytes = so.read(8);
|
||||
return SJCL_BN.fromBits(sjcl.codec.bytes.toBits(bytes));
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
serializeBits(so, hash.to_bits(), true); // noLength = true
|
||||
},
|
||||
parse: function(so) {
|
||||
return UInt128.from_bytes(so.read(16));
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
serializeBits(so, hash.to_bits(), true); // noLength = true
|
||||
},
|
||||
parse: function(so) {
|
||||
return UInt256.from_bytes(so.read(32));
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
}
|
||||
serializeBits(so, hash.to_bits(), true); // noLength = true
|
||||
},
|
||||
parse: function(so) {
|
||||
return UInt160.from_bytes(so.read(20));
|
||||
}
|
||||
});
|
||||
|
||||
STHash160.id = 17;
|
||||
|
||||
// Internal
|
||||
var STCurrency = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var currencyData = val.to_bytes();
|
||||
|
||||
if (!currencyData) {
|
||||
throw new Error(
|
||||
'Tried to serialize invalid/unimplemented currency type.');
|
||||
}
|
||||
|
||||
so.append(currencyData);
|
||||
},
|
||||
parse: function(so) {
|
||||
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: '+convertByteArrayToHex(bytes));
|
||||
// }
|
||||
return currency;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Quality is encoded into 64 bits:
|
||||
* (8 bits offset) (56 bits mantissa)
|
||||
*
|
||||
* Quality differs from Amount because it does not need the first two bits
|
||||
* to represent non-native and non-negative
|
||||
*/
|
||||
exports.Quality = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
var amount = Amount.from_json(val);
|
||||
|
||||
if (!amount.is_valid()) {
|
||||
throw new Error('Not a valid Amount object.');
|
||||
}
|
||||
|
||||
var hi = 0, lo = 0;
|
||||
var value = new BigNumber(amount.to_text());
|
||||
var offset = value.e - 15;
|
||||
|
||||
if (!amount.is_zero()) {
|
||||
// First eight bits: offset/exponent
|
||||
hi |= ((100 + offset) & 0xff) << 24;
|
||||
|
||||
// Remaining 56 bits: mantissa
|
||||
var mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
||||
var mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
||||
assert(mantissaHex.length <= 16,
|
||||
'Mantissa hex representation ' + mantissaHex +
|
||||
' exceeds the maximum length of 16');
|
||||
hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0xffffff;
|
||||
lo = parseInt(mantissaHex.slice(-8), 16);
|
||||
}
|
||||
|
||||
var valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
|
||||
|
||||
so.append(valueBytes);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Amount is encoded into 64 bits:
|
||||
* (1 bit non-native) (1 bit non-negative) (8 bits offset) (54 bits mantissa)
|
||||
*/
|
||||
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.');
|
||||
}
|
||||
|
||||
var value = new BigNumber(amount.to_text());
|
||||
var offset = value.e - 15;
|
||||
|
||||
// Amount (64-bit integer)
|
||||
var valueBytes = utils.arraySet(8, 0);
|
||||
|
||||
if (amount.is_native()) {
|
||||
var valueHex = value.abs().toString(16);
|
||||
|
||||
if (Amount.strict_mode && value.abs().greaterThan(Amount.bi_xns_max)) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
|
||||
// Enforce correct length (64 bits)
|
||||
if (Amount.strict_mode && valueHex.length > 16) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
|
||||
while (valueHex.length < 16) {
|
||||
valueHex = '0' + valueHex;
|
||||
}
|
||||
|
||||
valueBytes = sjcl.codec.bytes.fromBits(sjcl.codec.hex.toBits(valueHex));
|
||||
// Clear most significant two bits - these bits should already be 0 if
|
||||
// Amount enforces the range correctly, but we'll clear them anyway just
|
||||
// so this code can make certain guarantees about the encoded value.
|
||||
valueBytes[0] &= 0x3f;
|
||||
|
||||
if (!amount.is_negative()) {
|
||||
valueBytes[0] |= 0x40;
|
||||
}
|
||||
} else {
|
||||
var hi = 0, lo = 0;
|
||||
|
||||
// First bit: non-native
|
||||
hi |= 1 << 31;
|
||||
|
||||
if (!amount.is_zero()) {
|
||||
// Second bit: non-negative?
|
||||
if (!amount.is_negative()) {
|
||||
hi |= 1 << 30;
|
||||
}
|
||||
|
||||
// Next eight bits: offset/exponent
|
||||
hi |= ((97 + offset) & 0xff) << 22;
|
||||
|
||||
// Remaining 54 bits: mantissa
|
||||
var mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
||||
var mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
||||
assert(mantissaHex.length <= 16,
|
||||
'Mantissa hex representation ' + mantissaHex +
|
||||
' exceeds the maximum length of 16');
|
||||
hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0x3fffff;
|
||||
lo = parseInt(mantissaHex.slice(-8), 16);
|
||||
}
|
||||
|
||||
valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
|
||||
}
|
||||
|
||||
so.append(valueBytes);
|
||||
|
||||
if (!amount.is_native()) {
|
||||
// Currency (160-bit hash)
|
||||
var currency = amount.currency();
|
||||
STCurrency.serialize(so, currency, true);
|
||||
|
||||
// Issuer (160-bit hash)
|
||||
so.append(amount.issuer().to_bytes());
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
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];
|
||||
}
|
||||
|
||||
var is_negative = !is_zero && !(value_bytes[0] & 0x40);
|
||||
|
||||
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 mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16);
|
||||
var sign = is_negative ? '-' : '';
|
||||
var valueString = sign + mantissa.toString() + 'e' + offset.toString();
|
||||
|
||||
return Amount.from_json({
|
||||
currency: currency,
|
||||
issuer: issuer.to_json(),
|
||||
value: valueString
|
||||
});
|
||||
}
|
||||
|
||||
// native
|
||||
var integer_bytes = value_bytes.slice();
|
||||
integer_bytes[0] &= 0x3f;
|
||||
var integer_hex = utils.arrayToHex(integer_bytes);
|
||||
var value = new BigNumber(integer_hex, 16);
|
||||
return Amount.from_json((is_negative ? '-' : '') + value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
STAmount.id = 6;
|
||||
|
||||
var STVL = exports.VariableLength = exports.VL = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
if (typeof val === 'string') {
|
||||
serializeHex(so, val);
|
||||
} else {
|
||||
throw new Error('Unknown datatype.');
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
var len = this.parse_varint(so);
|
||||
return convertByteArrayToHex(so.read(len));
|
||||
}
|
||||
});
|
||||
|
||||
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!');
|
||||
}
|
||||
serializeBits(so, account.to_bits());
|
||||
},
|
||||
parse: function(so) {
|
||||
var len = this.parse_varint(so);
|
||||
|
||||
if (len !== 20) {
|
||||
throw new Error('Non-standard-length account ID');
|
||||
}
|
||||
|
||||
var result = UInt160.from_bytes(so.read(len));
|
||||
result.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
if (false && !result.is_valid()) {
|
||||
throw new Error('Invalid Account');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
STAccount.id = 8;
|
||||
|
||||
var STPathSet = exports.PathSet = new SerializedType({
|
||||
typeBoundary: 0xff,
|
||||
typeEnd: 0x00,
|
||||
typeAccount: 0x01,
|
||||
typeCurrency: 0x10,
|
||||
typeIssuer: 0x20,
|
||||
serialize: function(so, val) {
|
||||
for (var i = 0, l = val.length; i < l; i++) {
|
||||
// Boundary
|
||||
if (i) {
|
||||
STInt8.serialize(so, this.typeBoundary);
|
||||
}
|
||||
|
||||
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.currency) {
|
||||
type |= this.typeCurrency;
|
||||
}
|
||||
if (entry.issuer) {
|
||||
type |= this.typeIssuer;
|
||||
}
|
||||
|
||||
STInt8.serialize(so, type);
|
||||
|
||||
if (entry.account) {
|
||||
STHash160.serialize(so, entry.account);
|
||||
}
|
||||
|
||||
if (entry.currency) {
|
||||
var currency = Currency.from_json(entry.currency, entry.non_native);
|
||||
STCurrency.serialize(so, currency);
|
||||
}
|
||||
|
||||
if (entry.issuer) {
|
||||
STHash160.serialize(so, entry.issuer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STInt8.serialize(so, this.typeEnd);
|
||||
},
|
||||
parse: function(so) {
|
||||
// should return a list of lists:
|
||||
/*
|
||||
[
|
||||
[entry, entry],
|
||||
[entry, entry, entry],
|
||||
[entry],
|
||||
[]
|
||||
]
|
||||
|
||||
each entry has one or more of the following attributes:
|
||||
amount, currency, issuer.
|
||||
*/
|
||||
|
||||
var path_list = [];
|
||||
var current_path = [];
|
||||
var tag_byte;
|
||||
|
||||
/* eslint-disable no-cond-assign */
|
||||
|
||||
while ((tag_byte = so.read(1)[0]) !== this.typeEnd) {
|
||||
// TODO: try/catch this loop, and catch when we run out of data without
|
||||
// reaching the end of the data structure.
|
||||
// Now determine: is this an end, boundary, or entry-begin-tag?
|
||||
// console.log('Tag byte:', tag_byte);
|
||||
if (tag_byte === this.typeBoundary) {
|
||||
if (current_path) { // close the current path, if there is one,
|
||||
path_list.push(current_path);
|
||||
}
|
||||
current_path = [ ]; // and start a new one.
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's an entry-begin tag.
|
||||
var entry = {};
|
||||
var type = 0;
|
||||
|
||||
if (tag_byte & this.typeAccount) {
|
||||
entry.account = STHash160.parse(so);
|
||||
entry.account.set_version(Base.VER_ACCOUNT_ID);
|
||||
type = type | this.typeAccount;
|
||||
}
|
||||
if (tag_byte & this.typeCurrency) {
|
||||
entry.currency = STCurrency.parse(so);
|
||||
if (entry.currency.to_json() === 'XRP' && !entry.currency.is_native()) {
|
||||
entry.non_native = true;
|
||||
}
|
||||
type = type | this.typeCurrency;
|
||||
}
|
||||
if (tag_byte & this.typeIssuer) {
|
||||
entry.issuer = STHash160.parse(so);
|
||||
// Enable and set correct type of base-58 encoding
|
||||
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
type = type | this.typeIssuer;
|
||||
}
|
||||
|
||||
if (entry.account || entry.currency || entry.issuer) {
|
||||
entry.type = type;
|
||||
entry.type_hex = ('000000000000000' + type.toString(16)).slice(-16);
|
||||
current_path.push(entry);
|
||||
} else {
|
||||
// It must have at least something in it.
|
||||
throw new Error('Invalid path entry');
|
||||
}
|
||||
}
|
||||
|
||||
if (current_path) {
|
||||
// close the current path, if there is one,
|
||||
path_list.push(current_path);
|
||||
}
|
||||
|
||||
return path_list;
|
||||
}
|
||||
});
|
||||
|
||||
STPathSet.id = 18;
|
||||
|
||||
var STVector256 = exports.Vector256 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
// Assume val is an array of STHash256 objects.
|
||||
SerializedType.serialize_varint(so, val.length * 32);
|
||||
for (var i = 0, l = val.length; i < l; i++) {
|
||||
STHash256.serialize(so, val[i]);
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
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;
|
||||
|
||||
// Internal
|
||||
exports.STMemo = new SerializedType({
|
||||
serialize: function(so, val, no_marker) {
|
||||
var keys = [];
|
||||
|
||||
Object.keys(val).forEach(function(key) {
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
if (key[0] === key[0].toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
||||
throw new Error('JSON contains unknown field: "' + key + '"');
|
||||
}
|
||||
|
||||
keys.push(key);
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
keys.forEach(function(key) {
|
||||
serialize(so, key, val[key]);
|
||||
});
|
||||
|
||||
if (!no_marker) {
|
||||
// Object ending marker
|
||||
STInt8.serialize(so, 0xe1);
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
var output = {};
|
||||
|
||||
while (so.peek(1)[0] !== 0xe1) {
|
||||
var keyval = parse(so);
|
||||
output[keyval[0]] = keyval[1];
|
||||
}
|
||||
|
||||
if (output.MemoType !== undefined) {
|
||||
try {
|
||||
var parsedType = convertHexToString(output.MemoType);
|
||||
|
||||
if (parsedType !== 'unformatted_memo') {
|
||||
output.parsed_memo_type = parsedType;
|
||||
}
|
||||
} catch (e) {
|
||||
// empty
|
||||
// we don't know what's in the binary, apparently it's not a UTF-8
|
||||
// string
|
||||
// this is fine, we won't add the parsed_memo_type field
|
||||
}
|
||||
}
|
||||
|
||||
if (output.MemoFormat !== undefined) {
|
||||
try {
|
||||
output.parsed_memo_format = convertHexToString(output.MemoFormat);
|
||||
} catch (e) {
|
||||
// empty
|
||||
// we don't know what's in the binary, apparently it's not a UTF-8
|
||||
// string
|
||||
// this is fine, we won't add the parsed_memo_format field
|
||||
}
|
||||
}
|
||||
|
||||
if (output.MemoData !== undefined) {
|
||||
|
||||
try {
|
||||
if (output.parsed_memo_format === 'json') {
|
||||
// see if we can parse JSON
|
||||
output.parsed_memo_data =
|
||||
JSON.parse(convertHexToString(output.MemoData));
|
||||
|
||||
} else if (output.parsed_memo_format === 'text') {
|
||||
// otherwise see if we can parse text
|
||||
output.parsed_memo_data = convertHexToString(output.MemoData);
|
||||
}
|
||||
} catch(e) {
|
||||
// empty
|
||||
// we'll fail in case the content does not match what the MemoFormat
|
||||
// described
|
||||
// this is fine, we won't add the parsed_memo_data, the user has to
|
||||
// parse themselves
|
||||
}
|
||||
}
|
||||
|
||||
so.read(1);
|
||||
return output;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var STObject = exports.Object = new SerializedType({
|
||||
serialize: function(so, val, no_marker) {
|
||||
var keys = [];
|
||||
|
||||
Object.keys(val).forEach(function(key) {
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
if (key[0] === key[0].toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
||||
throw new Error('JSON contains unknown field: "' + key + '"');
|
||||
}
|
||||
|
||||
keys.push(key);
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
serialize(so, keys[i], val[keys[i]]);
|
||||
}
|
||||
|
||||
if (!no_marker) {
|
||||
// Object ending marker
|
||||
STInt8.serialize(so, 0xe1);
|
||||
}
|
||||
},
|
||||
|
||||
parse: function(so) {
|
||||
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) {
|
||||
for (var i = 0, l = val.length; i < l; i++) {
|
||||
var keys = Object.keys(val[i]);
|
||||
|
||||
if (keys.length !== 1) {
|
||||
throw new 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);
|
||||
}
|
||||
|
||||
// Array ending marker
|
||||
STInt8.serialize(so, 0xf1);
|
||||
},
|
||||
|
||||
parse: function(so) {
|
||||
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;
|
||||
935
src/server.js
Normal file
935
src/server.js
Normal file
@@ -0,0 +1,935 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const util = require('util');
|
||||
const url = require('url');
|
||||
const LRU = require('lru-cache');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const RippleError = require('./rippleerror').RippleError;
|
||||
const Amount = require('./amount').Amount;
|
||||
const RangeSet = require('./rangeset').RangeSet;
|
||||
const log = require('./log').internal.sub('server');
|
||||
|
||||
/**
|
||||
* @constructor Server
|
||||
*
|
||||
* @param {Remote} Reference to a Remote object
|
||||
* @param {Object} Options
|
||||
* @param {String} host
|
||||
* @param {Number|String} port
|
||||
* @param [Boolean] securec
|
||||
*/
|
||||
|
||||
function Server(remote, opts_) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
const self = this;
|
||||
let opts;
|
||||
|
||||
if (typeof opts_ === 'string') {
|
||||
const parsedUrl = url.parse(opts_);
|
||||
opts = {
|
||||
host: parsedUrl.hostname,
|
||||
port: parsedUrl.port,
|
||||
secure: (parsedUrl.protocol === 'ws:') ? false : true
|
||||
};
|
||||
} else {
|
||||
opts = opts_;
|
||||
}
|
||||
|
||||
if (typeof opts !== 'object') {
|
||||
throw new TypeError('Server configuration is not an Object');
|
||||
}
|
||||
|
||||
if (!Server.DOMAIN_RE.test(opts.host)) {
|
||||
throw new Error(
|
||||
'Server host is malformed, use "host" and "port" server configuration');
|
||||
}
|
||||
|
||||
// We want to allow integer strings as valid port numbers for backward
|
||||
// compatibility
|
||||
opts.port = Number(opts.port);
|
||||
if (isNaN(opts.port)) {
|
||||
throw new TypeError('Server port must be a number');
|
||||
}
|
||||
|
||||
if (opts.port < 1 || opts.port > 65535) {
|
||||
throw new TypeError('Server "port" must be an integer in range 1-65535');
|
||||
}
|
||||
|
||||
if (typeof opts.secure !== 'boolean') {
|
||||
opts.secure = true;
|
||||
}
|
||||
|
||||
this._remote = remote;
|
||||
this._opts = opts;
|
||||
this._ws = undefined;
|
||||
|
||||
this._connected = false;
|
||||
this._shouldConnect = false;
|
||||
this._state = 'offline';
|
||||
this._ledgerRanges = new RangeSet();
|
||||
this._ledgerMap = new LRU({max: 200});
|
||||
|
||||
this._id = 0; // request ID
|
||||
this._retry = 0;
|
||||
this._requests = {};
|
||||
|
||||
this._load_base = 256;
|
||||
this._load_factor = 256;
|
||||
|
||||
this._fee = 10;
|
||||
this._fee_ref = 10;
|
||||
this._fee_base = 10;
|
||||
this._reserve_base = undefined;
|
||||
this._reserve_inc = undefined;
|
||||
this._fee_cushion = this._remote.fee_cushion;
|
||||
|
||||
this._lastLedgerIndex = NaN;
|
||||
this._lastLedgerClose = NaN;
|
||||
|
||||
this._score = 0;
|
||||
this._scoreWeights = {
|
||||
ledgerclose: 5,
|
||||
response: 1
|
||||
};
|
||||
|
||||
this._pubkey_node = '';
|
||||
|
||||
this._url = this._opts.url = (this._opts.secure ? 'wss://' : 'ws://')
|
||||
+ this._opts.host + ':' + this._opts.port;
|
||||
|
||||
this.on('message', function onMessage(message) {
|
||||
self._handleMessage(message);
|
||||
});
|
||||
|
||||
this.on('response_subscribe', function onSubscribe(message) {
|
||||
self._handleResponseSubscribe(message);
|
||||
});
|
||||
|
||||
function setActivityInterval() {
|
||||
const interval = self._checkActivity.bind(self);
|
||||
self._activityInterval = setInterval(interval, 1000);
|
||||
}
|
||||
|
||||
this.on('disconnect', function onDisconnect() {
|
||||
clearInterval(self._activityInterval);
|
||||
self.once('ledger_closed', setActivityInterval);
|
||||
});
|
||||
|
||||
this.once('ledger_closed', setActivityInterval);
|
||||
|
||||
this._remote.on('ledger_closed', function onRemoteLedgerClose(ledger) {
|
||||
self._updateScore('ledgerclose', ledger);
|
||||
});
|
||||
|
||||
this.on('response_ping', function onPingResponse(message, request) {
|
||||
self._updateScore('response', request);
|
||||
});
|
||||
|
||||
this.on('load_changed', function onLoadChange(load) {
|
||||
self._updateScore('loadchange', load);
|
||||
});
|
||||
|
||||
this.on('connect', function() {
|
||||
self.requestServerID();
|
||||
});
|
||||
}
|
||||
|
||||
util.inherits(Server, EventEmitter);
|
||||
|
||||
/* eslint-disable max-len */
|
||||
Server.DOMAIN_RE = /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|[-_]){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|[-_]){0,61}[0-9A-Za-z])?)*\.?$/;
|
||||
/* eslint-enable max-len */
|
||||
|
||||
Server.TLS_ERRORS = [
|
||||
'UNABLE_TO_GET_ISSUER_CERT', 'UNABLE_TO_GET_CRL',
|
||||
'UNABLE_TO_DECRYPT_CERT_SIGNATURE', 'UNABLE_TO_DECRYPT_CRL_SIGNATURE',
|
||||
'UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY', 'CERT_SIGNATURE_FAILURE',
|
||||
'CRL_SIGNATURE_FAILURE', 'CERT_NOT_YET_VALID', 'CERT_HAS_EXPIRED',
|
||||
'CRL_NOT_YET_VALID', 'CRL_HAS_EXPIRED', 'ERROR_IN_CERT_NOT_BEFORE_FIELD',
|
||||
'ERROR_IN_CERT_NOT_AFTER_FIELD', 'ERROR_IN_CRL_LAST_UPDATE_FIELD',
|
||||
'ERROR_IN_CRL_NEXT_UPDATE_FIELD', 'OUT_OF_MEM',
|
||||
'DEPTH_ZERO_SELF_SIGNED_CERT', 'SELF_SIGNED_CERT_IN_CHAIN',
|
||||
'UNABLE_TO_GET_ISSUER_CERT_LOCALLY', 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
||||
'CERT_CHAIN_TOO_LONG', 'CERT_REVOKED', 'INVALID_CA',
|
||||
'PATH_LENGTH_EXCEEDED', 'INVALID_PURPOSE', 'CERT_UNTRUSTED',
|
||||
'CERT_REJECTED'
|
||||
];
|
||||
|
||||
/**
|
||||
* Server states that we will treat as the server being online.
|
||||
*
|
||||
* Our requirements are that the server can process transactions and notify
|
||||
* us of changes.
|
||||
*/
|
||||
|
||||
Server.onlineStates = [
|
||||
'syncing',
|
||||
'tracking',
|
||||
'proposing',
|
||||
'validating',
|
||||
'full'
|
||||
];
|
||||
|
||||
/**
|
||||
* This is the final interface between client code and a socket connection to a
|
||||
* `rippled` server. As such, this is a decent hook point to allow a WebSocket
|
||||
* interface conforming object to be used as a basis to mock rippled. This
|
||||
* avoids the need to bind a websocket server to a port and allows a more
|
||||
* synchronous style of code to represent a client <-> server message sequence.
|
||||
* We can also use this to log a message sequence to a buffer.
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.websocketConstructor = function() {
|
||||
// We require this late, because websocket shims may be loaded after
|
||||
// ripple-lib in the browser
|
||||
return require('ws');
|
||||
};
|
||||
|
||||
/**
|
||||
* Set server state
|
||||
*
|
||||
* @param {String} state
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._setState = function(state) {
|
||||
if (state !== this._state) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'set_state:', state);
|
||||
}
|
||||
|
||||
this._state = state;
|
||||
this.emit('state', state);
|
||||
|
||||
switch (state) {
|
||||
case 'online':
|
||||
this._connected = true;
|
||||
this._retry = 0;
|
||||
this.emit('connect');
|
||||
break;
|
||||
case 'offline':
|
||||
this._connected = false;
|
||||
this.emit('disconnect');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that server is still active.
|
||||
*
|
||||
* Server activity is determined by ledger_closed events.
|
||||
* Maximum delay to receive a ledger_closed event is 20s.
|
||||
*
|
||||
* If server is inactive, reconnect
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._checkActivity = function() {
|
||||
if (!this.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(this._lastLedgerClose)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const delta = (Date.now() - this._lastLedgerClose);
|
||||
|
||||
if (delta > (1000 * 25)) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'reconnect: activity delta:', delta);
|
||||
}
|
||||
this.reconnect();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If server is not up-to-date, request server_info for getting pubkey_node
|
||||
* & hostid information. Otherwise this information is available on the
|
||||
* initial server subscribe response
|
||||
*/
|
||||
|
||||
Server.prototype.requestServerID = function() {
|
||||
const self = this;
|
||||
|
||||
if (this._pubkey_node) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.on('response_server_info', function setServerID(message) {
|
||||
try {
|
||||
self._pubkey_node = message.info.pubkey_node;
|
||||
} catch (e) {
|
||||
log.warn('Failed to get server pubkey_node', message);
|
||||
}
|
||||
});
|
||||
|
||||
const serverInfoRequest = this._remote.requestServerInfo();
|
||||
serverInfoRequest.on('error', function() {});
|
||||
this._request(serverInfoRequest);
|
||||
};
|
||||
|
||||
/**
|
||||
* Server maintains a score for request prioritization.
|
||||
*
|
||||
* The score is determined by various data including
|
||||
* this server's lag to receive ledger_closed events,
|
||||
* ping response time, and load(fee) change
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {Object} data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._updateScore = function(type, data) {
|
||||
if (!this.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const weight = this._scoreWeights[type] || 1;
|
||||
let delta;
|
||||
|
||||
switch (type) {
|
||||
case 'ledgerclose':
|
||||
// Ledger lag
|
||||
delta = data.ledger_index - this._lastLedgerIndex;
|
||||
if (delta > 0) {
|
||||
this._score += weight * delta;
|
||||
}
|
||||
break;
|
||||
case 'response':
|
||||
// Ping lag
|
||||
// Servers are not pinged by default
|
||||
delta = Math.floor((Date.now() - data.time) / 200);
|
||||
this._score += weight * delta;
|
||||
break;
|
||||
case 'loadchange':
|
||||
// Load/fee change
|
||||
this._fee = Number(this._computeFee(10));
|
||||
break;
|
||||
}
|
||||
|
||||
if (this._score > 1e3) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'reconnect: score:', this._score);
|
||||
}
|
||||
this.reconnect();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the server's remote address
|
||||
*
|
||||
* Incompatible with ripple-lib client build
|
||||
*/
|
||||
|
||||
Server.prototype.getRemoteAddress =
|
||||
Server.prototype._remoteAddress = function() {
|
||||
try {
|
||||
return this._ws._socket.remoteAddress;
|
||||
} catch (e) {
|
||||
log.warn('Cannot get remote address. Does not work in browser');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the server's hostid
|
||||
*/
|
||||
|
||||
Server.prototype.getHostID = Server.prototype.getServerID = function() {
|
||||
return this._url + ' (' + (this._pubkey_node ? this._pubkey_node : '') + ')';
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect from rippled WebSocket server
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.disconnect = function() {
|
||||
const self = this;
|
||||
|
||||
if (!this.isConnected()) {
|
||||
this.once('socket_open', function() {
|
||||
self.disconnect();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// these need to be reset so that updateScore
|
||||
// and checkActivity do not trigger reconnect
|
||||
this._lastLedgerIndex = NaN;
|
||||
this._lastLedgerClose = NaN;
|
||||
this._score = 0;
|
||||
this._shouldConnect = false;
|
||||
this._ledgerRanges.reset();
|
||||
this._ledgerMap.reset();
|
||||
this._setState('offline');
|
||||
|
||||
if (this._ws) {
|
||||
this._ws.close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reconnect to rippled WebSocket server
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.reconnect = function() {
|
||||
const self = this;
|
||||
|
||||
function reconnect() {
|
||||
self._shouldConnect = true;
|
||||
self._retry = 0;
|
||||
self.connect();
|
||||
}
|
||||
|
||||
if (this._ws && this._shouldConnect) {
|
||||
if (this.isConnected()) {
|
||||
this.once('disconnect', reconnect);
|
||||
this.disconnect();
|
||||
} else {
|
||||
reconnect();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to rippled WebSocket server and subscribe to events that are
|
||||
* internally requisite. Automatically retry connections with a gradual
|
||||
* back-off
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Server.prototype.connect = function() {
|
||||
const self = this;
|
||||
|
||||
const WebSocket = Server.websocketConstructor();
|
||||
|
||||
if (!WebSocket) {
|
||||
throw new Error('No websocket support detected!');
|
||||
}
|
||||
|
||||
// We don't connect if we believe we're already connected. This means we have
|
||||
// recently received a message from the server and the WebSocket has not
|
||||
// reported any issues either. If we do fail to ping or the connection drops,
|
||||
// we will automatically reconnect.
|
||||
if (this.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure any existing socket is given the command to close first.
|
||||
if (this._ws) {
|
||||
this._ws.close();
|
||||
}
|
||||
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'connect');
|
||||
}
|
||||
|
||||
const ws = this._ws = new WebSocket(this._opts.url);
|
||||
|
||||
this._shouldConnect = true;
|
||||
|
||||
self.emit('connecting');
|
||||
|
||||
ws.onmessage = function onMessage(msg) {
|
||||
let message = msg.data;
|
||||
|
||||
try {
|
||||
message = JSON.parse(message);
|
||||
} catch (e) {
|
||||
const error = new RippleError('unexpected',
|
||||
'Unexpected response from server: ' + JSON.stringify(message));
|
||||
|
||||
self.emit('unexpected', message);
|
||||
log.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
self.emit('message', message);
|
||||
};
|
||||
|
||||
ws.onopen = function onOpen() {
|
||||
if (ws === self._ws) {
|
||||
self.emit('socket_open');
|
||||
// Subscribe to events
|
||||
self._request(self._remote._serverPrepareSubscribe(self));
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = function onError(e) {
|
||||
if (ws === self._ws) {
|
||||
self.emit('socket_error');
|
||||
|
||||
if (self._remote.trace) {
|
||||
log.info(self.getServerID(), 'onerror:', e.data || e);
|
||||
}
|
||||
|
||||
if (Server.TLS_ERRORS.indexOf(e.message) !== -1) {
|
||||
// Unrecoverable
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Most connection errors for WebSockets are conveyed as 'close'
|
||||
// events with
|
||||
// code 1006. This is done for security purposes and therefore unlikely to
|
||||
// ever change.
|
||||
|
||||
// This means that this handler is hardly ever called in practice.
|
||||
// If it is,
|
||||
// it probably means the server's WebSocket implementation is corrupt, or
|
||||
// the connection is somehow producing corrupt data.
|
||||
|
||||
// Most WebSocket applications simply log and ignore this error. Once we
|
||||
// support for multiple servers, we may consider doing something like
|
||||
// lowering this server's quality score.
|
||||
|
||||
// However, in Node.js this event may be triggered instead of the close
|
||||
// event, so we need to handle it.
|
||||
self._handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = function onClose() {
|
||||
if (ws === self._ws) {
|
||||
if (self._remote.trace) {
|
||||
log.info(self.getServerID(), 'onclose:', ws.readyState);
|
||||
}
|
||||
self._handleClose();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retry connection to rippled server
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._retryConnect = function() {
|
||||
const self = this;
|
||||
|
||||
this._retry += 1;
|
||||
|
||||
const retryTimeout = (this._retry < 40)
|
||||
// First, for 2 seconds: 20 times per second
|
||||
? (1000 / 20)
|
||||
: (this._retry < 40 + 60)
|
||||
// Then, for 1 minute: once per second
|
||||
? (1000)
|
||||
: (this._retry < 40 + 60 + 60)
|
||||
// Then, for 10 minutes: once every 10 seconds
|
||||
? (10 * 1000)
|
||||
// Then: once every 30 seconds
|
||||
: (30 * 1000);
|
||||
|
||||
function connectionRetry() {
|
||||
if (self._shouldConnect) {
|
||||
if (self._remote.trace) {
|
||||
log.info(self.getServerID(), 'retry', self._retry);
|
||||
}
|
||||
self.connect();
|
||||
}
|
||||
}
|
||||
|
||||
this._retryTimer = setTimeout(connectionRetry, retryTimeout);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle connection closes
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._handleClose = function() {
|
||||
const ws = this._ws;
|
||||
|
||||
// Prevent additional events from this socket
|
||||
ws.onopen = ws.onerror = ws.onclose = ws.onmessage = _.noop;
|
||||
|
||||
this.emit('socket_close');
|
||||
this._setState('offline');
|
||||
|
||||
if (this._shouldConnect) {
|
||||
this._retryConnect();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle incoming messages from rippled WebSocket server
|
||||
*
|
||||
* @param {JSON-parseable} message
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._handleMessage = function(message) {
|
||||
if (!Server.isValidMessage(message)) {
|
||||
this.emit('unexpected', message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case 'ledgerClosed':
|
||||
this._handleLedgerClosed(message);
|
||||
break;
|
||||
case 'serverStatus':
|
||||
this._handleServerStatus(message);
|
||||
break;
|
||||
case 'response':
|
||||
this._handleResponse(message);
|
||||
break;
|
||||
case 'path_find':
|
||||
this._handlePathFind(message);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._handleLedgerClosed = function(message) {
|
||||
this._lastLedgerIndex = message.ledger_index;
|
||||
this._lastLedgerClose = Date.now();
|
||||
this._ledgerRanges.add(message.ledger_index);
|
||||
this._ledgerMap.set(message.ledger_hash, message.ledger_index);
|
||||
this.emit('ledger_closed', message);
|
||||
};
|
||||
|
||||
Server.prototype._handleServerStatus = function(message) {
|
||||
// This message is only received when online.
|
||||
// As we are connected, it is the definitive final state.
|
||||
const isOnline = ~Server.onlineStates.indexOf(message.server_status);
|
||||
|
||||
this._setState(isOnline ? 'online' : 'offline');
|
||||
|
||||
if (!Server.isLoadStatus(message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('load', message, this);
|
||||
this._remote.emit('load', message, this);
|
||||
|
||||
const loadChanged = message.load_base !== this._load_base
|
||||
|| message.load_factor !== this._load_factor;
|
||||
|
||||
if (loadChanged) {
|
||||
this._load_base = message.load_base;
|
||||
this._load_factor = message.load_factor;
|
||||
this.emit('load_changed', message, this);
|
||||
this._remote.emit('load_changed', message, this);
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._handleResponse = function(message) {
|
||||
// A response to a request.
|
||||
const request = this._requests[message.id];
|
||||
|
||||
delete this._requests[message.id];
|
||||
|
||||
if (!request) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'UNEXPECTED:', message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.status === 'success') {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'response:', message);
|
||||
}
|
||||
|
||||
const command = request.message.command;
|
||||
const result = message.result;
|
||||
const responseEvent = 'response_' + command;
|
||||
|
||||
request.emit('success', result);
|
||||
|
||||
[this, this._remote].forEach(function(emitter) {
|
||||
emitter.emit(responseEvent, result, request, message);
|
||||
});
|
||||
} else if (message.error) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'error:', message);
|
||||
}
|
||||
|
||||
request.emit('error', {
|
||||
error: 'remoteError',
|
||||
error_message: 'Remote reported an error.',
|
||||
remote: message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._handlePathFind = function(message) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'path_find:', message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle initial subscription response message. The server is considered
|
||||
* `connected` after it has received a response to initial subscription to
|
||||
* ledger and server streams
|
||||
*
|
||||
* @param {Object} message
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._handleResponseSubscribe = function(message) {
|
||||
if (this.isConnected()) {
|
||||
// This function only concerns initializing the server's internal
|
||||
// state after a connection
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._remote.allow_partial_history
|
||||
&& !Server.hasFullLedgerHistory(message)) {
|
||||
// Server has partial history and Remote has been configured to disallow
|
||||
// servers with incomplete history
|
||||
this.reconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.pubkey_node) {
|
||||
// pubkey_node is used to identify the server
|
||||
this._pubkey_node = message.pubkey_node;
|
||||
}
|
||||
|
||||
if (Server.isLoadStatus(message)) {
|
||||
this._load_base = message.load_base || 256;
|
||||
this._load_factor = message.load_factor || 256;
|
||||
this._fee_ref = message.fee_ref || 10;
|
||||
this._fee_base = message.fee_base || 10;
|
||||
this._reserve_base = message.reserve_base;
|
||||
this._reserve_inc = message.reserve_inc;
|
||||
}
|
||||
|
||||
if (message.validated_ledgers) {
|
||||
// Add validated ledgers to ledger range set
|
||||
this._ledgerRanges.add(message.validated_ledgers);
|
||||
}
|
||||
|
||||
if (~Server.onlineStates.indexOf(message.server_status)) {
|
||||
this._setState('online');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that server message indicates that server has complete ledger history
|
||||
*
|
||||
* @param {Object} message
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
Server.hasFullLedgerHistory = function(message) {
|
||||
return (typeof message === 'object')
|
||||
&& (message.server_status === 'full')
|
||||
&& (typeof message.validated_ledgers === 'string')
|
||||
&& (message.validated_ledgers.split('-').length === 2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that received message from rippled is valid
|
||||
*
|
||||
* @param {Object} message
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
Server.isValidMessage = function(message) {
|
||||
return (typeof message === 'object')
|
||||
&& (typeof message.type === 'string');
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that received serverStatus message contains load status information
|
||||
*
|
||||
* @param {Object} message
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
Server.isLoadStatus = function(message) {
|
||||
return (typeof message === 'object')
|
||||
&& (typeof message.load_base === 'number')
|
||||
&& (typeof message.load_factor === 'number');
|
||||
};
|
||||
|
||||
/**
|
||||
* Send JSON message to rippled WebSocket server
|
||||
*
|
||||
* @param {JSON-Stringifiable} message
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._sendMessage = function(message) {
|
||||
if (this._ws) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'request:', message);
|
||||
}
|
||||
this._ws.send(JSON.stringify(message));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a Request object
|
||||
*
|
||||
* Requests are indexed by message ID, which is repeated in the response from
|
||||
* rippled WebSocket server
|
||||
*
|
||||
* @param {Request} request
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._request = function(request) {
|
||||
const self = this;
|
||||
|
||||
// Only bother if we are still connected.
|
||||
if (!this._ws) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'request: DROPPING:', request.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
request.server = this;
|
||||
request.message.id = this._id;
|
||||
request.time = Date.now();
|
||||
|
||||
this._requests[request.message.id] = request;
|
||||
|
||||
// Advance message ID
|
||||
this._id++;
|
||||
|
||||
function sendRequest() {
|
||||
self._sendMessage(request.message);
|
||||
}
|
||||
|
||||
const isOpen = this._ws.readyState === 1;
|
||||
const isSubscribeRequest = request && request.message.command === 'subscribe';
|
||||
|
||||
if (this.isConnected() || (isOpen && isSubscribeRequest)) {
|
||||
sendRequest();
|
||||
} else {
|
||||
this.once('connect', sendRequest);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get server connected status
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
|
||||
Server.prototype.isConnected = Server.prototype._isConnected = function() {
|
||||
return this._connected;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate transaction fee
|
||||
*
|
||||
* @param {Transaction|Number} Fee units for a provided transaction
|
||||
* @return {String} Final fee in XRP for specified number of fee units
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Server.prototype._computeFee = function(feeUnits) {
|
||||
if (isNaN(feeUnits)) {
|
||||
throw new Error('Invalid argument');
|
||||
}
|
||||
|
||||
return this._feeTx(Number(feeUnits)).to_json();
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate a transaction fee for a number of tx fee units.
|
||||
*
|
||||
* This takes into account the last known network and local load fees.
|
||||
*
|
||||
* @param {Number} Fee units for a provided transaction
|
||||
* @return {Amount} Final fee in XRP for specified number of fee units.
|
||||
*/
|
||||
|
||||
Server.prototype._feeTx = function(units) {
|
||||
const fee_unit = this._feeTxUnit();
|
||||
return Amount.from_json(String(Math.ceil(units * fee_unit)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current recommended transaction fee unit.
|
||||
*
|
||||
* Multiply this value with the number of fee units in order to calculate the
|
||||
* recommended fee for the transaction you are trying to submit.
|
||||
*
|
||||
* @return {Number} Recommended amount for one fee unit as float.
|
||||
*/
|
||||
|
||||
Server.prototype._feeTxUnit = function() {
|
||||
let fee_unit = this._fee_base / this._fee_ref;
|
||||
|
||||
// Apply load fees
|
||||
fee_unit *= this._load_factor / this._load_base;
|
||||
|
||||
// Apply fee cushion (a safety margin in case fees rise since
|
||||
// we were last updated)
|
||||
fee_unit *= this._fee_cushion;
|
||||
|
||||
return fee_unit;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current recommended reserve base.
|
||||
*
|
||||
* Returns the base reserve with load fees and safety margin applied.
|
||||
*/
|
||||
|
||||
Server.prototype._reserve = function(ownerCount) {
|
||||
const reserve_base = Amount.from_json(String(this._reserve_base));
|
||||
const reserve_inc = Amount.from_json(String(this._reserve_inc));
|
||||
const owner_count = ownerCount || 0;
|
||||
|
||||
if (owner_count < 0) {
|
||||
throw new Error('Owner count must not be negative.');
|
||||
}
|
||||
|
||||
return reserve_base.add(reserve_inc.product_human(owner_count));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that server has seen closed ledger
|
||||
*
|
||||
* @param {string|number} ledger hash or index
|
||||
* @return boolean
|
||||
*/
|
||||
|
||||
Server.prototype.hasLedger = function(ledger) {
|
||||
let result = false;
|
||||
|
||||
if (typeof ledger === 'string' && /^[A-F0-9]{64}$/.test(ledger)) {
|
||||
result = this._ledgerMap.has(ledger);
|
||||
} else if (ledger !== null && !isNaN(ledger)) {
|
||||
result = this._ledgerRanges.has(ledger);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get ledger index of last seen validated ledger
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
|
||||
Server.prototype.getLastLedger =
|
||||
Server.prototype.getLastLedgerIndex = function() {
|
||||
return this._lastLedgerIndex;
|
||||
};
|
||||
|
||||
exports.Server = Server;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
187
src/shamap.js
Normal file
187
src/shamap.js
Normal file
@@ -0,0 +1,187 @@
|
||||
'use strict';
|
||||
|
||||
var util = require('util');
|
||||
var hashprefixes = require('./hashprefixes');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
|
||||
/**
|
||||
* Abstract class representing a node in a SHAMap tree.
|
||||
*
|
||||
* Can be either SHAMapTreeNodeInner or SHAMapTreeNodeLeaf.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
function SHAMapTreeNode() { }
|
||||
|
||||
SHAMapTreeNode.TYPE_INNER = 1;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_NM = 2;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_MD = 3;
|
||||
SHAMapTreeNode.TYPE_ACCOUNT_STATE = 4;
|
||||
|
||||
/**
|
||||
* @param {String} tag (64 hexadecimal characters)
|
||||
* @param {SHAMapTreeNode} node
|
||||
* @return {void}
|
||||
* @virtual
|
||||
*/
|
||||
/*eslint-disable no-unused-vars*/
|
||||
SHAMapTreeNode.prototype.add_item = function(tag, node) {
|
||||
throw new Error(
|
||||
'Called unimplemented virtual method SHAMapTreeNode#add_item.');
|
||||
};
|
||||
/*eslint-enable no-unused-vars*/
|
||||
|
||||
SHAMapTreeNode.prototype.hash = function() {
|
||||
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Inner (non-leaf) node in a SHAMap tree.
|
||||
* @param {Number} depth (i.e. how many parent inner nodes)
|
||||
* @class
|
||||
*/
|
||||
function SHAMapTreeNodeInner(depth) {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
this.leaves = {};
|
||||
|
||||
this.type = SHAMapTreeNode.INNER;
|
||||
this.depth = depth === undefined ? 0 : depth;
|
||||
|
||||
this.empty = true;
|
||||
}
|
||||
|
||||
util.inherits(SHAMapTreeNodeInner, SHAMapTreeNode);
|
||||
|
||||
/**
|
||||
* @param {String} tag (equates to a ledger entry `index`)
|
||||
* @param {SHAMapTreeNode} node (to add)
|
||||
* @return {void}
|
||||
*/
|
||||
SHAMapTreeNodeInner.prototype.add_item = function(tag, node) {
|
||||
var depth = this.depth;
|
||||
var existing_node = this.get_node(tag[depth]);
|
||||
|
||||
if (existing_node) {
|
||||
// A node already exists in this slot
|
||||
if (existing_node instanceof SHAMapTreeNodeInner) {
|
||||
// There is an inner node, so we need to go deeper
|
||||
existing_node.add_item(tag, node);
|
||||
} else if (existing_node.tag === tag) {
|
||||
// Collision
|
||||
throw new Error(
|
||||
'Tried to add a node to a SHAMap that was already in there.');
|
||||
} else {
|
||||
// Turn it into an inner node
|
||||
var new_inner_node = new SHAMapTreeNodeInner(depth + 1);
|
||||
|
||||
// Parent new and existing node
|
||||
new_inner_node.add_item(existing_node.tag, existing_node);
|
||||
new_inner_node.add_item(tag, node);
|
||||
|
||||
// And place the newly created inner node in the slot
|
||||
this.set_node(tag[depth], new_inner_node);
|
||||
}
|
||||
} else {
|
||||
// Neat, we have a nice open spot for the new node
|
||||
this.set_node(tag[depth], node);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Overwrite the node that is currently in a given slot.
|
||||
* @param {String} slot (a character 0-F)
|
||||
* @param {SHAMapTreeNode} node (to place)
|
||||
* @return {void}
|
||||
*/
|
||||
SHAMapTreeNodeInner.prototype.set_node = function(slot, node) {
|
||||
this.leaves[slot] = node;
|
||||
this.empty = false;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.get_node = function(slot) {
|
||||
return this.leaves[slot];
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.hash = function() {
|
||||
if (this.empty) {
|
||||
return UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
}
|
||||
|
||||
var hash_buffer = new SerializedObject();
|
||||
|
||||
for (var i = 0; i < 16; i++) {
|
||||
var leafHash = UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
var slot = i.toString(16).toUpperCase();
|
||||
|
||||
if (typeof this.leaves[slot] === 'object') {
|
||||
leafHash = this.leaves[slot].hash();
|
||||
}
|
||||
|
||||
hash_buffer.append(leafHash.to_bytes());
|
||||
}
|
||||
|
||||
var hash = hash_buffer.hash(hashprefixes.HASH_INNER_NODE);
|
||||
|
||||
return UInt256.from_bits(hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaf node in a SHAMap tree.
|
||||
* @param {String} tag (equates to a ledger entry `index`)
|
||||
* @param {SerializedObject} node (bytes of account state, transaction etc)
|
||||
* @param {Number} type (one of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc)
|
||||
* @class
|
||||
*/
|
||||
function SHAMapTreeNodeLeaf(tag, node, type) {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
if (typeof tag !== 'string') {
|
||||
throw new Error('Tag is unexpected type.');
|
||||
}
|
||||
|
||||
this.tag = tag;
|
||||
this.tag_bytes = UInt256.from_hex(this.tag).to_bytes();
|
||||
this.type = type;
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
util.inherits(SHAMapTreeNodeLeaf, SHAMapTreeNode);
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.hash = function() {
|
||||
var buffer = new SerializedObject();
|
||||
switch (this.type) {
|
||||
case SHAMapTreeNode.TYPE_ACCOUNT_STATE:
|
||||
buffer.append(this.node);
|
||||
buffer.append(this.tag_bytes);
|
||||
return buffer.hash(hashprefixes.HASH_LEAF_NODE);
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_NM:
|
||||
return this.tag_bytes;
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_MD:
|
||||
buffer.append(this.node);
|
||||
buffer.append(this.tag_bytes);
|
||||
return buffer.hash(hashprefixes.HASH_TX_NODE);
|
||||
default:
|
||||
throw new Error('Tried to hash a SHAMap node of unknown type.');
|
||||
}
|
||||
};
|
||||
|
||||
function SHAMap() {
|
||||
this.root = new SHAMapTreeNodeInner(0);
|
||||
}
|
||||
|
||||
SHAMap.prototype.add_item = function(tag, node, type) {
|
||||
node = new SHAMapTreeNodeLeaf(tag, node, type);
|
||||
this.root.add_item(tag, node);
|
||||
};
|
||||
|
||||
SHAMap.prototype.hash = function() {
|
||||
return this.root.hash();
|
||||
};
|
||||
|
||||
exports.SHAMap = SHAMap;
|
||||
exports.SHAMapTreeNode = SHAMapTreeNode;
|
||||
exports.SHAMapTreeNodeInner = SHAMapTreeNodeInner;
|
||||
exports.SHAMapTreeNodeLeaf = SHAMapTreeNodeLeaf;
|
||||
8
src/sjcl-custom/index.js
Normal file
8
src/sjcl-custom/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
require('./sjcl-ecc-pointextras.js');
|
||||
require('./sjcl-secp256k1.js');
|
||||
require('./sjcl-extramath.js');
|
||||
require('./sjcl-validecc.js');
|
||||
require('./sjcl-ecdsa-canonical.js');
|
||||
require('./sjcl-ecdsa-der.js');
|
||||
require('./sjcl-ecdsa-recoverablepublickey.js');
|
||||
86
src/sjcl-custom/sjcl-ecc-pointextras.js
Normal file
86
src/sjcl-custom/sjcl-ecc-pointextras.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/* eslint new-cap: [2, {newIsCapExceptions: ["bn"]}] */
|
||||
'use strict';
|
||||
var sjcl = require('sjcl');
|
||||
/**
|
||||
* Check that the point is valid based on the method described in
|
||||
* SEC 1: Elliptic Curve Cryptography, section 3.2.2.1:
|
||||
* Elliptic Curve Public Key Validation Primitive
|
||||
* http://www.secg.org/download/aid-780/sec1-v2.pdf
|
||||
*
|
||||
* @returns {Boolean} true if point is valid
|
||||
*/
|
||||
sjcl.ecc.point.prototype.isValidPoint = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
var field_modulus = self.curve.field.modulus;
|
||||
|
||||
if (self.isIdentity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that coordinatres are in bounds
|
||||
// Return false if x < 1 or x > (field_modulus - 1)
|
||||
if (((new sjcl.bn(1).greaterEquals(self.x)) &&
|
||||
!self.x.equals(1)) ||
|
||||
(self.x.greaterEquals(field_modulus.sub(1))) &&
|
||||
!self.x.equals(1)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return false if y < 1 or y > (field_modulus - 1)
|
||||
if (((new sjcl.bn(1).greaterEquals(self.y)) &&
|
||||
!self.y.equals(1)) ||
|
||||
(self.y.greaterEquals(field_modulus.sub(1))) &&
|
||||
!self.y.equals(1)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self.isOnCurve()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO check to make sure point is a scalar multiple of base_point
|
||||
|
||||
return true;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the point is on the curve
|
||||
*
|
||||
* @returns {Boolean} true if point is on the curve
|
||||
*/
|
||||
sjcl.ecc.point.prototype.isOnCurve = function() {
|
||||
|
||||
var self = this;
|
||||
|
||||
var component_a = self.curve.a;
|
||||
var component_b = self.curve.b;
|
||||
var field_modulus = self.curve.field.modulus;
|
||||
|
||||
var left_hand_side = self.y.mul(self.y).mod(field_modulus);
|
||||
var right_hand_side = self.x.mul(self.x).mul(self.x).add(
|
||||
component_a.mul(self.x)).add(component_b).mod(field_modulus);
|
||||
|
||||
return left_hand_side.equals(right_hand_side);
|
||||
|
||||
};
|
||||
|
||||
|
||||
sjcl.ecc.point.prototype.toString = function() {
|
||||
return '(' +
|
||||
this.x.toString() + ', ' +
|
||||
this.y.toString() +
|
||||
')';
|
||||
};
|
||||
|
||||
sjcl.ecc.pointJac.prototype.toString = function() {
|
||||
return '(' +
|
||||
this.x.toString() + ', ' +
|
||||
this.y.toString() + ', ' +
|
||||
this.z.toString() +
|
||||
')';
|
||||
};
|
||||
20
src/sjcl-custom/sjcl-ecdsa-canonical.js
Normal file
20
src/sjcl-custom/sjcl-ecdsa-canonical.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
var sjcl = require('sjcl');
|
||||
|
||||
sjcl.ecc.ecdsa.secretKey.prototype.canonicalizeSignature = function(rs) {
|
||||
var w = sjcl.bitArray,
|
||||
R = this._curve.r,
|
||||
l = R.bitLength();
|
||||
|
||||
var r = sjcl.bn.fromBits(w.bitSlice(rs, 0, l)),
|
||||
s = sjcl.bn.fromBits(w.bitSlice(rs, l, 2 * l));
|
||||
|
||||
// For a canonical signature we want the lower of two possible values for s
|
||||
// 0 < s <= n/2
|
||||
if (!R.copy().halveM().greaterEquals(s)) {
|
||||
s = R.sub(s);
|
||||
}
|
||||
|
||||
return w.concat(r.toBits(l), s.toBits(l));
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
'use strict';
|
||||
var sjcl = require('sjcl');
|
||||
|
||||
sjcl.ecc.ecdsa.secretKey.prototype.signDER = function(hash, paranoia) {
|
||||
return this.encodeDER(this.sign(hash, paranoia));
|
||||
};
|
||||
@@ -7,16 +10,24 @@ sjcl.ecc.ecdsa.secretKey.prototype.encodeDER = function(rs) {
|
||||
R = this._curve.r,
|
||||
l = R.bitLength();
|
||||
|
||||
var rb = sjcl.codec.bytes.fromBits(w.bitSlice(rs,0,l)),
|
||||
sb = sjcl.codec.bytes.fromBits(w.bitSlice(rs,l,2*l));
|
||||
var rb = sjcl.codec.bytes.fromBits(w.bitSlice(rs, 0, l)),
|
||||
sb = sjcl.codec.bytes.fromBits(w.bitSlice(rs, l, 2 * l));
|
||||
|
||||
// Drop empty leading bytes
|
||||
while (!rb[0] && rb.length) rb.shift();
|
||||
while (!sb[0] && sb.length) sb.shift();
|
||||
while (!rb[0] && rb.length) {
|
||||
rb.shift();
|
||||
}
|
||||
while (!sb[0] && sb.length) {
|
||||
sb.shift();
|
||||
}
|
||||
|
||||
// If high bit is set, prepend an extra zero byte (DER signed integer)
|
||||
if (rb[0] & 0x80) rb.unshift(0);
|
||||
if (sb[0] & 0x80) sb.unshift(0);
|
||||
if (rb[0] & 0x80) {
|
||||
rb.unshift(0);
|
||||
}
|
||||
if (sb[0] & 0x80) {
|
||||
sb.unshift(0);
|
||||
}
|
||||
|
||||
var buffer = [].concat(
|
||||
0x30,
|
||||
325
src/sjcl-custom/sjcl-ecdsa-recoverablepublickey.js
Normal file
325
src/sjcl-custom/sjcl-ecdsa-recoverablepublickey.js
Normal file
@@ -0,0 +1,325 @@
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/* eslint new-cap: [2, {newIsCapExceptions: [
|
||||
"bn", "invalid", "point", "corrupt", "bug", "publicKey"]}] */
|
||||
'use strict';
|
||||
const sjcl = require('sjcl');
|
||||
|
||||
/**
|
||||
* This module uses the public key recovery method
|
||||
* described in SEC 1: Elliptic Curve Cryptography,
|
||||
* section 4.1.6, "Public Key Recovery Operation".
|
||||
* http://www.secg.org/download/aid-780/sec1-v2.pdf
|
||||
*
|
||||
* Implementation based on:
|
||||
* https://github.com/bitcoinjs/bitcoinjs-lib/blob/
|
||||
* 89cf731ac7309b4f98994e3b4b67b7226020181f/src/ecdsa.js
|
||||
*/
|
||||
|
||||
// Defined here so that this value only needs to be calculated once
|
||||
let FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR;
|
||||
|
||||
/**
|
||||
* Retrieve the r and s components of a signature
|
||||
*
|
||||
* @param {sjcl.ecc.curve} curve - curve
|
||||
* @param {bitArray} signature - signature
|
||||
* @returns {Object} Object with 'r' and 's' fields each as an sjcl.bn
|
||||
*/
|
||||
function getRandSFromSignature(curve, signature) {
|
||||
|
||||
const r_length = curve.r.bitLength();
|
||||
|
||||
return {
|
||||
r: sjcl.bn.fromBits(sjcl.bitArray.bitSlice(signature, 0, r_length)),
|
||||
s: sjcl.bn.fromBits(sjcl.bitArray.bitSlice(
|
||||
signature, r_length, sjcl.bitArray.bitLength(signature)))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a signature given the raw components
|
||||
* using method defined in section 4.1.5:
|
||||
* "Alternative Verifying Operation"
|
||||
*
|
||||
* @param {sjcl.ecc.curve} curve
|
||||
* @param {sjcl.bn} e
|
||||
* @param {sjcl.bn} r
|
||||
* @param {sjcl.bn} s
|
||||
* @param {sjcl.ecc.point} public_key_point
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
function verify_raw(curve, e, r, s, public_key_point) {
|
||||
|
||||
const field_order = curve.r;
|
||||
|
||||
// Return false if r is out of bounds
|
||||
if ((new sjcl.bn(1)).greaterEquals(r)
|
||||
|| r.greaterEquals(new sjcl.bn(field_order))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return false if s is out of bounds
|
||||
if ((new sjcl.bn(1)).greaterEquals(s)
|
||||
|| s.greaterEquals(new sjcl.bn(field_order))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that r = (u1 + u2)G
|
||||
// u1 = e x s^-1 (mod field_order)
|
||||
// u2 = r x s^-1 (mod field_order)
|
||||
const s_mod_inverse_field_order = s.inverseMod(field_order);
|
||||
const u1 = e.mul(s_mod_inverse_field_order).mod(field_order);
|
||||
const u2 = r.mul(s_mod_inverse_field_order).mod(field_order);
|
||||
|
||||
const point_computed = curve.G.mult2(u1, u2, public_key_point);
|
||||
|
||||
return r.equals(point_computed.x.mod(field_order));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover the public key from the signature.
|
||||
*
|
||||
* @param {sjcl.ecc.curve} curve
|
||||
* @param {sjcl.bn} r
|
||||
* @param {sjcl.bn} s
|
||||
* @param {bitArray} hash_bits
|
||||
* @param {Number, 0-3} recovery_factor
|
||||
* @returns {sjcl.point} Public key corresponding to signature
|
||||
*/
|
||||
function recoverPublicKeyPointFromSignature(curve, signature_r, signature_s,
|
||||
hash_bits, recovery_factor) {
|
||||
|
||||
const field_order = curve.r;
|
||||
const field_modulus = curve.field.modulus;
|
||||
|
||||
// The less significant bit specifies whether the y coordinate
|
||||
// of the compressed point is even or not.
|
||||
const compressed_point_y_coord_is_even = recovery_factor & 1;
|
||||
|
||||
// The more significant bit specifies whether we should use the
|
||||
// first or second candidate key.
|
||||
const use_second_candidate_key = (recovery_factor & 2) >> 1;
|
||||
|
||||
// Calculate (field_order + 1) / 4
|
||||
if (!FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR) {
|
||||
FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR = field_modulus.add(1).div(4);
|
||||
}
|
||||
|
||||
// In the paper they write "1. For j from 0 to h do the following..."
|
||||
// That is not necessary here because we are given the recovery_factor
|
||||
// step 1.1 Let x = r + jn
|
||||
// Here "j" is either 0 or 1
|
||||
let x;
|
||||
if (use_second_candidate_key) {
|
||||
x = signature_r.add(field_order);
|
||||
} else {
|
||||
x = signature_r;
|
||||
}
|
||||
|
||||
// step 1.2 and 1.3 convert x to an elliptic curve point
|
||||
// Following formula in section 2.3.4 Octet-String-to-Elliptic-Curve-Point
|
||||
// Conversion
|
||||
const alpha = x.mul(x).mul(x).add(curve.a.mul(x)).add(curve.b).mod(
|
||||
field_modulus);
|
||||
const beta = alpha.powermod(FIELD_MODULUS_PLUS_ONE_DIVIDED_BY_FOUR,
|
||||
field_modulus);
|
||||
|
||||
// If beta is even but y isn't or
|
||||
// if beta is odd and y is even
|
||||
// then subtract beta from the field_modulus
|
||||
let y;
|
||||
const beta_is_even = beta.mod(2).equals(0);
|
||||
if (beta_is_even && !compressed_point_y_coord_is_even ||
|
||||
!beta_is_even && compressed_point_y_coord_is_even) {
|
||||
y = beta;
|
||||
} else {
|
||||
y = field_modulus.sub(beta);
|
||||
}
|
||||
|
||||
// generated_point_R is the point generated from x and y
|
||||
const generated_point_R = new sjcl.ecc.point(curve, x, y);
|
||||
|
||||
// step 1.4 check that R is valid and R x field_order !== infinity
|
||||
// TODO: add check for R x field_order === infinity
|
||||
if (!generated_point_R.isValidPoint()) {
|
||||
throw new sjcl.exception.corrupt(
|
||||
'point R. Not a valid point on the curve. Cannot recover public key');
|
||||
}
|
||||
|
||||
// step 1.5 Compute e from M
|
||||
const message_e = sjcl.bn.fromBits(hash_bits);
|
||||
const message_e_neg = new sjcl.bn(0).sub(message_e).mod(field_order);
|
||||
|
||||
// step 1.6 Compute Q = r^-1 (sR - eG)
|
||||
// console.log('r: ', signature_r);
|
||||
const signature_r_inv = signature_r.inverseMod(field_order);
|
||||
const public_key_point = generated_point_R.mult2(signature_s, message_e_neg,
|
||||
curve.G).mult(signature_r_inv);
|
||||
|
||||
// Validate public key point
|
||||
if (!public_key_point.isValidPoint()) {
|
||||
throw new sjcl.exception.corrupt('public_key_point. Not a valid point'
|
||||
+ ' on the curve. Cannot recover public key');
|
||||
}
|
||||
|
||||
// Verify that this public key matches the signature
|
||||
if (!verify_raw(curve, message_e, signature_r, signature_s,
|
||||
public_key_point)) {
|
||||
throw new sjcl.exception.corrupt('cannot recover public key');
|
||||
}
|
||||
|
||||
return public_key_point;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the recovery factor by trying all four
|
||||
* possibilities and figuring out which results in the
|
||||
* correct public key
|
||||
*
|
||||
* @param {sjcl.ecc.curve} curve
|
||||
* @param {sjcl.bn} r
|
||||
* @param {sjcl.bn} s
|
||||
* @param {bitArray} hash_bits
|
||||
* @param {sjcl.ecc.point} original_public_key_point
|
||||
* @returns {Number, 0-3} Recovery factor
|
||||
*/
|
||||
function calculateRecoveryFactor(curve, r, s, hash_bits,
|
||||
original_public_key_point) {
|
||||
|
||||
const original_public_key_point_bits = original_public_key_point.toBits();
|
||||
|
||||
// TODO: verify that it is possible for the recovery_factor to be 2 or 3,
|
||||
// we may only need 1 bit because the canonical signature might remove the
|
||||
// possibility of us needing to "use the second candidate key"
|
||||
for (let possible_factor = 0; possible_factor < 4; possible_factor++) {
|
||||
|
||||
let resulting_public_key_point;
|
||||
try {
|
||||
resulting_public_key_point = recoverPublicKeyPointFromSignature(
|
||||
curve, r, s, hash_bits, possible_factor);
|
||||
} catch (err) {
|
||||
// console.log(err, err.stack);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sjcl.bitArray.equal(resulting_public_key_point.toBits(),
|
||||
original_public_key_point_bits)) {
|
||||
return possible_factor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new sjcl.exception.bug(
|
||||
'unable to calculate recovery factor from signature');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the given hash such that the public key, prepending an extra byte
|
||||
* so that the public key will be recoverable from the signature
|
||||
*
|
||||
* @param {bitArray} hash - hash to sign
|
||||
* @param {Number} paranoia - minimum entropy required
|
||||
* @param {Number} k_for_testing - fixed k value to bypass PRNG
|
||||
* @returns {bitArray} Signature formatted as bitArray
|
||||
*/
|
||||
sjcl.ecc.ecdsa.secretKey.prototype.signWithRecoverablePublicKey = function(
|
||||
hash, paranoia, k_for_testing) {
|
||||
|
||||
const self = this;
|
||||
|
||||
// Convert hash to bits and determine encoding for output
|
||||
let hash_bits;
|
||||
if (typeof hash === 'object' && hash.length > 0
|
||||
&& typeof hash[0] === 'number') {
|
||||
hash_bits = hash;
|
||||
} else {
|
||||
throw new sjcl.exception.invalid('hash. Must be a bitArray');
|
||||
}
|
||||
|
||||
// Sign hash with standard, canonicalized method
|
||||
const standard_signature = self.sign(hash_bits, paranoia, k_for_testing);
|
||||
const canonical_signature = self.canonicalizeSignature(standard_signature);
|
||||
|
||||
// Extract r and s signature components from canonical signature
|
||||
const r_and_s = getRandSFromSignature(self._curve, canonical_signature);
|
||||
|
||||
// Rederive public key
|
||||
const public_key = self._curve.G.mult(sjcl.bn.fromBits(self.get()));
|
||||
|
||||
// Determine recovery factor based on which possible value
|
||||
// returns the correct public key
|
||||
const recovery_factor = calculateRecoveryFactor(self._curve, r_and_s.r,
|
||||
r_and_s.s, hash_bits, public_key);
|
||||
|
||||
// Prepend recovery_factor to signature and encode in DER
|
||||
// The value_to_prepend should be 4 bytes total
|
||||
const value_to_prepend = recovery_factor + 27;
|
||||
|
||||
const final_signature_bits = sjcl.bitArray.concat([value_to_prepend],
|
||||
canonical_signature);
|
||||
|
||||
// Return value in bits
|
||||
return final_signature_bits;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recover the public key from a signature created with the
|
||||
* signWithRecoverablePublicKey method in this module
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {bitArray} hash
|
||||
* @param {bitArray} signature
|
||||
* @param {sjcl.ecc.curve} [curve=sjcl.ecc.curves['k256']]
|
||||
* @returns {sjcl.ecc.ecdsa.publicKey} Public key
|
||||
*/
|
||||
sjcl.ecc.ecdsa.publicKey.recoverFromSignature = function(
|
||||
hash, signature, curve=sjcl.ecc.curves.k256) {
|
||||
|
||||
if (!signature || signature instanceof sjcl.ecc.curve) {
|
||||
throw new sjcl.exception.invalid(
|
||||
'must supply hash and signature to recover public key');
|
||||
}
|
||||
|
||||
// Convert hash to bits and determine encoding for output
|
||||
let hash_bits;
|
||||
if (typeof hash === 'object' && hash.length > 0
|
||||
&& typeof hash[0] === 'number') {
|
||||
hash_bits = hash;
|
||||
} else {
|
||||
throw new sjcl.exception.invalid('hash. Must be a bitArray');
|
||||
}
|
||||
|
||||
let signature_bits;
|
||||
if (typeof signature === 'object' && signature.length > 0
|
||||
&& typeof signature[0] === 'number') {
|
||||
signature_bits = signature;
|
||||
} else {
|
||||
throw new sjcl.exception.invalid('signature. Must be a bitArray');
|
||||
}
|
||||
|
||||
// Extract recovery_factor from first 4 bytes
|
||||
const recovery_factor = signature_bits[0] - 27;
|
||||
|
||||
if (recovery_factor < 0 || recovery_factor > 3) {
|
||||
throw new sjcl.exception.invalid(
|
||||
'signature. Signature must be generated with algorithm ' +
|
||||
'that prepends the recovery factor in order to recover the public key');
|
||||
}
|
||||
|
||||
// Separate r and s values
|
||||
const r_and_s = getRandSFromSignature(curve, signature_bits.slice(1));
|
||||
const signature_r = r_and_s.r;
|
||||
const signature_s = r_and_s.s;
|
||||
|
||||
// Recover public key using recovery_factor
|
||||
const recovered_public_key_point = recoverPublicKeyPointFromSignature(
|
||||
curve, signature_r, signature_s, hash_bits, recovery_factor);
|
||||
const recovered_public_key = new sjcl.ecc.ecdsa.publicKey(
|
||||
curve, recovered_public_key_point);
|
||||
|
||||
return recovered_public_key;
|
||||
};
|
||||
156
src/sjcl-custom/sjcl-extramath.js
Executable file
156
src/sjcl-custom/sjcl-extramath.js
Executable file
@@ -0,0 +1,156 @@
|
||||
/* eslint new-cap: [2, {newIsCapExceptions: ["bn"]}] */
|
||||
'use strict';
|
||||
var sjcl = require('sjcl');
|
||||
|
||||
sjcl.bn.ZERO = new sjcl.bn(0);
|
||||
|
||||
/* [ this / that , this % that ] */
|
||||
sjcl.bn.prototype.divRem = function(that) {
|
||||
if (typeof that !== 'object') {
|
||||
that = new this._class(that);
|
||||
}
|
||||
var thisa = this.abs(), thata = that.abs(), quot = new this._class(0),
|
||||
ci = 0;
|
||||
if (!thisa.greaterEquals(thata)) {
|
||||
return [new sjcl.bn(0), this.copy()];
|
||||
} else if (thisa.equals(thata)) {
|
||||
return [new sjcl.bn(1), new sjcl.bn(0)];
|
||||
}
|
||||
|
||||
for (; thisa.greaterEquals(thata); ci++) {
|
||||
thata.doubleM();
|
||||
}
|
||||
for (; ci > 0; ci--) {
|
||||
quot.doubleM();
|
||||
thata.halveM();
|
||||
if (thisa.greaterEquals(thata)) {
|
||||
quot.addM(1);
|
||||
thisa.subM(that).normalize();
|
||||
}
|
||||
}
|
||||
return [quot, thisa];
|
||||
};
|
||||
|
||||
/* this /= that (rounded to nearest int) */
|
||||
sjcl.bn.prototype.divRound = function(that) {
|
||||
var dr = this.divRem(that), quot = dr[0], rem = dr[1];
|
||||
|
||||
if (rem.doubleM().greaterEquals(that)) {
|
||||
quot.addM(1);
|
||||
}
|
||||
|
||||
return quot;
|
||||
};
|
||||
|
||||
/* this /= that (rounded down) */
|
||||
sjcl.bn.prototype.div = function(that) {
|
||||
var dr = this.divRem(that);
|
||||
return dr[0];
|
||||
};
|
||||
|
||||
sjcl.bn.prototype.sign = function() {
|
||||
return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1;
|
||||
};
|
||||
|
||||
/* -this */
|
||||
sjcl.bn.prototype.neg = function() {
|
||||
return sjcl.bn.ZERO.sub(this);
|
||||
};
|
||||
|
||||
/* |this| */
|
||||
sjcl.bn.prototype.abs = function() {
|
||||
if (this.sign() === -1) {
|
||||
return this.neg();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/* this >> that */
|
||||
sjcl.bn.prototype.shiftRight = function(that) {
|
||||
if (typeof that !== 'number') {
|
||||
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 (typeof that !== 'number') {
|
||||
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;
|
||||
};
|
||||
81
src/sjcl-custom/sjcl-secp256k1.js
Executable file
81
src/sjcl-custom/sjcl-secp256k1.js
Executable file
@@ -0,0 +1,81 @@
|
||||
/* eslint new-cap: [2, {newIsCapExceptions: ["pointJac"]}] */
|
||||
'use strict';
|
||||
var sjcl = require('sjcl');
|
||||
|
||||
// ----- for secp256k1 ------
|
||||
|
||||
sjcl.ecc.point.prototype.toBytesCompressed = function() {
|
||||
var header = this.y.mod(2).toString() === '0x0' ? 0x02 : 0x03;
|
||||
return [header].concat(sjcl.codec.bytes.fromBits(this.x.toBits()));
|
||||
};
|
||||
|
||||
// Replace point addition and doubling algorithms
|
||||
// NIST-P256 is a=-3, we need algorithms for a=0
|
||||
//
|
||||
// This is a custom point addition formula that
|
||||
// only works for a=-3 Jacobian curve. It's much
|
||||
// faster than the generic implementation
|
||||
sjcl.ecc.pointJac.prototype.add = function(T) {
|
||||
var self = this;
|
||||
if (self.curve !== T.curve) {
|
||||
throw ('sjcl.ecc.add(): Points must be on the same curve to add them!');
|
||||
}
|
||||
|
||||
if (self.isIdentity) {
|
||||
return T.toJac();
|
||||
} else if (T.isIdentity) {
|
||||
return self;
|
||||
}
|
||||
|
||||
var z1z1 = self.z.square();
|
||||
var h = T.x.mul(z1z1).subM(self.x);
|
||||
var s2 = T.y.mul(self.z).mul(z1z1);
|
||||
|
||||
if (h.equals(0)) {
|
||||
if (self.y.equals(T.y.mul(z1z1.mul(self.z)))) {
|
||||
// same point
|
||||
return self.doubl();
|
||||
}
|
||||
// inverses
|
||||
return new sjcl.ecc.pointJac(self.curve);
|
||||
}
|
||||
|
||||
var hh = h.square();
|
||||
var i = hh.copy().doubleM().doubleM();
|
||||
var j = h.mul(i);
|
||||
var r = s2.sub(self.y).doubleM();
|
||||
var v = self.x.mul(i);
|
||||
|
||||
var x = r.square().subM(j).subM(v.copy().doubleM());
|
||||
var y = r.mul(v.sub(x)).subM(self.y.mul(j).doubleM());
|
||||
var z = self.z.add(h).square().subM(z1z1).subM(hh);
|
||||
|
||||
return new sjcl.ecc.pointJac(this.curve, x, y, z);
|
||||
};
|
||||
|
||||
// This is a custom doubling algorithm that
|
||||
// only works for a=-3 Jacobian curve. It's much
|
||||
// faster than the generic implementation
|
||||
sjcl.ecc.pointJac.prototype.doubl = function() {
|
||||
if (this.isIdentity) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var a = this.x.square();
|
||||
var b = this.y.square();
|
||||
var c = b.square();
|
||||
var d = this.x.add(b).square().subM(a).subM(c).doubleM();
|
||||
var e = a.mul(3);
|
||||
var f = e.square();
|
||||
var x = f.sub(d.copy().doubleM());
|
||||
var y = e.mul(d.sub(x)).subM(c.doubleM().doubleM().doubleM());
|
||||
var z = this.z.mul(this.y).doubleM();
|
||||
return new sjcl.ecc.pointJac(this.curve, x, y, z);
|
||||
};
|
||||
|
||||
// DEPRECATED:
|
||||
// previously the c256 curve was overridden with the secp256k1 curve
|
||||
// since then, sjcl has been updated to support k256
|
||||
// this override exist to keep supporting the old c256 with k256 behavior
|
||||
// this will be removed in future release
|
||||
sjcl.ecc.curves.c256 = sjcl.ecc.curves.k256;
|
||||
47
src/sjcl-custom/sjcl-validecc.js
Normal file
47
src/sjcl-custom/sjcl-validecc.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/* eslint new-cap: [2, {newIsCapExceptions: ["corrupt"]}] */
|
||||
'use strict';
|
||||
var sjcl = require('sjcl');
|
||||
|
||||
sjcl.ecc.ecdsa.secretKey.prototype.sign = function(hash, paranoia,
|
||||
k_for_testing) {
|
||||
var R = this._curve.r,
|
||||
l = R.bitLength();
|
||||
|
||||
// k_for_testing should ONLY BE SPECIFIED FOR TESTING
|
||||
// specifying it will make the signature INSECURE
|
||||
var k;
|
||||
if (typeof k_for_testing === 'object' && k_for_testing.length > 0
|
||||
&& typeof k_for_testing[0] === 'number') {
|
||||
k = k_for_testing;
|
||||
} else if (typeof k_for_testing === 'string'
|
||||
&& /^[0-9a-fA-F]+$/.test(k_for_testing)) {
|
||||
k = sjcl.bn.fromBits(sjcl.codec.hex.toBits(k_for_testing));
|
||||
} else {
|
||||
// This is the only option that should be used in production
|
||||
k = sjcl.bn.random(R.sub(1), paranoia).add(1);
|
||||
}
|
||||
|
||||
var r = this._curve.G.mult(k).x.mod(R);
|
||||
var s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent))
|
||||
.mul(k.inverseMod(R)).mod(R);
|
||||
|
||||
return sjcl.bitArray.concat(r.toBits(l), s.toBits(l));
|
||||
};
|
||||
|
||||
sjcl.ecc.ecdsa.publicKey.prototype.verify = function(hash, rs) {
|
||||
var w = sjcl.bitArray;
|
||||
var R = this._curve.r;
|
||||
var l = R.bitLength();
|
||||
var r = sjcl.bn.fromBits(w.bitSlice(rs, 0, l));
|
||||
var s = sjcl.bn.fromBits(w.bitSlice(rs, l, 2 * l));
|
||||
var sInv = s.inverseMod(R);
|
||||
var hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R);
|
||||
var hA = r.mul(sInv).mod(R);
|
||||
var r2 = this._curve.G.mult2(hG, hA, this._point).x;
|
||||
|
||||
if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R)
|
||||
|| !r2.equals(r)) {
|
||||
throw (new sjcl.exception.corrupt('signature didn\'t check out'));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
1467
src/transaction.js
Normal file
1467
src/transaction.js
Normal file
File diff suppressed because it is too large
Load Diff
739
src/transactionmanager.js
Normal file
739
src/transactionmanager.js
Normal file
@@ -0,0 +1,739 @@
|
||||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
const assert = require('assert');
|
||||
const async = require('async');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const Transaction = require('./transaction').Transaction;
|
||||
const RippleError = require('./rippleerror').RippleError;
|
||||
const PendingQueue = require('./transactionqueue').TransactionQueue;
|
||||
const log = require('./log').internal.sub('transactionmanager');
|
||||
|
||||
/**
|
||||
* @constructor TransactionManager
|
||||
* @param {Account} account
|
||||
*/
|
||||
|
||||
function TransactionManager(account) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
const self = this;
|
||||
|
||||
this._account = account;
|
||||
this._accountID = account._account_id;
|
||||
this._remote = account._remote;
|
||||
this._nextSequence = undefined;
|
||||
this._maxFee = this._remote.max_fee;
|
||||
this._maxAttempts = this._remote.max_attempts;
|
||||
this._submissionTimeout = this._remote.submission_timeout;
|
||||
this._lastLedgerOffset = this._remote.last_ledger_offset;
|
||||
this._pending = new PendingQueue();
|
||||
|
||||
this._account.on('transaction-outbound', function(res) {
|
||||
self._transactionReceived(res);
|
||||
});
|
||||
|
||||
this._remote.on('load_changed', function(load) {
|
||||
self._adjustFees(load);
|
||||
});
|
||||
|
||||
function updatePendingStatus(ledger) {
|
||||
self._updatePendingStatus(ledger);
|
||||
}
|
||||
|
||||
this._remote.on('ledger_closed', updatePendingStatus);
|
||||
|
||||
function handleReconnect() {
|
||||
self._handleReconnect(function() {
|
||||
// Handle reconnect, account_tx procedure first, before
|
||||
// hooking back into ledger_closed
|
||||
self._remote.on('ledger_closed', updatePendingStatus);
|
||||
});
|
||||
}
|
||||
|
||||
this._remote.on('disconnect', function() {
|
||||
self._remote.removeListener('ledger_closed', updatePendingStatus);
|
||||
self._remote.once('connect', handleReconnect);
|
||||
});
|
||||
|
||||
// Query server for next account transaction sequence
|
||||
this._loadSequence();
|
||||
}
|
||||
|
||||
util.inherits(TransactionManager, EventEmitter);
|
||||
|
||||
TransactionManager._isNoOp = function(transaction) {
|
||||
return (typeof transaction === 'object')
|
||||
&& (typeof transaction.tx_json === 'object')
|
||||
&& (transaction.tx_json.TransactionType === 'AccountSet')
|
||||
&& (transaction.tx_json.Flags === 0);
|
||||
};
|
||||
|
||||
TransactionManager._isRemoteError = function(error) {
|
||||
return (typeof error === 'object')
|
||||
&& (error.error === 'remoteError')
|
||||
&& (typeof error.remote === 'object');
|
||||
};
|
||||
|
||||
TransactionManager._isNotFound = function(error) {
|
||||
return TransactionManager._isRemoteError(error)
|
||||
&& /^(txnNotFound|transactionNotFound)$/.test(error.remote.error);
|
||||
};
|
||||
|
||||
TransactionManager._isTooBusy = function(error) {
|
||||
return TransactionManager._isRemoteError(error)
|
||||
&& (error.remote.error === 'tooBusy');
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize transactions received from account transaction stream and
|
||||
* account_tx
|
||||
*
|
||||
* @param {Transaction}
|
||||
* @return {Transaction} normalized
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.normalizeTransaction = function(tx) {
|
||||
let transaction = { };
|
||||
const keys = Object.keys(tx);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i];
|
||||
switch (k) {
|
||||
case 'transaction':
|
||||
// Account transaction stream
|
||||
transaction.tx_json = tx[k];
|
||||
break;
|
||||
case 'tx':
|
||||
// account_tx response
|
||||
transaction.engine_result = tx.meta.TransactionResult;
|
||||
transaction.result = transaction.engine_result;
|
||||
transaction.tx_json = tx[k];
|
||||
transaction.hash = tx[k].hash;
|
||||
transaction.ledger_index = tx[k].ledger_index;
|
||||
transaction.type = 'transaction';
|
||||
transaction.validated = tx.validated;
|
||||
break;
|
||||
case 'meta':
|
||||
case 'metadata':
|
||||
transaction.metadata = tx[k];
|
||||
break;
|
||||
case 'mmeta':
|
||||
// Don't copy mmeta
|
||||
break;
|
||||
default:
|
||||
transaction[k] = tx[k];
|
||||
}
|
||||
}
|
||||
|
||||
return transaction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle received transaction from two possible sources
|
||||
*
|
||||
* + Account transaction stream (normal operation)
|
||||
* + account_tx (after reconnect)
|
||||
*
|
||||
* @param {Object} transaction
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._transactionReceived = function(tx) {
|
||||
const transaction = TransactionManager.normalizeTransaction(tx);
|
||||
|
||||
if (!transaction.validated) {
|
||||
// Transaction has not been validated
|
||||
return;
|
||||
}
|
||||
|
||||
if (transaction.tx_json.Account !== this._accountID) {
|
||||
// Received transaction's account does not match
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._remote.trace) {
|
||||
log.info('transaction received:', transaction.tx_json);
|
||||
}
|
||||
|
||||
this._pending.addReceivedSequence(transaction.tx_json.Sequence);
|
||||
|
||||
const hash = transaction.tx_json.hash;
|
||||
const submission = this._pending.getSubmission(hash);
|
||||
|
||||
if (!(submission instanceof Transaction)) {
|
||||
// The received transaction does not correlate to one submitted
|
||||
this._pending.addReceivedId(hash, transaction);
|
||||
return;
|
||||
}
|
||||
|
||||
// ND: A `success` handler will `finalize` this later
|
||||
switch (transaction.engine_result) {
|
||||
case 'tesSUCCESS':
|
||||
submission.emit('success', transaction);
|
||||
break;
|
||||
default:
|
||||
submission.emit('error', transaction);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adjust pending transactions' fees in real-time. This does not resubmit
|
||||
* pending transactions; they will be resubmitted periodically with an updated
|
||||
* fee (and as a consequence, a new transaction ID) if not already validated
|
||||
*
|
||||
* ND: note, that `Fee` is a component of a transactionID
|
||||
*
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._adjustFees = function() {
|
||||
const self = this;
|
||||
|
||||
if (!this._remote.local_fee) {
|
||||
return;
|
||||
}
|
||||
|
||||
function maxFeeExceeded(transaction) {
|
||||
// Don't err until attempting to resubmit
|
||||
transaction.once('presubmit', function() {
|
||||
transaction.emit('error', 'tejMaxFeeExceeded');
|
||||
});
|
||||
}
|
||||
|
||||
this._pending.forEach(function(transaction) {
|
||||
if (transaction._setFixedFee) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldFee = transaction.tx_json.Fee;
|
||||
const newFee = transaction._computeFee();
|
||||
|
||||
if (Number(newFee) > self._maxFee) {
|
||||
// Max transaction fee exceeded, abort submission
|
||||
maxFeeExceeded(transaction);
|
||||
return;
|
||||
}
|
||||
|
||||
transaction.tx_json.Fee = newFee;
|
||||
transaction.emit('fee_adjusted', oldFee, newFee);
|
||||
|
||||
if (self._remote.trace) {
|
||||
log.info('fee adjusted:', transaction.tx_json, oldFee, newFee);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get pending transactions
|
||||
*
|
||||
* @return {Array} pending transactions
|
||||
*/
|
||||
|
||||
TransactionManager.prototype.getPending = function() {
|
||||
return this._pending;
|
||||
};
|
||||
|
||||
/**
|
||||
* Legacy code. Update transaction status after excessive ledgers pass. One of
|
||||
* either "missing" or "lost"
|
||||
*
|
||||
* @param {Object} ledger data
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._updatePendingStatus = function(ledger) {
|
||||
assert.strictEqual(typeof ledger, 'object');
|
||||
assert.strictEqual(typeof ledger.ledger_index, 'number');
|
||||
|
||||
this._pending.forEach(function(transaction) {
|
||||
if (transaction.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (ledger.ledger_index - transaction.submitIndex) {
|
||||
case 4:
|
||||
transaction.emit('missing', ledger);
|
||||
break;
|
||||
case 8:
|
||||
transaction.emit('lost', ledger);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ledger.ledger_index > transaction.tx_json.LastLedgerSequence) {
|
||||
// Transaction must fail
|
||||
transaction.emit('error', new RippleError(
|
||||
'tejMaxLedger', 'Transaction LastLedgerSequence exceeded'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Fill an account transaction sequence
|
||||
TransactionManager.prototype._fillSequence = function(tx, callback) {
|
||||
const self = this;
|
||||
|
||||
function submitFill(sequence, fCallback) {
|
||||
let fillTransaction = self._remote.createTransaction('AccountSet', {
|
||||
account: self._accountID
|
||||
});
|
||||
fillTransaction.tx_json.Sequence = sequence;
|
||||
|
||||
// Secrets may be set on a per-transaction basis
|
||||
if (tx._secret) {
|
||||
fillTransaction.secret(tx._secret);
|
||||
}
|
||||
|
||||
fillTransaction.once('submitted', fCallback);
|
||||
fillTransaction.submit();
|
||||
}
|
||||
|
||||
function sequenceLoaded(err, sequence) {
|
||||
if (typeof sequence !== 'number') {
|
||||
log.info('fill sequence: failed to fetch account transaction sequence');
|
||||
return callback();
|
||||
}
|
||||
|
||||
const sequenceDiff = tx.tx_json.Sequence - sequence;
|
||||
let submitted = 0;
|
||||
|
||||
async.whilst(
|
||||
function() {
|
||||
return submitted < sequenceDiff;
|
||||
},
|
||||
function(asyncCallback) {
|
||||
submitFill(sequence, function(res) {
|
||||
++submitted;
|
||||
if (res.engine_result === 'tesSUCCESS') {
|
||||
self.emit('sequence_filled', err);
|
||||
}
|
||||
asyncCallback();
|
||||
});
|
||||
},
|
||||
function() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this._loadSequence(sequenceLoaded);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load account transaction sequence
|
||||
*
|
||||
* @param [Function] callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._loadSequence = function(callback_) {
|
||||
const self = this;
|
||||
const callback = (typeof callback_ === 'function')
|
||||
? callback_
|
||||
: function() {};
|
||||
|
||||
function sequenceLoaded(err, sequence) {
|
||||
if (err || typeof sequence !== 'number') {
|
||||
if (self._remote.trace) {
|
||||
log.info('error requesting account transaction sequence', err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self._nextSequence = sequence;
|
||||
self.emit('sequence_loaded', sequence);
|
||||
callback(err, sequence);
|
||||
}
|
||||
|
||||
this._account.getNextSequence(sequenceLoaded);
|
||||
};
|
||||
|
||||
/**
|
||||
* On reconnect, load account_tx in case a pending transaction succeeded while
|
||||
* disconnected
|
||||
*
|
||||
* @param [Function] callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._handleReconnect = function(callback_) {
|
||||
const self = this;
|
||||
const callback = (typeof callback_ === 'function')
|
||||
? callback_
|
||||
: function() {};
|
||||
|
||||
if (!this._pending.length()) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
function handleTransactions(err, transactions) {
|
||||
if (err || typeof transactions !== 'object') {
|
||||
if (self._remote.trace) {
|
||||
log.info('error requesting account_tx', err);
|
||||
}
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(transactions.transactions)) {
|
||||
// Treat each transaction in account transaction history as received
|
||||
transactions.transactions.forEach(self._transactionReceived, self);
|
||||
}
|
||||
|
||||
callback();
|
||||
|
||||
self._loadSequence(function() {
|
||||
// Resubmit pending transactions after sequence is loaded
|
||||
self._resubmit();
|
||||
});
|
||||
}
|
||||
|
||||
const options = {
|
||||
account: this._accountID,
|
||||
ledger_index_min: this._pending.getMinLedger(),
|
||||
ledger_index_max: -1,
|
||||
binary: true,
|
||||
parseBinary: true,
|
||||
limit: 20
|
||||
};
|
||||
|
||||
this._remote.requestAccountTx(options, handleTransactions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for specified number of ledgers to pass
|
||||
*
|
||||
* @param {Number} ledgers
|
||||
* @param {Function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._waitLedgers = function(ledgers, callback) {
|
||||
assert.strictEqual(typeof ledgers, 'number');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if (ledgers < 1) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
const self = this;
|
||||
let closes = 0;
|
||||
|
||||
function ledgerClosed() {
|
||||
if (++closes === ledgers) {
|
||||
self._remote.removeListener('ledger_closed', ledgerClosed);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
this._remote.on('ledger_closed', ledgerClosed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resubmit pending transactions. If a transaction is specified, it will be
|
||||
* resubmitted. Otherwise, all pending transactions will be resubmitted
|
||||
*
|
||||
* @param [Number] ledgers to wait before resubmitting
|
||||
* @param [Transaction] pending transactions to resubmit
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._resubmit = function(ledgers_, pending_) {
|
||||
const self = this;
|
||||
|
||||
let ledgers = ledgers_;
|
||||
let pending = pending_;
|
||||
|
||||
if (arguments.length === 1) {
|
||||
pending = ledgers;
|
||||
ledgers = 0;
|
||||
}
|
||||
|
||||
ledgers = ledgers || 0;
|
||||
pending = pending instanceof Transaction
|
||||
? [pending]
|
||||
: this.getPending().getQueue();
|
||||
|
||||
function resubmitTransaction(transaction, next) {
|
||||
if (!transaction || transaction.finalized) {
|
||||
// Transaction has been finalized, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// Find ID within cache of received (validated) transaction IDs
|
||||
const received = transaction.findId(self._pending._idCache);
|
||||
|
||||
if (received) {
|
||||
switch (received.engine_result) {
|
||||
case 'tesSUCCESS':
|
||||
transaction.emit('success', received);
|
||||
break;
|
||||
default:
|
||||
transaction.emit('error', received);
|
||||
}
|
||||
}
|
||||
|
||||
while (self._pending.hasSequence(transaction.tx_json.Sequence)) {
|
||||
// Sequence number has been consumed by another transaction
|
||||
transaction.tx_json.Sequence += 1;
|
||||
|
||||
if (self._remote.trace) {
|
||||
log.info('incrementing sequence:', transaction.tx_json);
|
||||
}
|
||||
}
|
||||
|
||||
if (self._remote.trace) {
|
||||
log.info('resubmit:', transaction.tx_json);
|
||||
}
|
||||
|
||||
transaction.once('submitted', function(m) {
|
||||
transaction.emit('resubmitted', m);
|
||||
next();
|
||||
});
|
||||
|
||||
self._request(transaction);
|
||||
}
|
||||
|
||||
this._waitLedgers(ledgers, function() {
|
||||
async.eachSeries(pending, resubmitTransaction);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare submit request
|
||||
*
|
||||
* @param {Transaction} transaction to submit
|
||||
* @return {Request} submit request
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._prepareRequest = function(tx) {
|
||||
const submitRequest = this._remote.requestSubmit();
|
||||
|
||||
if (this._remote.local_signing) {
|
||||
tx.sign();
|
||||
|
||||
const serialized = tx.serialize();
|
||||
submitRequest.tx_blob(serialized.to_hex());
|
||||
|
||||
const hash = tx.hash(null, null, serialized);
|
||||
tx.addId(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);
|
||||
}
|
||||
|
||||
return submitRequest;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send `submit` request, handle response
|
||||
*
|
||||
* @param {Transaction} transaction to submit
|
||||
* @api private
|
||||
*/
|
||||
|
||||
TransactionManager.prototype._request = function(tx) {
|
||||
const self = this;
|
||||
const remote = this._remote;
|
||||
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tx.attempts > this._maxAttempts) {
|
||||
tx.emit('error', new RippleError('tejAttemptsExceeded'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (tx.attempts > 0 && !remote.local_signing) {
|
||||
const errMessage = 'Automatic resubmission requires local signing';
|
||||
tx.emit('error', new RippleError('tejLocalSigningRequired', errMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
if (remote.trace) {
|
||||
log.info('submit transaction:', tx.tx_json);
|
||||
}
|
||||
|
||||
function transactionFailed(message) {
|
||||
if (message.engine_result === 'tefPAST_SEQ') {
|
||||
// Transaction may succeed after Sequence is updated
|
||||
self._resubmit(1, tx);
|
||||
}
|
||||
}
|
||||
|
||||
function transactionRetry() {
|
||||
// XXX This may no longer be necessary. Instead, update sequence numbers
|
||||
// after a transaction fails definitively
|
||||
self._fillSequence(tx, function() {
|
||||
self._resubmit(1, tx);
|
||||
});
|
||||
}
|
||||
|
||||
function transactionFailedLocal(message) {
|
||||
if (message.engine_result === 'telINSUF_FEE_P') {
|
||||
// Transaction may succeed after Fee is updated
|
||||
self._resubmit(1, tx);
|
||||
}
|
||||
}
|
||||
|
||||
function submissionError(error) {
|
||||
// Either a tem-class error or generic server error such as tooBusy. This
|
||||
// should be a definitive failure
|
||||
if (TransactionManager._isTooBusy(error)) {
|
||||
self._resubmit(1, tx);
|
||||
} else {
|
||||
self._nextSequence--;
|
||||
tx.emit('error', error);
|
||||
}
|
||||
}
|
||||
|
||||
function submitted(message) {
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ND: If for some unknown reason our hash wasn't computed correctly this
|
||||
// is an extra measure.
|
||||
if (message.tx_json && message.tx_json.hash) {
|
||||
tx.addId(message.tx_json.hash);
|
||||
}
|
||||
|
||||
message.result = message.engine_result || '';
|
||||
|
||||
tx.result = message;
|
||||
tx.responses += 1;
|
||||
|
||||
if (remote.trace) {
|
||||
log.info('submit response:', message);
|
||||
}
|
||||
|
||||
tx.emit('submitted', message);
|
||||
|
||||
switch (message.result.slice(0, 3)) {
|
||||
case 'tes':
|
||||
tx.emit('proposed', message);
|
||||
break;
|
||||
case 'tec':
|
||||
break;
|
||||
case 'ter':
|
||||
transactionRetry(message);
|
||||
break;
|
||||
case 'tef':
|
||||
transactionFailed(message);
|
||||
break;
|
||||
case 'tel':
|
||||
transactionFailedLocal(message);
|
||||
break;
|
||||
default:
|
||||
// tem
|
||||
submissionError(message);
|
||||
}
|
||||
}
|
||||
|
||||
function requestTimeout() {
|
||||
// ND: What if the response is just slow and we get a response that
|
||||
// `submitted` above will cause to have concurrent resubmit logic streams?
|
||||
// It's simpler to just mute handlers and look out for finalized
|
||||
// `transaction` messages.
|
||||
if (tx.finalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
tx.emit('timeout');
|
||||
|
||||
if (remote.isConnected()) {
|
||||
if (remote.trace) {
|
||||
log.info('timeout:', tx.tx_json);
|
||||
}
|
||||
self._resubmit(1, tx);
|
||||
}
|
||||
}
|
||||
|
||||
tx.submitIndex = this._remote._ledger_current_index;
|
||||
|
||||
if (tx.attempts === 0) {
|
||||
tx.initialSubmitIndex = tx.submitIndex;
|
||||
}
|
||||
|
||||
if (!tx._setLastLedger) {
|
||||
// Honor LastLedgerSequence set with tx.lastLedger()
|
||||
tx.tx_json.LastLedgerSequence = tx.initialSubmitIndex
|
||||
+ this._lastLedgerOffset;
|
||||
}
|
||||
|
||||
tx.lastLedgerSequence = tx.tx_json.LastLedgerSequence;
|
||||
|
||||
if (remote.local_signing) {
|
||||
tx.sign();
|
||||
}
|
||||
|
||||
const submitRequest = this._prepareRequest(tx);
|
||||
submitRequest.once('error', submitted);
|
||||
submitRequest.once('success', submitted);
|
||||
|
||||
tx.emit('presubmit');
|
||||
|
||||
submitRequest.broadcast().request();
|
||||
tx.attempts++;
|
||||
|
||||
tx.emit('postsubmit');
|
||||
|
||||
submitRequest.timeout(self._submissionTimeout, requestTimeout);
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point for TransactionManager submission
|
||||
*
|
||||
* @param {Transaction} tx
|
||||
*/
|
||||
|
||||
TransactionManager.prototype.submit = function(tx) {
|
||||
const self = this;
|
||||
|
||||
if (typeof this._nextSequence !== 'number') {
|
||||
// If sequence number is not yet known, defer until it is.
|
||||
this.once('sequence_loaded', function() {
|
||||
self.submit(tx);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (tx.finalized) {
|
||||
// Finalized transactions must stop all activity
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof tx.tx_json.Sequence !== 'number') {
|
||||
// Honor manually-set sequences
|
||||
tx.tx_json.Sequence = this._nextSequence++;
|
||||
}
|
||||
|
||||
tx.once('cleanup', function() {
|
||||
self.getPending().remove(tx);
|
||||
});
|
||||
|
||||
if (!tx.complete()) {
|
||||
this._nextSequence--;
|
||||
return;
|
||||
}
|
||||
|
||||
// ND: this is the ONLY place we put the tx into the queue. The
|
||||
// TransactionQueue queue is merely a list, so any mutations to tx._hash
|
||||
// will cause subsequent look ups (eg. inside 'transaction-outbound'
|
||||
// validated transaction clearing) to fail.
|
||||
this._pending.push(tx);
|
||||
this._request(tx);
|
||||
};
|
||||
|
||||
exports.TransactionManager = TransactionManager;
|
||||
164
src/transactionqueue.js
Normal file
164
src/transactionqueue.js
Normal file
@@ -0,0 +1,164 @@
|
||||
'use strict';
|
||||
|
||||
var lodash = require('lodash');
|
||||
var LRU = require('lru-cache');
|
||||
var Transaction = require('./transaction').Transaction;
|
||||
|
||||
/**
|
||||
* Manager for pending transactions
|
||||
*/
|
||||
|
||||
function TransactionQueue() {
|
||||
this._queue = [ ];
|
||||
this._idCache = new LRU({max: 200});
|
||||
this._sequenceCache = new LRU({max: 200});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store received (validated) sequence
|
||||
*
|
||||
* @param {Number} sequence
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.addReceivedSequence = function(sequence) {
|
||||
this._sequenceCache.set(String(sequence), true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that sequence number has been consumed by a validated
|
||||
* transaction
|
||||
*
|
||||
* @param {Number} sequence
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.hasSequence = function(sequence) {
|
||||
return this._sequenceCache.has(String(sequence));
|
||||
};
|
||||
|
||||
/**
|
||||
* Store received (validated) ID transaction
|
||||
*
|
||||
* @param {String} transaction id
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.addReceivedId = function(id, transaction) {
|
||||
this._idCache.set(id, transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get received (validated) transaction by ID
|
||||
*
|
||||
* @param {String} transaction id
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.getReceived = function(id) {
|
||||
return this._idCache.get(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a submitted transaction by ID. Transactions
|
||||
* may have multiple associated IDs.
|
||||
*
|
||||
* @param {String} transaction id
|
||||
* @return {Transaction}
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.getSubmission = function(id) {
|
||||
return lodash.find(this._queue, function(tx) {
|
||||
return lodash.contains(tx.submittedIDs, id);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get earliest ledger in the pending queue
|
||||
*
|
||||
* @return {Number} ledger
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.getMinLedger = function() {
|
||||
if (this.length() < 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var result = Infinity;
|
||||
|
||||
for (var i = 0; i < this.length(); i++) {
|
||||
if (this._queue[i].initialSubmitIndex < result) {
|
||||
result = this._queue[i].initialSubmitIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isFinite(result)) {
|
||||
result = -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a transaction from the queue
|
||||
*
|
||||
* @param {String|Transaction} transaction or id
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.remove = function(tx) {
|
||||
// ND: We are just removing the Transaction by identity
|
||||
var i = this._queue.length;
|
||||
|
||||
if (typeof tx === 'string') {
|
||||
tx = this.getSubmission(tx);
|
||||
}
|
||||
|
||||
if (!(tx instanceof Transaction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (i--) {
|
||||
if (this._queue[i] === tx) {
|
||||
this._queue.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a transaction to pending queue
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.push = function(tx) {
|
||||
this._queue.push(tx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterate over pending transactions
|
||||
*
|
||||
* @param {Function} iterator
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.forEach = function(fn) {
|
||||
this._queue.forEach(fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Number} length of pending queue
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.length =
|
||||
TransactionQueue.prototype.getLength = function() {
|
||||
return this._queue.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Array} pending queue
|
||||
*/
|
||||
|
||||
TransactionQueue.prototype.getQueue = function() {
|
||||
return this._queue;
|
||||
};
|
||||
|
||||
exports.TransactionQueue = TransactionQueue;
|
||||
273
src/uint.js
Normal file
273
src/uint.js
Normal file
@@ -0,0 +1,273 @@
|
||||
'use strict';
|
||||
|
||||
/*eslint new-cap: 1*/
|
||||
|
||||
var utils = require('./utils');
|
||||
var sjcl = utils.sjcl;
|
||||
|
||||
//
|
||||
// Abstract UInt class
|
||||
//
|
||||
// Base class for UInt classes
|
||||
//
|
||||
|
||||
function UInt() {
|
||||
// Internal form: NaN or sjcl.bn
|
||||
this._value = NaN;
|
||||
this._update();
|
||||
}
|
||||
|
||||
UInt.json_rewrite = function(j, opts) {
|
||||
return this.from_json(j).to_json(opts);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_generic = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
return (new this()).parse_generic(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_hex = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
return (new this()).parse_hex(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_json = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
return (new this()).parse_json(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bits = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
return (new this()).parse_bits(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bytes = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
return (new this()).parse_bytes(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bn = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
return (new this()).parse_bn(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_number = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
return (new this()).parse_number(j);
|
||||
};
|
||||
|
||||
UInt.is_valid = function(j) {
|
||||
return this.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
UInt.prototype.clone = function() {
|
||||
return this.copyTo(new this.constructor());
|
||||
};
|
||||
|
||||
// Returns copy.
|
||||
UInt.prototype.copyTo = function(d) {
|
||||
d._value = this._value;
|
||||
|
||||
if (this._version_byte !== undefined) {
|
||||
d._version_byte = this._version_byte;
|
||||
}
|
||||
|
||||
if (typeof d._update === 'function') {
|
||||
d._update();
|
||||
}
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
UInt.prototype.equals = function(d) {
|
||||
return this.is_valid() && d.is_valid() && this._value.equals(d._value);
|
||||
};
|
||||
|
||||
UInt.prototype.is_valid = function() {
|
||||
return this._value instanceof sjcl.bn;
|
||||
};
|
||||
|
||||
UInt.prototype.is_zero = function() {
|
||||
return this.is_valid() && this._value.equals(new sjcl.bn(0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Update any derivative values.
|
||||
*
|
||||
* This allows subclasses to maintain caches of any data that they derive from
|
||||
* the main _value. For example, the Currency class keeps the currency type, the
|
||||
* currency code and other information about the currency cached.
|
||||
*
|
||||
* The reason for keeping this mechanism in this class is so every subclass can
|
||||
* call it whenever it modifies the internal state.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
UInt.prototype._update = function() {
|
||||
// Nothing to do by default. Subclasses will override this.
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt.prototype.parse_generic = function(j) {
|
||||
switch (j) {
|
||||
case undefined:
|
||||
case '0':
|
||||
case this.constructor.STR_ZERO:
|
||||
case this.constructor.ACCOUNT_ZERO:
|
||||
case this.constructor.HEX_ZERO:
|
||||
this._value = new sjcl.bn(0);
|
||||
break;
|
||||
|
||||
case '1':
|
||||
case this.constructor.STR_ONE:
|
||||
case this.constructor.ACCOUNT_ONE:
|
||||
case this.constructor.HEX_ONE:
|
||||
this._value = new sjcl.bn(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (typeof j !== 'string') {
|
||||
this._value = NaN;
|
||||
} else if (this.constructor.width === j.length) {
|
||||
var hex = utils.arrayToHex(utils.stringToArray(j));
|
||||
this._value = new sjcl.bn(hex, 16);
|
||||
} else if ((this.constructor.width * 2) === j.length) {
|
||||
// XXX Check char set!
|
||||
this._value = new sjcl.bn(j, 16);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_hex = function(j) {
|
||||
if (typeof j === 'string' && j.length === (this.constructor.width * 2)) {
|
||||
this._value = new sjcl.bn(j, 16);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_bits = function(j) {
|
||||
if (sjcl.bitArray.bitLength(j) !== this.constructor.width * 8) {
|
||||
this._value = NaN;
|
||||
} else {
|
||||
this._value = sjcl.bn.fromBits(j);
|
||||
// var bytes = sjcl.codec.bytes.fromBits(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;
|
||||
} else {
|
||||
var bits = sjcl.codec.bytes.toBits(j);
|
||||
this._value = sjcl.bn.fromBits(bits);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
UInt.prototype.parse_json = UInt.prototype.parse_hex;
|
||||
|
||||
UInt.prototype.parse_bn = function(j) {
|
||||
if ((j instanceof sjcl.bn) &&
|
||||
j.bitLength() <= this.constructor.width * 8) {
|
||||
this._value = new sjcl.bn(j);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_number = function(j) {
|
||||
this._value = NaN;
|
||||
|
||||
if (typeof j === 'number' && isFinite(j) && j >= 0) {
|
||||
this._value = new sjcl.bn(j);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Convert from internal form.
|
||||
UInt.prototype.to_bytes = function() {
|
||||
if (!this.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
return sjcl.codec.bytes.fromBits(this.to_bits());
|
||||
};
|
||||
|
||||
UInt.prototype.to_hex = function() {
|
||||
if (!this.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
|
||||
};
|
||||
|
||||
UInt.prototype.to_json = UInt.prototype.to_hex;
|
||||
|
||||
UInt.prototype.to_bits = function() {
|
||||
if (!this.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._value.toBits(this.constructor.width * 8);
|
||||
};
|
||||
|
||||
UInt.prototype.to_bn = function() {
|
||||
if (!this.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var bits = this.to_bits();
|
||||
|
||||
return sjcl.bn.fromBits(bits);
|
||||
};
|
||||
|
||||
exports.UInt = UInt;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
25
src/uint128.js
Normal file
25
src/uint128.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
var utils = require('./utils');
|
||||
var extend = require('extend');
|
||||
var UInt = require('./uint').UInt;
|
||||
|
||||
//
|
||||
// UInt128 support
|
||||
//
|
||||
|
||||
var UInt128 = extend(function() {
|
||||
this._value = NaN;
|
||||
}, UInt);
|
||||
|
||||
UInt128.width = 16;
|
||||
UInt128.prototype = Object.create(extend({}, UInt.prototype));
|
||||
UInt128.prototype.constructor = UInt128;
|
||||
|
||||
var HEX_ZERO = UInt128.HEX_ZERO = '00000000000000000000000000000000';
|
||||
var HEX_ONE = UInt128.HEX_ONE = '00000000000000000000000000000000';
|
||||
|
||||
UInt128.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
UInt128.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
exports.UInt128 = UInt128;
|
||||
98
src/uint160.js
Normal file
98
src/uint160.js
Normal file
@@ -0,0 +1,98 @@
|
||||
'use strict';
|
||||
|
||||
var utils = require('./utils');
|
||||
var extend = require('extend');
|
||||
|
||||
var UInt = require('./uint').UInt;
|
||||
var Base = require('./base').Base;
|
||||
|
||||
//
|
||||
// UInt160 support
|
||||
//
|
||||
|
||||
var UInt160 = extend(function() {
|
||||
this._value = NaN;
|
||||
this._version_byte = undefined;
|
||||
this._update();
|
||||
}, UInt);
|
||||
|
||||
UInt160.width = 20;
|
||||
UInt160.prototype = Object.create(extend({}, UInt.prototype));
|
||||
UInt160.prototype.constructor = UInt160;
|
||||
|
||||
var HEX_ZERO = UInt160.HEX_ZERO = '0000000000000000000000000000000000000000';
|
||||
var HEX_ONE = UInt160.HEX_ONE = '0000000000000000000000000000000000000001';
|
||||
|
||||
UInt160.ACCOUNT_ZERO = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp';
|
||||
UInt160.ACCOUNT_ONE = 'rrrrrrrrrrrrrrrrrrrrBZbvji';
|
||||
UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
UInt160.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
UInt160.prototype.set_version = function(j) {
|
||||
this._version_byte = j;
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt160.prototype.get_version = function() {
|
||||
return this._version_byte;
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt160.prototype.parse_json = function(j) {
|
||||
if (typeof j === 'number' && !isNaN(j)) {
|
||||
// Allow raw numbers - DEPRECATED
|
||||
// This is used mostly by the test suite and is supported
|
||||
// as a legacy feature only. DO NOT RELY ON THIS BEHAVIOR.
|
||||
this.parse_number(j);
|
||||
this._version_byte = Base.VER_ACCOUNT_ID;
|
||||
} else if (typeof j !== 'string') {
|
||||
this._value = NaN;
|
||||
} else if (j[0] === 'r') {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
this._version_byte = Base.VER_ACCOUNT_ID;
|
||||
} else {
|
||||
this.parse_hex(j);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt160.prototype.parse_generic = function(j) {
|
||||
UInt.prototype.parse_generic.call(this, j);
|
||||
|
||||
if (isNaN(this._value)) {
|
||||
if ((typeof j === 'string') && j[0] === 'r') {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
}
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// XXX Json form should allow 0 and 1, C++ doesn't currently allow it.
|
||||
UInt160.prototype.to_json = function(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
if (this.is_valid()) {
|
||||
// If this value has a type, return a Base58 encoded string.
|
||||
if (typeof this._version_byte === 'number') {
|
||||
var output = Base.encode_check(this._version_byte, this.to_bytes());
|
||||
|
||||
if (opts.gateways && output in opts.gateways) {
|
||||
output = opts.gateways[output];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
return this.to_hex();
|
||||
}
|
||||
return NaN;
|
||||
};
|
||||
|
||||
exports.UInt160 = UInt160;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user