From 30ff63e653fae25279a2a410616e9f5f6ffd4cb7 Mon Sep 17 00:00:00 2001 From: Nathan Nichols Date: Thu, 13 Aug 2020 12:20:12 -0500 Subject: [PATCH] Support encoding and decoding of NegativeUNL pseudo-transactions (#89) * Support encoding and decoding of NegativeUNL pseudo-transactions --- .../src/enums/definitions.json | 76 ++++++++++++++++++- .../src/types/account-id.ts | 6 +- .../ripple-binary-codec/src/types/hash-160.ts | 6 +- .../ripple-binary-codec/src/types/hash.ts | 14 ++-- .../ripple-binary-codec/src/types/path-set.ts | 13 ++-- .../test/binary-serializer.test.js | 14 +++- .../test/fixtures/negative-unl.json | 12 +++ .../test/pseudo-transaction.test.js | 37 +++++++++ 8 files changed, 161 insertions(+), 17 deletions(-) create mode 100644 packages/ripple-binary-codec/test/fixtures/negative-unl.json create mode 100644 packages/ripple-binary-codec/test/pseudo-transaction.test.js diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 3aa48dda..fb853fa2 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -40,7 +40,8 @@ "Check": 67, "Nickname": 110, "Contract": 99, - "GeneratorMap": 103 + "GeneratorMap": 103, + "NegativeUNL": 78 }, "FIELDS": [ [ @@ -1023,6 +1024,36 @@ "type": "Blob" } ], + [ + "UNLModifyValidator", + { + "nth": 19, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "ValidatorToDisable", + { + "nth": 20, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "ValidatorToReEnable", + { + "nth": 20, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], [ "Account", { @@ -1233,6 +1264,16 @@ "type": "STObject" } ], + [ + "DisabledValidator", + { + "nth": 19, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], [ "ArrayEndMarker", { @@ -1323,6 +1364,16 @@ "type": "STArray" } ], + [ + "DisabledValidators", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], [ "CloseResolution", { @@ -1483,6 +1534,16 @@ "type": "UInt32" } ], + [ + "BeginLedgerSeq", + { + "nth": 40, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], [ "Channel", { @@ -1523,6 +1584,16 @@ "type": "UInt8" } ], + [ + "UNLModifyDisabling", + { + "nth": 17, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], [ "DestinationNode", { @@ -1685,6 +1756,7 @@ "AccountDelete": 21, "EnableAmendment": 100, - "SetFee": 101 + "SetFee": 101, + "UNLModify": 102 } } diff --git a/packages/ripple-binary-codec/src/types/account-id.ts b/packages/ripple-binary-codec/src/types/account-id.ts index 00b472a6..609f85e4 100644 --- a/packages/ripple-binary-codec/src/types/account-id.ts +++ b/packages/ripple-binary-codec/src/types/account-id.ts @@ -7,7 +7,7 @@ import { Hash160 } from "./hash-160"; class AccountID extends Hash160 { static readonly defaultAccountID: AccountID = new AccountID(Buffer.alloc(20)); - constructor(bytes: Buffer) { + constructor(bytes?: Buffer) { super(bytes ?? AccountID.defaultAccountID.bytes); } @@ -23,6 +23,10 @@ class AccountID extends Hash160 { } if (typeof value === "string") { + if (value === "") { + return new AccountID(); + } + return /^r/.test(value) ? this.fromBase58(value) : new AccountID(Buffer.from(value, "hex")); diff --git a/packages/ripple-binary-codec/src/types/hash-160.ts b/packages/ripple-binary-codec/src/types/hash-160.ts index bb4f673c..22ccaca2 100644 --- a/packages/ripple-binary-codec/src/types/hash-160.ts +++ b/packages/ripple-binary-codec/src/types/hash-160.ts @@ -7,7 +7,11 @@ class Hash160 extends Hash { static readonly width = 20; static readonly ZERO_160: Hash160 = new Hash160(Buffer.alloc(Hash160.width)); - constructor(bytes: Buffer) { + constructor(bytes?: Buffer) { + if (bytes && bytes.byteLength === 0) { + bytes = Hash160.ZERO_160.bytes; + } + super(bytes ?? Hash160.ZERO_160.bytes); } } diff --git a/packages/ripple-binary-codec/src/types/hash.ts b/packages/ripple-binary-codec/src/types/hash.ts index 82a1ea9f..efabff84 100644 --- a/packages/ripple-binary-codec/src/types/hash.ts +++ b/packages/ripple-binary-codec/src/types/hash.ts @@ -9,24 +9,26 @@ class Hash extends Comparable { constructor(bytes: Buffer) { super(bytes); + if (this.bytes.byteLength !== (this.constructor as typeof Hash).width) { + throw new Error(`Invalid Hash length ${this.bytes.byteLength}`); + } } /** * Construct a Hash object from an existing Hash object or a hex-string * * @param value A hash object or hex-string of a hash - */ + */ static from(value: T): Hash { - if(value instanceof this) { - return value + if (value instanceof this) { + return value; } - - if(typeof value === "string") { + + if (typeof value === "string") { return new this(Buffer.from(value, "hex")); } throw new Error("Cannot construct Hash from given value"); - } /** diff --git a/packages/ripple-binary-codec/src/types/path-set.ts b/packages/ripple-binary-codec/src/types/path-set.ts index 1aff2599..969b4ea4 100644 --- a/packages/ripple-binary-codec/src/types/path-set.ts +++ b/packages/ripple-binary-codec/src/types/path-set.ts @@ -29,9 +29,10 @@ interface HopObject extends JsonObject { * TypeGuard for HopObject */ function isHopObject(arg): arg is HopObject { - return (arg.issuer !== undefined || - arg.account !== undefined || - arg.currency !== undefined + return ( + arg.issuer !== undefined || + arg.account !== undefined || + arg.currency !== undefined ); } @@ -40,9 +41,9 @@ function isHopObject(arg): arg is HopObject { */ function isPathSet(arg): arg is Array> { return ( - Array.isArray(arg) && arg.length === 0 || - Array.isArray(arg) && Array.isArray(arg[0]) && arg[0].length === 0 || - Array.isArray(arg) && Array.isArray(arg[0]) && isHopObject(arg[0][0]) + (Array.isArray(arg) && arg.length === 0) || + (Array.isArray(arg) && Array.isArray(arg[0]) && arg[0].length === 0) || + (Array.isArray(arg) && Array.isArray(arg[0]) && isHopObject(arg[0][0])) ); } diff --git a/packages/ripple-binary-codec/test/binary-serializer.test.js b/packages/ripple-binary-codec/test/binary-serializer.test.js index a653682e..9eef6ab8 100644 --- a/packages/ripple-binary-codec/test/binary-serializer.test.js +++ b/packages/ripple-binary-codec/test/binary-serializer.test.js @@ -1,7 +1,7 @@ /* eslint-disable func-style */ const { binary } = require('../dist/coretypes') -const { encode } = require('../dist') +const { encode, decode } = require('../dist') const { makeParser, BytesList, BinarySerializer } = binary const { coreTypes } = require('../dist/types') const { UInt8, UInt16, UInt32, UInt64, STObject } = coreTypes @@ -50,6 +50,8 @@ const PaymentChannel = { } } +const NegativeUNL = require('./fixtures/negative-unl.json'); + function bytesListTest () { const list = new BytesList().put(Buffer.from([0])).put(Buffer.from([2, 3])).put(Buffer.from([4, 5])) test('is an Array', function () { @@ -171,6 +173,15 @@ function PaymentChannelTest () { }) } +function NegativeUNLTest () { + test('can serialize NegativeUNL', () => { + expect(encode(NegativeUNL.tx)).toEqual(NegativeUNL.binary); + }) + test('can deserialize NegativeUNL', () => { + expect(decode(NegativeUNL.binary)).toEqual(NegativeUNL.tx); + }) +} + describe('Binary Serialization', function() { describe('nestedObjectTests', nestedObjectTests); describe('BytesList', bytesListTest); @@ -179,4 +190,5 @@ describe('Binary Serialization', function() { describe('SignerListSet', SignerListSetTest); describe('Escrow', EscrowTest); describe('PaymentChannel', PaymentChannelTest); + describe('NegativeUNLTest', NegativeUNLTest); }) \ No newline at end of file diff --git a/packages/ripple-binary-codec/test/fixtures/negative-unl.json b/packages/ripple-binary-codec/test/fixtures/negative-unl.json new file mode 100644 index 00000000..03817cd2 --- /dev/null +++ b/packages/ripple-binary-codec/test/fixtures/negative-unl.json @@ -0,0 +1,12 @@ +{ + "binary": "120066240000000026000003006840000000000000007300701321ED9D593004CC501CACD261BD8E31E863F2B3F6CA69505E7FD54DA8F5690BEFB7AE8114000000000000000000000000000000000000000000101101", + "tx": { + "UNLModifyDisabling": 1, + "LedgerSequence": 768, + "UNLModifyValidator": "ED9D593004CC501CACD261BD8E31E863F2B3F6CA69505E7FD54DA8F5690BEFB7AE", + "TransactionType": "UNLModify", + "Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp", + "Sequence": 0, + "Fee": "0", + "SigningPubKey": ""} +} \ No newline at end of file diff --git a/packages/ripple-binary-codec/test/pseudo-transaction.test.js b/packages/ripple-binary-codec/test/pseudo-transaction.test.js new file mode 100644 index 00000000..e3a21890 --- /dev/null +++ b/packages/ripple-binary-codec/test/pseudo-transaction.test.js @@ -0,0 +1,37 @@ +const { encode, decode } = require('../dist') + +let json = { + "Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp", + "Sequence": 0, + "Fee": "0", + "SigningPubKey": "", + "Signature": "" +} + +let json_blank_acct = { + "Account": "", + "Sequence": 0, + "Fee": "0", + "SigningPubKey": "", + "Signature": "" +} + +let binary = "24000000006840000000000000007300760081140000000000000000000000000000000000000000" + +describe("Can encode Pseudo Transactions", () => { + test("Correctly encodes Pseudo Transaciton", () => { + expect(encode(json)).toEqual(binary); + }) + + test("Can decode account objects", () => { + expect(decode(encode(json))).toEqual(json); + }) + + test("Blank AccountID is ACCOUNT_ZERO", () => { + expect(encode(json_blank_acct)).toEqual(binary) + }) + + test("Decodes Blank AccountID", () => { + expect(decode(encode(json_blank_acct))).toEqual(json); + }) +}) \ No newline at end of file