fix: add workaround for rippled UNLModify encoding bug (#1830)

* initial patch

* clean up

* make ts happy

* fix typing

* respond to comments

* Revert "respond to comments"

This reverts commit 689768bbf1.

* Publish

 - ripple-address-codec@4.2.1-beta.0
 - ripple-binary-codec@1.2.1-beta.0
 - ripple-keypairs@1.1.1-beta.0
 - xrpl@2.0.3-beta.0

* add helper comments

Co-authored-by: Nathan Nichols <natenichols@cox.net>
This commit is contained in:
Mayukha Vadari
2021-11-30 17:16:03 -05:00
committed by GitHub
parent 3e55066b70
commit fc101c6733
9 changed files with 44 additions and 23 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-address-codec",
"version": "4.2.0",
"version": "4.2.1-beta.0",
"description": "encodes/decodes base58 encoded XRP Ledger identifiers",
"files": [
"dist/*",

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-binary-codec",
"version": "1.2.0",
"version": "1.2.1-beta.0",
"description": "XRP Ledger binary codec",
"files": [
"dist/*",
@@ -17,7 +17,7 @@
"buffer": "5.6.0",
"create-hash": "^1.2.0",
"decimal.js": "^10.2.0",
"ripple-address-codec": "^4.2.0"
"ripple-address-codec": "^4.2.1-beta.0"
},
"scripts": {
"build": "run-script-os",

View File

@@ -66,6 +66,7 @@ function serializeObject(object: JsonObject, opts: OptionObject = {}): Buffer {
const filter = signingFieldsOnly
? (f: FieldInstance): boolean => f.isSigningField
: undefined
coreTypes.STObject.from(object, filter).toBytesSink(bytesList)
if (suffix) {

View File

@@ -1,6 +1,7 @@
import * as enums from './definitions.json'
import { SerializedType } from '../types/serialized-type'
import { Buffer } from 'buffer/'
import { BytesList } from '../binary'
const TYPE_WIDTH = 2
const LEDGER_ENTRY_WIDTH = 2
@@ -29,8 +30,8 @@ function fieldHeader(type: number, nth: number): Buffer {
/*
* @brief: Bytes, name, and ordinal representing one type, ledger_type, transaction type, or result
*/
class Bytes {
readonly bytes: Uint8Array
export class Bytes {
readonly bytes: Buffer
constructor(
readonly name: string,
@@ -47,7 +48,7 @@ class Bytes {
return this.name
}
toBytesSink(sink): void {
toBytesSink(sink: BytesList): void {
sink.put(this.bytes)
}

View File

@@ -126,7 +126,11 @@ class BinarySerializer {
* @param field field to write to BinarySerializer
* @param value value to write to BinarySerializer
*/
writeFieldAndValue(field: FieldInstance, value: SerializedType): void {
writeFieldAndValue(
field: FieldInstance,
value: SerializedType,
isUnlModifyWorkaround = false,
): void {
const associatedValue = field.associatedType.from(value)
assert.ok(associatedValue.toBytesSink !== undefined)
assert.ok(field.name !== undefined)
@@ -134,7 +138,7 @@ class BinarySerializer {
this.sink.put(field.header)
if (field.isVariableLengthEncoded) {
this.writeLengthEncoded(associatedValue)
this.writeLengthEncoded(associatedValue, isUnlModifyWorkaround)
} else {
associatedValue.toBytesSink(this.sink)
}
@@ -145,9 +149,15 @@ class BinarySerializer {
*
* @param value length encoded value to write to BytesList
*/
public writeLengthEncoded(value: SerializedType): void {
public writeLengthEncoded(
value: SerializedType,
isUnlModifyWorkaround = false,
): void {
const bytes = new BytesList()
value.toBytesSink(bytes)
if (!isUnlModifyWorkaround) {
// this part doesn't happen for the Account field in a UNLModify transaction
value.toBytesSink(bytes)
}
this.put(this.encodeVariableLength(bytes.getLength()))
this.writeBytesList(bytes)
}

View File

@@ -1,4 +1,4 @@
import { Field, FieldInstance } from '../enums'
import { Field, FieldInstance, Bytes } from '../enums'
import { SerializedType, JsonObject } from './serialized-type'
import { xAddressToClassicAddress, isValidXAddress } from 'ripple-address-codec'
import { BinaryParser } from '../serdes/binary-parser'
@@ -96,6 +96,8 @@ class STObject extends SerializedType {
const list: BytesList = new BytesList()
const bytes: BinarySerializer = new BinarySerializer(list)
let isUnlModify = false
const xAddressDecoded = Object.entries(value).reduce((acc, [key, val]) => {
let handled: JsonObject | undefined = undefined
if (val && isValidXAddress(val.toString())) {
@@ -125,8 +127,15 @@ class STObject extends SerializedType {
const associatedValue = field.associatedType.from(
xAddressDecoded[field.name],
)
bytes.writeFieldAndValue(field, associatedValue)
if ((associatedValue as unknown as Bytes).name === 'UNLModify') {
// triggered when the TransactionType field has a value of 'UNLModify'
isUnlModify = true
}
// true when in the UNLModify pseudotransaction (after the transaction type has been processed) and working with the
// Account field
// The Account field must not be a part of the UNLModify pseudotransaction encoding, due to a bug in rippled
const isUnlModifyWorkaround = field.name == 'Account' && isUnlModify
bytes.writeFieldAndValue(field, associatedValue, isUnlModifyWorkaround)
if (field.type.name === ST_OBJECT) {
bytes.put(OBJECT_END_MARKER_BYTE)
}

View File

@@ -1,12 +1,12 @@
{
"binary": "120066240000000026000003006840000000000000007300701321ED9D593004CC501CACD261BD8E31E863F2B3F6CA69505E7FD54DA8F5690BEFB7AE8114000000000000000000000000000000000000000000101101",
"binary": "120066240000000026040B52006840000000000000007300701321EDB6FC8E803EE8EDC2793F1EC917B2EE41D35255618DEB91D3F9B1FC89B75D4539810000101101",
"tx": {
"UNLModifyDisabling": 1,
"LedgerSequence": 768,
"UNLModifyValidator": "ED9D593004CC501CACD261BD8E31E863F2B3F6CA69505E7FD54DA8F5690BEFB7AE",
"LedgerSequence": 67850752,
"UNLModifyValidator": "EDB6FC8E803EE8EDC2793F1EC917B2EE41D35255618DEB91D3F9B1FC89B75D4539",
"TransactionType": "UNLModify",
"Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
"Sequence": 0,
"Fee": "0",
"SigningPubKey": ""}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-keypairs",
"version": "1.1.0",
"version": "1.1.1-beta.0",
"description": "Cryptographic key pairs for the XRP Ledger",
"scripts": {
"build": "tsc -b",
@@ -21,7 +21,7 @@
"brorand": "^1.0.5",
"elliptic": "^6.5.4",
"hash.js": "^1.0.3",
"ripple-address-codec": "^4.2.0"
"ripple-address-codec": "^4.2.1-beta.0"
},
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "xrpl",
"version": "2.0.2",
"version": "2.0.3-beta.0",
"license": "ISC",
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
"files": [
@@ -24,9 +24,9 @@
"bip39": "^3.0.4",
"https-proxy-agent": "^5.0.0",
"lodash": "^4.17.4",
"ripple-address-codec": "^4.2.0",
"ripple-binary-codec": "^1.2.0",
"ripple-keypairs": "^1.1.0",
"ripple-address-codec": "^4.2.1-beta.0",
"ripple-binary-codec": "^1.2.1-beta.0",
"ripple-keypairs": "^1.1.1-beta.0",
"ws": "^8.2.2"
},
"devDependencies": {