binary-parser.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. const { coreTypes } = require('../dist/types')
  2. const Decimal = require('decimal.js')
  3. const { encodeAccountID } = require('ripple-address-codec')
  4. const { binary } = require('../dist/coretypes')
  5. const { Amount, Hash160 } = coreTypes
  6. const { makeParser, readJSON } = binary
  7. const { Field, TransactionType } = require('./../dist/enums')
  8. const { parseHexOnly, hexOnly, loadFixture } = require('./utils')
  9. const fixtures = loadFixture('data-driven-tests.json')
  10. const { BytesList } = require('../dist/serdes/binary-serializer')
  11. const { Buffer } = require('buffer/')
  12. const __ = hexOnly
  13. function toJSON(v) {
  14. return v.toJSON ? v.toJSON() : v
  15. }
  16. function assertEqualAmountJSON(actual, expected) {
  17. expect(typeof actual === typeof expected).toBe(true)
  18. if (typeof actual === 'string') {
  19. expect(actual).toEqual(expected)
  20. return
  21. }
  22. expect(actual.currency).toEqual(expected.currency)
  23. expect(actual.issuer).toEqual(expected.issuer)
  24. expect(
  25. actual.value === expected.value ||
  26. new Decimal(actual.value).equals(new Decimal(expected.value)),
  27. ).toBe(true)
  28. }
  29. function basicApiTests() {
  30. const bytes = parseHexOnly('00,01020304,0506', Uint8Array)
  31. test('can read slices of bytes', () => {
  32. const parser = makeParser(bytes)
  33. expect(parser.bytes instanceof Buffer).toBe(true)
  34. const read1 = parser.read(1)
  35. expect(read1 instanceof Buffer).toBe(true)
  36. expect(read1).toEqual(Buffer.from([0]))
  37. expect(parser.read(4)).toEqual(Buffer.from([1, 2, 3, 4]))
  38. expect(parser.read(2)).toEqual(Buffer.from([5, 6]))
  39. expect(() => parser.read(1)).toThrow()
  40. })
  41. test('can read a Uint32 at full', () => {
  42. const parser = makeParser('FFFFFFFF')
  43. expect(parser.readUInt32()).toEqual(0xffffffff)
  44. })
  45. }
  46. function transactionParsingTests() {
  47. const transaction = {
  48. json: {
  49. Account: 'raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3',
  50. Fee: '10',
  51. Flags: 0,
  52. Sequence: 103929,
  53. SigningPubKey:
  54. '028472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F418D6A7166',
  55. TakerGets: {
  56. currency: 'ILS',
  57. issuer: 'rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9',
  58. value: '1694.768',
  59. },
  60. TakerPays: '98957503520',
  61. TransactionType: 'OfferCreate',
  62. TxnSignature: __(`
  63. 304502202ABE08D5E78D1E74A4C18F2714F64E87B8BD57444AF
  64. A5733109EB3C077077520022100DB335EE97386E4C0591CAC02
  65. 4D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C`),
  66. },
  67. binary: __(`
  68. 120007220000000024000195F964400000170A53AC2065D5460561E
  69. C9DE000000000000000000000000000494C53000000000092D70596
  70. 8936C419CE614BF264B5EEB1CEA47FF468400000000000000A73210
  71. 28472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F
  72. 418D6A71667447304502202ABE08D5E78D1E74A4C18F2714F64E87B
  73. 8BD57444AFA5733109EB3C077077520022100DB335EE97386E4C059
  74. 1CAC024D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C811439408
  75. A69F0895E62149CFCC006FB89FA7D1E6E5D`),
  76. }
  77. const tx_json = transaction.json
  78. // These tests are basically development logs
  79. test('can be done with low level apis', () => {
  80. const parser = makeParser(transaction.binary)
  81. expect(parser.readField()).toEqual(Field.TransactionType)
  82. expect(parser.readUInt16()).toEqual(7)
  83. expect(parser.readField()).toEqual(Field.Flags)
  84. expect(parser.readUInt32()).toEqual(0)
  85. expect(parser.readField()).toEqual(Field.Sequence)
  86. expect(parser.readUInt32()).toEqual(103929)
  87. expect(parser.readField()).toEqual(Field.TakerPays)
  88. parser.read(8)
  89. expect(parser.readField()).toEqual(Field.TakerGets)
  90. // amount value
  91. expect(parser.read(8)).not.toBe([])
  92. // amount currency
  93. expect(Hash160.fromParser(parser)).not.toBe([])
  94. expect(encodeAccountID(parser.read(20))).toEqual(tx_json.TakerGets.issuer)
  95. expect(parser.readField()).toEqual(Field.Fee)
  96. expect(parser.read(8)).not.toEqual([])
  97. expect(parser.readField()).toEqual(Field.SigningPubKey)
  98. expect(parser.readVariableLengthLength()).toBe(33)
  99. expect(parser.read(33).toString('hex').toUpperCase()).toEqual(
  100. tx_json.SigningPubKey,
  101. )
  102. expect(parser.readField()).toEqual(Field.TxnSignature)
  103. expect(parser.readVariableLength().toString('hex').toUpperCase()).toEqual(
  104. tx_json.TxnSignature,
  105. )
  106. expect(parser.readField()).toEqual(Field.Account)
  107. expect(encodeAccountID(parser.readVariableLength())).toEqual(
  108. tx_json.Account,
  109. )
  110. expect(parser.end()).toBe(true)
  111. })
  112. test('can be done with high level apis', () => {
  113. const parser = makeParser(transaction.binary)
  114. function readField() {
  115. return parser.readFieldAndValue()
  116. }
  117. {
  118. const [field, value] = readField()
  119. expect(field).toEqual(Field.TransactionType)
  120. expect(value).toEqual(TransactionType.OfferCreate)
  121. }
  122. {
  123. const [field, value] = readField()
  124. expect(field).toEqual(Field.Flags)
  125. expect(value.valueOf()).toEqual(0)
  126. }
  127. {
  128. const [field, value] = readField()
  129. expect(field).toEqual(Field.Sequence)
  130. expect(value.valueOf()).toEqual(103929)
  131. }
  132. {
  133. const [field, value] = readField()
  134. expect(field).toEqual(Field.TakerPays)
  135. expect(value.isNative()).toEqual(true)
  136. expect(value.toJSON()).toEqual('98957503520')
  137. }
  138. {
  139. const [field, value] = readField()
  140. expect(field).toEqual(Field.TakerGets)
  141. expect(value.isNative()).toEqual(false)
  142. expect(value.toJSON().issuer).toEqual(tx_json.TakerGets.issuer)
  143. }
  144. {
  145. const [field, value] = readField()
  146. expect(field).toEqual(Field.Fee)
  147. expect(value.isNative()).toEqual(true)
  148. }
  149. {
  150. const [field, value] = readField()
  151. expect(field).toEqual(Field.SigningPubKey)
  152. expect(value.toJSON()).toEqual(tx_json.SigningPubKey)
  153. }
  154. {
  155. const [field, value] = readField()
  156. expect(field).toEqual(Field.TxnSignature)
  157. expect(value.toJSON()).toEqual(tx_json.TxnSignature)
  158. }
  159. {
  160. const [field, value] = readField()
  161. expect(field).toEqual(Field.Account)
  162. expect(value.toJSON()).toEqual(tx_json.Account)
  163. }
  164. expect(parser.end()).toBe(true)
  165. })
  166. test('can be done with higher level apis', () => {
  167. const parser = makeParser(transaction.binary)
  168. const jsonFromBinary = readJSON(parser)
  169. expect(jsonFromBinary).toEqual(tx_json)
  170. })
  171. test('readJSON (binary.decode) does not return STObject ', () => {
  172. const parser = makeParser(transaction.binary)
  173. const jsonFromBinary = readJSON(parser)
  174. expect(jsonFromBinary instanceof coreTypes.STObject).toBe(false)
  175. expect(jsonFromBinary instanceof Object).toBe(true)
  176. expect(jsonFromBinary.prototype).toBe(undefined)
  177. })
  178. }
  179. function amountParsingTests() {
  180. fixtures.values_tests
  181. .filter((obj) => obj.type === 'Amount')
  182. .forEach((f, i) => {
  183. if (f.error) {
  184. return
  185. }
  186. const parser = makeParser(f.expected_hex)
  187. const testName = `values_tests[${i}] parses ${f.expected_hex.slice(
  188. 0,
  189. 16,
  190. )}...
  191. as ${JSON.stringify(f.test_json)}`
  192. test(testName, () => {
  193. const value = parser.readType(Amount)
  194. // May not actually be in canonical form. The fixtures are to be used
  195. // also for json -> binary;
  196. const json = toJSON(value)
  197. assertEqualAmountJSON(json, f.test_json)
  198. if (f.exponent) {
  199. const exponent = new Decimal(json.value)
  200. expect(exponent.e - 15).toEqual(f.exponent)
  201. }
  202. })
  203. })
  204. }
  205. function fieldParsingTests() {
  206. fixtures.fields_tests.forEach((f, i) => {
  207. const parser = makeParser(f.expected_hex)
  208. test(`fields[${i}]: parses ${f.expected_hex} as ${f.name}`, () => {
  209. const field = parser.readField()
  210. expect(field.name).toEqual(f.name)
  211. expect(field.type.name).toEqual(f.type_name)
  212. })
  213. })
  214. test('Field throws when type code out of range', () => {
  215. const parser = makeParser('0101')
  216. expect(() => parser.readField()).toThrow(
  217. new Error('Cannot read FieldOrdinal, type_code out of range'),
  218. )
  219. })
  220. test('Field throws when field code out of range', () => {
  221. const parser = makeParser('1001')
  222. expect(() => parser.readFieldOrdinal()).toThrowError(
  223. new Error('Cannot read FieldOrdinal, field_code out of range'),
  224. )
  225. })
  226. test('Field throws when both type and field code out of range', () => {
  227. const parser = makeParser('000101')
  228. expect(() => parser.readFieldOrdinal()).toThrowError(
  229. new Error('Cannot read FieldOrdinal, type_code out of range'),
  230. )
  231. })
  232. }
  233. function assertRecyclable(json, forField) {
  234. const Type = forField.associatedType
  235. const recycled = Type.from(json).toJSON()
  236. expect(recycled).toEqual(json)
  237. const sink = new BytesList()
  238. Type.from(recycled).toBytesSink(sink)
  239. const recycledAgain = makeParser(sink.toHex()).readType(Type).toJSON()
  240. expect(recycledAgain).toEqual(json)
  241. }
  242. function nestedObjectTests() {
  243. fixtures.whole_objects.forEach((f, i) => {
  244. test(`whole_objects[${i}]: can parse blob into
  245. ${JSON.stringify(
  246. f.tx_json,
  247. )}`, /* */ () => {
  248. const parser = makeParser(f.blob_with_no_signing)
  249. let ix = 0
  250. while (!parser.end()) {
  251. const [field, value] = parser.readFieldAndValue()
  252. const expected = f.fields[ix]
  253. const expectedJSON = expected[1].json
  254. const expectedField = expected[0]
  255. const actual = toJSON(value)
  256. try {
  257. expect(actual).toEqual(expectedJSON)
  258. } catch (e) {
  259. throw new Error(`${e} ${field} a: ${actual} e: ${expectedJSON}`)
  260. }
  261. expect(field.name).toEqual(expectedField)
  262. assertRecyclable(actual, field)
  263. ix++
  264. }
  265. })
  266. })
  267. }
  268. function pathSetBinaryTests() {
  269. const bytes = __(
  270. `1200002200000000240000002E2E00004BF161D4C71AFD498D00000000000000
  271. 0000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA0
  272. 6594D168400000000000000A69D446F8038585E9400000000000000000000000
  273. 00425443000000000078CA21A6014541AB7B26C3929B9E0CD8C284D61C732103
  274. A4665B1F0B7AE2BCA12E2DB80A192125BBEA660F80E9CEE137BA444C1B0769EC
  275. 7447304502205A964536805E35785C659D1F9670D057749AE39668175D6AA75D
  276. 25B218FE682E0221009252C0E5DDD5F2712A48F211669DE17B54113918E0D2C2
  277. 66F818095E9339D7D3811478CA21A6014541AB7B26C3929B9E0CD8C284D61C83
  278. 140A20B3C85F482532A9578DBB3950B85CA06594D1011231585E1F3BD02A15D6
  279. 185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000
  280. 585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D694C
  281. 8531CDEECBE84F33670000000000000000000000004254430000000000E4FE68
  282. 7C90257D3D2D694C8531CDEECBE84F3367310A20B3C85F482532A9578DBB3950
  283. B85CA06594D100000000000000000000000042544300000000000A20B3C85F48
  284. 2532A9578DBB3950B85CA06594D1300000000000000000000000005553440000
  285. 0000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A15
  286. D6185F8BB9B57CC60DEDDB37C100000000000000000000000042544300000000
  287. 00585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D69
  288. 4C8531CDEECBE84F33670000000000000000000000004254430000000000E4FE
  289. 687C90257D3D2D694C8531CDEECBE84F33673115036E2D3F5437A83E5AC3CAEE
  290. 34FF2C21DEB618000000000000000000000000425443000000000015036E2D3F
  291. 5437A83E5AC3CAEE34FF2C21DEB6183000000000000000000000000055534400
  292. 000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A
  293. 15D6185F8BB9B57CC60DEDDB37C1000000000000000000000000425443000000
  294. 0000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C13157180C769B66D942EE
  295. 69E6DCC940CA48D82337AD000000000000000000000000425443000000000057
  296. 180C769B66D942EE69E6DCC940CA48D82337AD10000000000000000000000000
  297. 00000000000000003000000000000000000000000055534400000000000A20B3
  298. C85F482532A9578DBB3950B85CA06594D100`,
  299. )
  300. const expectedJSON = [
  301. [
  302. {
  303. account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
  304. currency: 'BTC',
  305. issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
  306. },
  307. {
  308. account: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo',
  309. currency: 'BTC',
  310. issuer: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo',
  311. },
  312. {
  313. account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
  314. currency: 'BTC',
  315. issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
  316. },
  317. {
  318. currency: 'USD',
  319. issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
  320. },
  321. ],
  322. [
  323. {
  324. account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
  325. currency: 'BTC',
  326. issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
  327. },
  328. {
  329. account: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo',
  330. currency: 'BTC',
  331. issuer: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo',
  332. },
  333. {
  334. account: 'rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi',
  335. currency: 'BTC',
  336. issuer: 'rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi',
  337. },
  338. {
  339. currency: 'USD',
  340. issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
  341. },
  342. ],
  343. [
  344. {
  345. account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
  346. currency: 'BTC',
  347. issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
  348. },
  349. {
  350. account: 'r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn',
  351. currency: 'BTC',
  352. issuer: 'r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn',
  353. },
  354. { currency: 'XRP' },
  355. {
  356. currency: 'USD',
  357. issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
  358. },
  359. ],
  360. ]
  361. test('works with long paths', () => {
  362. const parser = makeParser(bytes)
  363. const txn = readJSON(parser)
  364. expect(txn.Paths).toEqual(expectedJSON)
  365. // TODO: this should go elsewhere
  366. expect(coreTypes.PathSet.from(txn.Paths).toJSON()).toEqual(expectedJSON)
  367. })
  368. }
  369. describe('Binary Parser', function () {
  370. describe('pathSetBinaryTests', () => pathSetBinaryTests())
  371. describe('nestedObjectTests', () => nestedObjectTests())
  372. describe('fieldParsingTests', () => fieldParsingTests())
  373. describe('amountParsingTests', () => amountParsingTests())
  374. describe('transactionParsingTests', () => transactionParsingTests())
  375. describe('basicApiTests', () => basicApiTests())
  376. })