mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 12:15:51 +00:00
ripple-binary-codec refactor (#88)
Refactored all components in ripple-binary-codec /src
This commit is contained in:
@@ -14,12 +14,10 @@
|
||||
"dependencies": {
|
||||
"create-hash": "^1.2.0",
|
||||
"decimal.js": "^10.2.0",
|
||||
"inherits": "^2.0.4",
|
||||
"lodash": "^4.17.15",
|
||||
"ripple-address-codec": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.0",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.155",
|
||||
"@types/node": "^14.0.10",
|
||||
"@typescript-eslint/eslint-plugin": "^3.2.0",
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
# ripple-binary-codec
|
||||
# ripple-binary-codec
|
||||
|
||||
Serialize and deserialize transactions according to the XRP Ledger protocol.
|
||||
@@ -1,36 +1,107 @@
|
||||
/* eslint-disable func-style */
|
||||
|
||||
import { coreTypes } from "./types";
|
||||
const { HashPrefix } = require("./hash-prefixes");
|
||||
const { BinaryParser } = require("./serdes/binary-parser");
|
||||
const { BinarySerializer, BytesList } = require("./serdes/binary-serializer");
|
||||
const { bytesToHex, slice, parseBytes } = require("./utils/bytes-utils");
|
||||
import { BinaryParser } from "./serdes/binary-parser";
|
||||
import { AccountID } from "./types/account-id";
|
||||
import { HashPrefix } from "./hash-prefixes";
|
||||
import { BinarySerializer, BytesList } from "./serdes/binary-serializer";
|
||||
import { sha512Half, transactionID } from "./hashes";
|
||||
import { FieldInstance } from "./enums";
|
||||
import { STObject } from "./types/st-object";
|
||||
import { JsonObject } from "./types/serialized-type";
|
||||
|
||||
const { sha512Half, transactionID } = require("./hashes");
|
||||
/**
|
||||
* Construct a BinaryParser
|
||||
*
|
||||
* @param bytes hex-string to construct BinaryParser from
|
||||
* @returns A BinaryParser
|
||||
*/
|
||||
const makeParser = (bytes: string): BinaryParser => new BinaryParser(bytes);
|
||||
|
||||
const makeParser = (bytes) => new BinaryParser(bytes);
|
||||
const readJSON = (parser) => parser.readType(coreTypes.STObject).toJSON();
|
||||
const binaryToJSON = (bytes) => readJSON(makeParser(bytes));
|
||||
/**
|
||||
* Parse BinaryParser into JSON
|
||||
*
|
||||
* @param parser BinaryParser object
|
||||
* @returns JSON for the bytes in the BinaryParser
|
||||
*/
|
||||
const readJSON = (parser: BinaryParser): JsonObject =>
|
||||
(parser.readType(coreTypes.STObject) as STObject).toJSON();
|
||||
|
||||
function serializeObject(object, opts = <any>{}) {
|
||||
/**
|
||||
* Parse a hex-string into its JSON interpretation
|
||||
*
|
||||
* @param bytes hex-string to parse into JSON
|
||||
* @returns JSON
|
||||
*/
|
||||
const binaryToJSON = (bytes: string): JsonObject => readJSON(makeParser(bytes));
|
||||
|
||||
/**
|
||||
* Interface for passing parameters to SerializeObject
|
||||
*
|
||||
* @field set signingFieldOnly to true if you want to serialize only signing fields
|
||||
*/
|
||||
interface OptionObject {
|
||||
prefix?: Buffer;
|
||||
suffix?: Buffer;
|
||||
signingFieldsOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to serialize JSON object representing a transaction
|
||||
*
|
||||
* @param object JSON object to serialize
|
||||
* @param opts options for serializing, including optional prefix, suffix, and signingFieldOnly
|
||||
* @returns A Buffer containing the serialized object
|
||||
*/
|
||||
function serializeObject(object: JsonObject, opts: OptionObject = {}): Buffer {
|
||||
const { prefix, suffix, signingFieldsOnly = false } = opts;
|
||||
const bytesList = new BytesList();
|
||||
|
||||
if (prefix) {
|
||||
bytesList.put(prefix);
|
||||
}
|
||||
const filter = signingFieldsOnly ? (f) => f.isSigningField : undefined;
|
||||
|
||||
const filter = signingFieldsOnly
|
||||
? (f: FieldInstance): boolean => f.isSigningField
|
||||
: undefined;
|
||||
coreTypes.STObject.from(object, filter).toBytesSink(bytesList);
|
||||
|
||||
if (suffix) {
|
||||
bytesList.put(suffix);
|
||||
}
|
||||
|
||||
return bytesList.toBytes();
|
||||
}
|
||||
|
||||
function signingData(tx, prefix = HashPrefix.transactionSig) {
|
||||
return serializeObject(tx, { prefix, signingFieldsOnly: true });
|
||||
/**
|
||||
* Serialize an object for signing
|
||||
*
|
||||
* @param transaction Transaction to serialize
|
||||
* @param prefix Prefix bytes to put before the serialized object
|
||||
* @returns A Buffer with the serialized object
|
||||
*/
|
||||
function signingData(
|
||||
transaction: JsonObject,
|
||||
prefix: Buffer = HashPrefix.transactionSig
|
||||
): Buffer {
|
||||
return serializeObject(transaction, { prefix, signingFieldsOnly: true });
|
||||
}
|
||||
|
||||
function signingClaimData(claim) {
|
||||
/**
|
||||
* Interface describing fields required for a Claim
|
||||
*/
|
||||
interface ClaimObject extends JsonObject {
|
||||
channel: string;
|
||||
amount: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a signingClaim
|
||||
*
|
||||
* @param claim A claim object to serialize
|
||||
* @returns the serialized object with appropriate prefix
|
||||
*/
|
||||
function signingClaimData(claim: ClaimObject): Buffer {
|
||||
const prefix = HashPrefix.paymentChannelClaim;
|
||||
const channel = coreTypes.Hash256.from(claim.channel).toBytes();
|
||||
const amount = coreTypes.UInt64.from(BigInt(claim.amount)).toBytes();
|
||||
@@ -43,26 +114,38 @@ function signingClaimData(claim) {
|
||||
return bytesList.toBytes();
|
||||
}
|
||||
|
||||
function multiSigningData(tx, signingAccount) {
|
||||
/**
|
||||
* Serialize a transaction object for multiSigning
|
||||
*
|
||||
* @param transaction transaction to serialize
|
||||
* @param signingAccount Account to sign the transaction with
|
||||
* @returns serialized transaction with appropriate prefix and suffix
|
||||
*/
|
||||
function multiSigningData(
|
||||
transaction: JsonObject,
|
||||
signingAccount: string | AccountID
|
||||
): Buffer {
|
||||
const prefix = HashPrefix.transactionMultiSig;
|
||||
const suffix = coreTypes.AccountID.from(signingAccount).toBytes();
|
||||
return serializeObject(tx, { prefix, suffix, signingFieldsOnly: true });
|
||||
return serializeObject(transaction, {
|
||||
prefix,
|
||||
suffix,
|
||||
signingFieldsOnly: true,
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
BinaryParser,
|
||||
BinarySerializer,
|
||||
BytesList,
|
||||
ClaimObject,
|
||||
makeParser,
|
||||
serializeObject,
|
||||
readJSON,
|
||||
bytesToHex,
|
||||
parseBytes,
|
||||
multiSigningData,
|
||||
signingData,
|
||||
signingClaimData,
|
||||
binaryToJSON,
|
||||
sha512Half,
|
||||
transactionID,
|
||||
slice,
|
||||
};
|
||||
|
||||
@@ -5,13 +5,13 @@ import {
|
||||
Type,
|
||||
TransactionResult,
|
||||
} from "./enums";
|
||||
const types = require("./types");
|
||||
const binary = require("./binary");
|
||||
const { ShaMap } = require("./shamap");
|
||||
const ledgerHashes = require("./ledger-hashes");
|
||||
const hashes = require("./hashes");
|
||||
const quality = require("./quality");
|
||||
const { HashPrefix } = require("./hash-prefixes");
|
||||
import * as types from "./types";
|
||||
import * as binary from "./binary";
|
||||
import { ShaMap } from "./shamap";
|
||||
import * as ledgerHashes from "./ledger-hashes";
|
||||
import * as hashes from "./hashes";
|
||||
import { quality } from "./quality";
|
||||
import { HashPrefix } from "./hash-prefixes";
|
||||
|
||||
export {
|
||||
hashes,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { serializeUIntN } from "../utils/bytes-utils";
|
||||
import * as enums from "./definitions.json";
|
||||
import { SerializedType } from "../types/serialized-type";
|
||||
|
||||
@@ -37,7 +36,10 @@ class Bytes {
|
||||
readonly ordinal: number,
|
||||
readonly ordinalWidth: number
|
||||
) {
|
||||
this.bytes = serializeUIntN(ordinal, ordinalWidth);
|
||||
this.bytes = Buffer.alloc(ordinalWidth);
|
||||
for (let i = 0; i < ordinalWidth; i++) {
|
||||
this.bytes[ordinalWidth - i - 1] = (ordinal >>> (i * 8)) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
@@ -57,7 +59,7 @@ class Bytes {
|
||||
* @brief: Collection of Bytes objects, mapping bidirectionally
|
||||
*/
|
||||
class BytesLookup {
|
||||
constructor(types: { [key: string]: number }, readonly ordinalWidth: number) {
|
||||
constructor(types: Record<string, number>, readonly ordinalWidth: number) {
|
||||
Object.entries(types).forEach(([k, v]) => {
|
||||
this[k] = new Bytes(k, v, ordinalWidth);
|
||||
this[v.toString()] = this[k];
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { serializeUIntN } from "./utils/bytes-utils";
|
||||
|
||||
function bytes(uint32) {
|
||||
return serializeUIntN(uint32, 4);
|
||||
/**
|
||||
* Write a 32 bit integer to a Buffer
|
||||
*
|
||||
* @param uint32 32 bit integer to write to buffer
|
||||
* @returns a buffer with the bytes representation of uint32
|
||||
*/
|
||||
function bytes(uint32: number): Buffer {
|
||||
const result = Buffer.alloc(4);
|
||||
result.writeUInt32BE(uint32);
|
||||
return result;
|
||||
}
|
||||
|
||||
const HashPrefix = {
|
||||
/**
|
||||
* Maps HashPrefix names to their byte representation
|
||||
*/
|
||||
const HashPrefix: Record<string, Buffer> = {
|
||||
transactionID: bytes(0x54584e00),
|
||||
// transaction plus metadata
|
||||
transaction: bytes(0x534e4400),
|
||||
|
||||
@@ -1,44 +1,76 @@
|
||||
import { makeClass } from "./utils/make-class";
|
||||
import { HashPrefix } from "./hash-prefixes";
|
||||
import { coreTypes } from "./types";
|
||||
import { parseBytes } from "./utils/bytes-utils";
|
||||
import * as createHash from "create-hash";
|
||||
import { Hash256 } from "./types/hash-256";
|
||||
import { BytesList } from "./serdes/binary-serializer";
|
||||
|
||||
const Sha512Half = makeClass(
|
||||
{
|
||||
Sha512Half() {
|
||||
this.hash = createHash("sha512");
|
||||
},
|
||||
statics: {
|
||||
put(bytes) {
|
||||
return new this().put(bytes);
|
||||
},
|
||||
},
|
||||
put(bytes) {
|
||||
this.hash.update(parseBytes(bytes, Buffer));
|
||||
return this;
|
||||
},
|
||||
finish256() {
|
||||
const bytes = this.hash.digest();
|
||||
return bytes.slice(0, 32);
|
||||
},
|
||||
finish() {
|
||||
return new coreTypes.Hash256(this.finish256());
|
||||
},
|
||||
},
|
||||
undefined
|
||||
);
|
||||
/**
|
||||
* Class for hashing with SHA512
|
||||
* @extends BytesList So SerializedTypes can write bytes to a Sha512Half
|
||||
*/
|
||||
class Sha512Half extends BytesList {
|
||||
private hash: createHash = createHash("sha512");
|
||||
|
||||
function sha512Half(...args) {
|
||||
const hash = new Sha512Half();
|
||||
args.forEach((a) => hash.put(a));
|
||||
return parseBytes(hash.finish256(), Uint8Array);
|
||||
/**
|
||||
* Construct a new Sha512Hash and write bytes this.hash
|
||||
*
|
||||
* @param bytes bytes to write to this.hash
|
||||
* @returns the new Sha512Hash object
|
||||
*/
|
||||
static put(bytes: Buffer): Sha512Half {
|
||||
return new Sha512Half().put(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write bytes to an existing Sha512Hash
|
||||
*
|
||||
* @param bytes bytes to write to object
|
||||
* @returns the Sha512 object
|
||||
*/
|
||||
put(bytes: Buffer): Sha512Half {
|
||||
this.hash.update(bytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute SHA512 hash and slice in half
|
||||
*
|
||||
* @returns half of a SHA512 hash
|
||||
*/
|
||||
finish256(): Buffer {
|
||||
const bytes: Buffer = this.hash.digest();
|
||||
return bytes.slice(0, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Hash256 from the Sha512Half object
|
||||
*
|
||||
* @returns a Hash256 object
|
||||
*/
|
||||
finish(): Hash256 {
|
||||
return new Hash256(this.finish256());
|
||||
}
|
||||
}
|
||||
|
||||
function transactionID(serialized) {
|
||||
return new coreTypes.Hash256(
|
||||
sha512Half(HashPrefix.transactionID, serialized)
|
||||
);
|
||||
/**
|
||||
* compute SHA512 hash of a list of bytes
|
||||
*
|
||||
* @param args zero or more arguments to hash
|
||||
* @returns the sha512half hash of the arguments.
|
||||
*/
|
||||
function sha512Half(...args: Buffer[]): Buffer {
|
||||
const hash = new Sha512Half();
|
||||
args.forEach((a) => hash.put(a));
|
||||
return hash.finish256();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a transactionID from a Serialized Transaction
|
||||
*
|
||||
* @param serialized bytes to hash
|
||||
* @returns a Hash256 object
|
||||
*/
|
||||
function transactionID(serialized: Buffer): Hash256 {
|
||||
return new Hash256(sha512Half(HashPrefix.transactionID, serialized));
|
||||
}
|
||||
|
||||
export { Sha512Half, sha512Half, transactionID };
|
||||
|
||||
@@ -1,69 +1,98 @@
|
||||
import { strict as assert } from "assert";
|
||||
import * as assert from "assert";
|
||||
import { quality, binary } from "./coretypes";
|
||||
import { coreTypes } from "./types";
|
||||
import { decodeLedgerData } from "./ledger-hashes";
|
||||
import { ClaimObject } from "./binary";
|
||||
import { JsonObject } from "./types/serialized-type";
|
||||
const {
|
||||
bytesToHex,
|
||||
signingData,
|
||||
signingClaimData,
|
||||
multiSigningData,
|
||||
binaryToJSON,
|
||||
serializeObject,
|
||||
BinaryParser,
|
||||
} = binary;
|
||||
|
||||
function decodeLedgerData(binary) {
|
||||
assert(typeof binary === "string", "binary must be a hex string");
|
||||
const parser = new BinaryParser(binary);
|
||||
return {
|
||||
ledger_index: parser.readUInt32(),
|
||||
total_coins: parser.readType(coreTypes.UInt64).valueOf().toString(),
|
||||
parent_hash: parser.readType(coreTypes.Hash256).toHex(),
|
||||
transaction_hash: parser.readType(coreTypes.Hash256).toHex(),
|
||||
account_hash: parser.readType(coreTypes.Hash256).toHex(),
|
||||
parent_close_time: parser.readUInt32(),
|
||||
close_time: parser.readUInt32(),
|
||||
close_time_resolution: parser.readUInt8(),
|
||||
close_flags: parser.readUInt8(),
|
||||
};
|
||||
}
|
||||
|
||||
function decode(binary) {
|
||||
/**
|
||||
* Decode a transaction
|
||||
*
|
||||
* @param binary hex-string of the encoded transaction
|
||||
* @returns the JSON representation of the transaction
|
||||
*/
|
||||
function decode(binary: string): JsonObject {
|
||||
assert(typeof binary === "string", "binary must be a hex string");
|
||||
return binaryToJSON(binary);
|
||||
}
|
||||
|
||||
function encode(json) {
|
||||
/**
|
||||
* Encode a transaction
|
||||
*
|
||||
* @param json The JSON representation of a transaction
|
||||
* @returns A hex-string of the encoded transaction
|
||||
*/
|
||||
function encode(json: JsonObject): string {
|
||||
assert(typeof json === "object");
|
||||
return bytesToHex(serializeObject(json));
|
||||
return serializeObject(json).toString("hex").toUpperCase();
|
||||
}
|
||||
|
||||
function encodeForSigning(json) {
|
||||
/**
|
||||
* Encode a transaction and prepare for signing
|
||||
*
|
||||
* @param json JSON object representing the transaction
|
||||
* @param signer string representing the account to sign the transaction with
|
||||
* @returns a hex string of the encoded transaction
|
||||
*/
|
||||
function encodeForSigning(json: JsonObject): string {
|
||||
assert(typeof json === "object");
|
||||
return bytesToHex(signingData(json));
|
||||
return signingData(json).toString("hex").toUpperCase();
|
||||
}
|
||||
|
||||
function encodeForSigningClaim(json) {
|
||||
/**
|
||||
* Encode a transaction and prepare for signing with a claim
|
||||
*
|
||||
* @param json JSON object representing the transaction
|
||||
* @param signer string representing the account to sign the transaction with
|
||||
* @returns a hex string of the encoded transaction
|
||||
*/
|
||||
function encodeForSigningClaim(json: ClaimObject): string {
|
||||
assert(typeof json === "object");
|
||||
return bytesToHex(signingClaimData(json));
|
||||
return signingClaimData(json).toString("hex").toUpperCase();
|
||||
}
|
||||
|
||||
function encodeForMultisigning(json, signer) {
|
||||
/**
|
||||
* Encode a transaction and prepare for multi-signing
|
||||
*
|
||||
* @param json JSON object representing the transaction
|
||||
* @param signer string representing the account to sign the transaction with
|
||||
* @returns a hex string of the encoded transaction
|
||||
*/
|
||||
function encodeForMultisigning(json: JsonObject, signer: string): string {
|
||||
assert(typeof json === "object");
|
||||
assert.equal(json.SigningPubKey, "");
|
||||
return bytesToHex(multiSigningData(json, signer));
|
||||
return multiSigningData(json, signer).toString("hex").toUpperCase();
|
||||
}
|
||||
|
||||
function encodeQuality(value) {
|
||||
/**
|
||||
* Encode a quality value
|
||||
*
|
||||
* @param value string representation of a number
|
||||
* @returns a hex-string representing the quality
|
||||
*/
|
||||
function encodeQuality(value: string): string {
|
||||
assert(typeof value === "string");
|
||||
return bytesToHex(quality.encode(value));
|
||||
return quality.encode(value).toString("hex").toUpperCase();
|
||||
}
|
||||
|
||||
function decodeQuality(value) {
|
||||
/**
|
||||
* Decode a quality value
|
||||
*
|
||||
* @param value hex-string of a quality
|
||||
* @returns a string representing the quality
|
||||
*/
|
||||
function decodeQuality(value: string): string {
|
||||
assert(typeof value === "string");
|
||||
return quality.decode(value).toString();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
decode,
|
||||
encode,
|
||||
encodeForSigning,
|
||||
|
||||
@@ -1,19 +1,49 @@
|
||||
import * as _ from "lodash";
|
||||
import { strict as assert } from "assert";
|
||||
import { coreTypes } from "./types";
|
||||
const { STObject, Hash256 } = coreTypes;
|
||||
import { ShaMap } from "./shamap";
|
||||
import * as assert from "assert";
|
||||
import { ShaMap, ShaMapNode, ShaMapLeaf } from "./shamap";
|
||||
import { HashPrefix } from "./hash-prefixes";
|
||||
import { Sha512Half } from "./hashes";
|
||||
import { BinarySerializer, serializeObject } from "./binary";
|
||||
import { Hash256 } from "./types/hash-256";
|
||||
import { STObject } from "./types/st-object";
|
||||
import { UInt64 } from "./types/uint-64";
|
||||
import { UInt32 } from "./types/uint-32";
|
||||
import { UInt8 } from "./types/uint-8";
|
||||
import { BinaryParser } from "./serdes/binary-parser";
|
||||
import { JsonObject } from "./types/serialized-type";
|
||||
|
||||
function computeHash(itemizer, itemsJson) {
|
||||
/**
|
||||
* Computes the hash of a list of objects
|
||||
*
|
||||
* @param itemizer Converts an item into a format that can be added to SHAMap
|
||||
* @param itemsJson Array of items to add to a SHAMap
|
||||
* @returns the hash of the SHAMap
|
||||
*/
|
||||
function computeHash(
|
||||
itemizer: (item: JsonObject) => [Hash256?, ShaMapNode?, ShaMapLeaf?],
|
||||
itemsJson: Array<JsonObject>
|
||||
): Hash256 {
|
||||
const map = new ShaMap();
|
||||
itemsJson.forEach((item) => map.addItem(...itemizer(item)));
|
||||
return map.hash();
|
||||
}
|
||||
|
||||
function transactionItem(json) {
|
||||
/**
|
||||
* Interface describing a transaction item
|
||||
*/
|
||||
interface transactionItemObject extends JsonObject {
|
||||
hash: string;
|
||||
metaData: JsonObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a transaction into an index and an item
|
||||
*
|
||||
* @param json transaction with metadata
|
||||
* @returns a tuple of index and item to be added to SHAMap
|
||||
*/
|
||||
function transactionItemizer(
|
||||
json: transactionItemObject
|
||||
): [Hash256, ShaMapNode, undefined] {
|
||||
assert(json.hash);
|
||||
const index = Hash256.from(json.hash);
|
||||
const item = {
|
||||
@@ -25,11 +55,26 @@ function transactionItem(json) {
|
||||
serializer.writeLengthEncoded(STObject.from(json));
|
||||
serializer.writeLengthEncoded(STObject.from(json.metaData));
|
||||
},
|
||||
};
|
||||
return [index, item];
|
||||
} as ShaMapNode;
|
||||
return [index, item, undefined];
|
||||
}
|
||||
|
||||
function entryItem(json) {
|
||||
/**
|
||||
* Interface describing an entry item
|
||||
*/
|
||||
interface entryItemObject extends JsonObject {
|
||||
index: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an entry to a pair Hash256 and ShaMapNode
|
||||
*
|
||||
* @param json JSON describing a ledger entry item
|
||||
* @returns a tuple of index and item to be added to SHAMap
|
||||
*/
|
||||
function entryItemizer(
|
||||
json: entryItemObject
|
||||
): [Hash256, ShaMapNode, undefined] {
|
||||
const index = Hash256.from(json.index);
|
||||
const bytes = serializeObject(json);
|
||||
const item = {
|
||||
@@ -39,29 +84,95 @@ function entryItem(json) {
|
||||
toBytesSink(sink) {
|
||||
sink.put(bytes);
|
||||
},
|
||||
};
|
||||
return [index, item];
|
||||
} as ShaMapNode;
|
||||
return [index, item, undefined];
|
||||
}
|
||||
|
||||
const transactionTreeHash = _.partial(computeHash, transactionItem);
|
||||
const accountStateHash = _.partial(computeHash, entryItem);
|
||||
/**
|
||||
* Function computing the hash of a transaction tree
|
||||
*
|
||||
* @param param An array of transaction objects to hash
|
||||
* @returns A Hash256 object
|
||||
*/
|
||||
function transactionTreeHash(param: Array<JsonObject>): Hash256 {
|
||||
const itemizer = transactionItemizer as (
|
||||
json: JsonObject
|
||||
) => [Hash256, ShaMapNode, undefined];
|
||||
return computeHash(itemizer, param);
|
||||
}
|
||||
|
||||
function ledgerHash(header) {
|
||||
/**
|
||||
* Function computing the hash of accountState
|
||||
*
|
||||
* @param param A list of accountStates hash
|
||||
* @returns A Hash256 object
|
||||
*/
|
||||
function accountStateHash(param: Array<JsonObject>): Hash256 {
|
||||
const itemizer = entryItemizer as (
|
||||
json: JsonObject
|
||||
) => [Hash256, ShaMapNode, undefined];
|
||||
return computeHash(itemizer, param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface describing a ledger header
|
||||
*/
|
||||
interface ledgerObject {
|
||||
ledger_index: number;
|
||||
total_coins: string | number | bigint;
|
||||
parent_hash: string;
|
||||
transaction_hash: string;
|
||||
account_hash: string;
|
||||
parent_close_time: number;
|
||||
close_time: number;
|
||||
close_time_resolution: number;
|
||||
close_flags: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize and hash a ledger header
|
||||
*
|
||||
* @param header a ledger header
|
||||
* @returns the hash of header
|
||||
*/
|
||||
function ledgerHash(header: ledgerObject): Hash256 {
|
||||
const hash = new Sha512Half();
|
||||
hash.put(HashPrefix.ledgerHeader);
|
||||
assert(header.parent_close_time !== undefined);
|
||||
assert(header.close_flags !== undefined);
|
||||
|
||||
coreTypes.UInt32.from(header.ledger_index).toBytesSink(hash);
|
||||
coreTypes.UInt64.from(BigInt(header.total_coins)).toBytesSink(hash);
|
||||
coreTypes.Hash256.from(header.parent_hash).toBytesSink(hash);
|
||||
coreTypes.Hash256.from(header.transaction_hash).toBytesSink(hash);
|
||||
coreTypes.Hash256.from(header.account_hash).toBytesSink(hash);
|
||||
coreTypes.UInt32.from(header.parent_close_time).toBytesSink(hash);
|
||||
coreTypes.UInt32.from(header.close_time).toBytesSink(hash);
|
||||
coreTypes.UInt8.from(header.close_time_resolution).toBytesSink(hash);
|
||||
coreTypes.UInt8.from(header.close_flags).toBytesSink(hash);
|
||||
UInt32.from<number>(header.ledger_index).toBytesSink(hash);
|
||||
UInt64.from<bigint>(BigInt(header.total_coins)).toBytesSink(hash);
|
||||
Hash256.from(header.parent_hash).toBytesSink(hash);
|
||||
Hash256.from(header.transaction_hash).toBytesSink(hash);
|
||||
Hash256.from(header.account_hash).toBytesSink(hash);
|
||||
UInt32.from<number>(header.parent_close_time).toBytesSink(hash);
|
||||
UInt32.from<number>(header.close_time).toBytesSink(hash);
|
||||
UInt8.from<number>(header.close_time_resolution).toBytesSink(hash);
|
||||
UInt8.from<number>(header.close_flags).toBytesSink(hash);
|
||||
return hash.finish();
|
||||
}
|
||||
|
||||
export { accountStateHash, transactionTreeHash, ledgerHash };
|
||||
/**
|
||||
* Decodes a serialized ledger header
|
||||
*
|
||||
* @param binary A serialized ledger header
|
||||
* @returns A JSON object describing a ledger header
|
||||
*/
|
||||
function decodeLedgerData(binary: string): ledgerObject {
|
||||
assert(typeof binary === "string", "binary must be a hex string");
|
||||
const parser = new BinaryParser(binary);
|
||||
return {
|
||||
ledger_index: parser.readUInt32(),
|
||||
total_coins: parser.readType(UInt64).valueOf().toString(),
|
||||
parent_hash: parser.readType(Hash256).toHex(),
|
||||
transaction_hash: parser.readType(Hash256).toHex(),
|
||||
account_hash: parser.readType(Hash256).toHex(),
|
||||
parent_close_time: parser.readUInt32(),
|
||||
close_time: parser.readUInt32(),
|
||||
close_time_resolution: parser.readUInt8(),
|
||||
close_flags: parser.readUInt8(),
|
||||
};
|
||||
}
|
||||
|
||||
export { accountStateHash, transactionTreeHash, ledgerHash, decodeLedgerData };
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
const Decimal = require("decimal.js");
|
||||
import { bytesToHex, slice, parseBytes } from "./utils/bytes-utils";
|
||||
import { coreTypes } from "./types";
|
||||
import { Decimal } from "decimal.js";
|
||||
|
||||
module.exports = {
|
||||
encode(arg) {
|
||||
const quality = arg instanceof Decimal ? arg : new Decimal(arg);
|
||||
const exponent = quality.e - 15;
|
||||
const qualityString = quality
|
||||
.times("1e" + -exponent)
|
||||
.abs()
|
||||
.toString();
|
||||
/**
|
||||
* class for encoding and decoding quality
|
||||
*/
|
||||
class quality {
|
||||
/**
|
||||
* Encode quality amount
|
||||
*
|
||||
* @param arg string representation of an amount
|
||||
* @returns Serialized quality
|
||||
*/
|
||||
static encode(quality: string): Buffer {
|
||||
const decimal = new Decimal(quality);
|
||||
const exponent = decimal.e - 15;
|
||||
const qualityString = decimal.times(`1e${-exponent}`).abs().toString();
|
||||
const bytes = coreTypes.UInt64.from(BigInt(qualityString)).toBytes();
|
||||
bytes[0] = exponent + 100;
|
||||
return bytes;
|
||||
},
|
||||
decode(arg) {
|
||||
const bytes = slice(parseBytes(arg), -8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode quality amount
|
||||
*
|
||||
* @param arg hex-string denoting serialized quality
|
||||
* @returns deserialized quality
|
||||
*/
|
||||
static decode(quality: string): Decimal {
|
||||
const bytes = Buffer.from(quality, "hex").slice(-8);
|
||||
const exponent = bytes[0] - 100;
|
||||
const mantissa = new Decimal("0x" + bytesToHex(slice(bytes, 1)));
|
||||
return mantissa.times("1e" + exponent);
|
||||
},
|
||||
};
|
||||
const mantissa = new Decimal(`0x${bytes.slice(1).toString("hex")}`);
|
||||
return mantissa.times(`1e${exponent}`);
|
||||
}
|
||||
}
|
||||
|
||||
export { quality };
|
||||
|
||||
@@ -127,7 +127,8 @@ class BinarySerializer {
|
||||
*/
|
||||
writeFieldAndValue(field: FieldInstance, value: SerializedType): void {
|
||||
const associatedValue = field.associatedType.from(value);
|
||||
assert(associatedValue.toBytesSink, field.name);
|
||||
assert(associatedValue.toBytesSink !== undefined);
|
||||
assert(field.name !== undefined);
|
||||
|
||||
this.sink.put(field.header);
|
||||
|
||||
|
||||
@@ -1,118 +1,181 @@
|
||||
import { strict as assert } from "assert";
|
||||
import { makeClass } from "./utils/make-class";
|
||||
import { coreTypes } from "./types";
|
||||
import { HashPrefix } from "./hash-prefixes";
|
||||
import { Sha512Half } from "./hashes";
|
||||
import { Hash256 } from "./types/hash-256";
|
||||
import { BytesList } from "./serdes/binary-serializer";
|
||||
|
||||
const ShaMapNode = makeClass(
|
||||
{
|
||||
virtuals: {
|
||||
hashPrefix() {},
|
||||
isLeaf() {},
|
||||
isInner() {},
|
||||
},
|
||||
cached: {
|
||||
hash() {
|
||||
const hasher = Sha512Half.put(this.hashPrefix());
|
||||
this.toBytesSink(hasher);
|
||||
return hasher.finish();
|
||||
},
|
||||
},
|
||||
},
|
||||
undefined
|
||||
);
|
||||
/**
|
||||
* Abstract class describing a SHAMapNode
|
||||
*/
|
||||
abstract class ShaMapNode {
|
||||
abstract hashPrefix(): Buffer;
|
||||
abstract isLeaf(): boolean;
|
||||
abstract isInner(): boolean;
|
||||
abstract toBytesSink(list: BytesList): void;
|
||||
abstract hash(): Hash256;
|
||||
}
|
||||
|
||||
const ShaMapLeaf = makeClass(
|
||||
{
|
||||
inherits: ShaMapNode,
|
||||
ShaMapLeaf(index, item) {
|
||||
ShaMapNode.call(this);
|
||||
this.index = index;
|
||||
this.item = item;
|
||||
},
|
||||
isLeaf() {
|
||||
return true;
|
||||
},
|
||||
isInner() {
|
||||
return false;
|
||||
},
|
||||
hashPrefix() {
|
||||
return this.item.hashPrefix();
|
||||
},
|
||||
toBytesSink(sink) {
|
||||
this.item.toBytesSink(sink);
|
||||
this.index.toBytesSink(sink);
|
||||
},
|
||||
},
|
||||
undefined
|
||||
);
|
||||
/**
|
||||
* Class describing a Leaf of SHAMap
|
||||
*/
|
||||
class ShaMapLeaf extends ShaMapNode {
|
||||
constructor(public index: Hash256, public item?: ShaMapNode) {
|
||||
super();
|
||||
}
|
||||
|
||||
const $uper = ShaMapNode.prototype;
|
||||
/**
|
||||
* @returns true as ShaMapLeaf is a leaf node
|
||||
*/
|
||||
isLeaf(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ShaMapInner = makeClass(
|
||||
{
|
||||
inherits: ShaMapNode,
|
||||
ShaMapInner(depth = 0) {
|
||||
ShaMapNode.call(this);
|
||||
this.depth = depth;
|
||||
this.slotBits = 0;
|
||||
this.branches = Array(16);
|
||||
},
|
||||
isInner() {
|
||||
return true;
|
||||
},
|
||||
isLeaf() {
|
||||
return false;
|
||||
},
|
||||
hashPrefix() {
|
||||
return HashPrefix.innerNode;
|
||||
},
|
||||
setBranch(slot, branch) {
|
||||
this.slotBits = this.slotBits | (1 << slot);
|
||||
this.branches[slot] = branch;
|
||||
},
|
||||
empty() {
|
||||
return this.slotBits === 0;
|
||||
},
|
||||
hash() {
|
||||
if (this.empty()) {
|
||||
return coreTypes.Hash256.ZERO_256;
|
||||
}
|
||||
return $uper.hash.call(this);
|
||||
},
|
||||
toBytesSink(sink) {
|
||||
for (let i = 0; i < this.branches.length; i++) {
|
||||
const branch = this.branches[i];
|
||||
const hash = branch ? branch.hash() : coreTypes.Hash256.ZERO_256;
|
||||
hash.toBytesSink(sink);
|
||||
}
|
||||
},
|
||||
addItem(index, item, leaf) {
|
||||
assert(index instanceof coreTypes.Hash256);
|
||||
const nibble = index.nibblet(this.depth);
|
||||
const existing = this.branches[nibble];
|
||||
if (!existing) {
|
||||
this.setBranch(nibble, leaf || new ShaMapLeaf(index, item));
|
||||
} else if (existing.isLeaf()) {
|
||||
const newInner = new ShaMapInner(this.depth + 1);
|
||||
newInner.addItem(existing.index, null, existing);
|
||||
newInner.addItem(index, item, leaf);
|
||||
this.setBranch(nibble, newInner);
|
||||
} else if (existing.isInner()) {
|
||||
existing.addItem(index, item, leaf);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
},
|
||||
},
|
||||
undefined
|
||||
);
|
||||
/**
|
||||
* @returns false as ShaMapLeaf is not an inner node
|
||||
*/
|
||||
isInner(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ShaMap = makeClass(
|
||||
{
|
||||
inherits: ShaMapInner,
|
||||
},
|
||||
undefined
|
||||
);
|
||||
/**
|
||||
* Get the prefix of the this.item
|
||||
*
|
||||
* @returns The hash prefix, unless this.item is undefined, then it returns an empty Buffer
|
||||
*/
|
||||
hashPrefix(): Buffer {
|
||||
return this.item === undefined ? Buffer.alloc(0) : this.item.hashPrefix();
|
||||
}
|
||||
|
||||
export { ShaMap };
|
||||
/**
|
||||
* Hash the bytes representation of this
|
||||
*
|
||||
* @returns hash of this.item concatenated with this.index
|
||||
*/
|
||||
hash(): Hash256 {
|
||||
const hash = Sha512Half.put(this.hashPrefix());
|
||||
this.toBytesSink(hash);
|
||||
return hash.finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the bytes representation of this to a BytesList
|
||||
* @param list BytesList to write bytes to
|
||||
*/
|
||||
toBytesSink(list: BytesList): void {
|
||||
if (this.item !== undefined) {
|
||||
this.item.toBytesSink(list);
|
||||
}
|
||||
this.index.toBytesSink(list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class defining an Inner Node of a SHAMap
|
||||
*/
|
||||
class ShaMapInner extends ShaMapNode {
|
||||
private slotBits = 0;
|
||||
private branches: Array<ShaMapNode> = Array(16);
|
||||
|
||||
constructor(private depth: number = 0) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true as ShaMapInner is an inner node
|
||||
*/
|
||||
isInner(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns false as ShaMapInner is not a leaf node
|
||||
*/
|
||||
isLeaf(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash prefix for this node
|
||||
*
|
||||
* @returns hash prefix describing an inner node
|
||||
*/
|
||||
hashPrefix(): Buffer {
|
||||
return HashPrefix.innerNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a branch of this node to be another node
|
||||
*
|
||||
* @param slot Slot to add branch to this.branches
|
||||
* @param branch Branch to add
|
||||
*/
|
||||
setBranch(slot: number, branch: ShaMapNode): void {
|
||||
this.slotBits = this.slotBits | (1 << slot);
|
||||
this.branches[slot] = branch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if node is empty
|
||||
*/
|
||||
empty(): boolean {
|
||||
return this.slotBits === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the hash of this node
|
||||
*
|
||||
* @returns The hash of this node
|
||||
*/
|
||||
hash(): Hash256 {
|
||||
if (this.empty()) {
|
||||
return coreTypes.Hash256.ZERO_256;
|
||||
}
|
||||
const hash = Sha512Half.put(this.hashPrefix());
|
||||
this.toBytesSink(hash);
|
||||
return hash.finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the bytes representation of this node to a BytesList
|
||||
*
|
||||
* @param list BytesList to write bytes to
|
||||
*/
|
||||
toBytesSink(list: BytesList): void {
|
||||
for (let i = 0; i < this.branches.length; i++) {
|
||||
const branch = this.branches[i];
|
||||
const hash = branch ? branch.hash() : coreTypes.Hash256.ZERO_256;
|
||||
hash.toBytesSink(list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to the SHAMap
|
||||
*
|
||||
* @param index Hash of the index of the item being inserted
|
||||
* @param item Item to insert in the map
|
||||
* @param leaf Leaf node to insert when branch doesn't exist
|
||||
*/
|
||||
addItem(index?: Hash256, item?: ShaMapNode, leaf?: ShaMapLeaf): void {
|
||||
assert(index !== undefined);
|
||||
const nibble = index.nibblet(this.depth);
|
||||
const existing = this.branches[nibble];
|
||||
|
||||
if (existing === undefined) {
|
||||
this.setBranch(nibble, leaf || new ShaMapLeaf(index, item));
|
||||
} else if (existing instanceof ShaMapLeaf) {
|
||||
const newInner = new ShaMapInner(this.depth + 1);
|
||||
newInner.addItem(existing.index, undefined, existing);
|
||||
newInner.addItem(index, item, leaf);
|
||||
this.setBranch(nibble, newInner);
|
||||
} else if (existing instanceof ShaMapInner) {
|
||||
existing.addItem(index, item, leaf);
|
||||
} else {
|
||||
throw new Error("invalid ShaMap.addItem call");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShaMap extends ShaMapInner {}
|
||||
|
||||
export { ShaMap, ShaMapNode, ShaMapLeaf };
|
||||
|
||||
@@ -17,13 +17,18 @@ class AccountID extends Hash160 {
|
||||
* @param value either an existing AccountID, a hex-string, or a base58 r-Address
|
||||
* @returns an AccountID object
|
||||
*/
|
||||
static from(value: AccountID | string): AccountID {
|
||||
if (value instanceof this) {
|
||||
static from<T extends Hash160 | string>(value: T): AccountID {
|
||||
if (value instanceof AccountID) {
|
||||
return value;
|
||||
}
|
||||
return /^r/.test(value)
|
||||
? this.fromBase58(value)
|
||||
: new AccountID(Buffer.from(value, "hex"));
|
||||
|
||||
if (typeof value === "string") {
|
||||
return /^r/.test(value)
|
||||
? this.fromBase58(value)
|
||||
: new AccountID(Buffer.from(value, "hex"));
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct AccountID from value given");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Decimal } from "decimal.js";
|
||||
import { SerializedType } from "./serialized-type";
|
||||
|
||||
import { BinaryParser } from "../serdes/binary-parser";
|
||||
import { Currency } from "./currency";
|
||||
|
||||
import { AccountID } from "./account-id";
|
||||
import { Currency } from "./currency";
|
||||
import { JsonObject, SerializedType } from "./serialized-type";
|
||||
|
||||
/**
|
||||
* Constants for validating amounts
|
||||
@@ -24,12 +26,25 @@ Decimal.config({
|
||||
/**
|
||||
* Interface for JSON objects that represent amounts
|
||||
*/
|
||||
interface AmountObject {
|
||||
interface AmountObject extends JsonObject {
|
||||
value: string;
|
||||
currency: string;
|
||||
issuer: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for AmountObject
|
||||
*/
|
||||
function isAmountObject(arg): arg is AmountObject {
|
||||
const keys = Object.keys(arg).sort();
|
||||
return (
|
||||
keys.length === 3 &&
|
||||
keys[0] === "currency" &&
|
||||
keys[1] === "issuer" &&
|
||||
keys[2] === "value"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for serializing/Deserializing Amounts
|
||||
*/
|
||||
@@ -45,10 +60,11 @@ class Amount extends SerializedType {
|
||||
/**
|
||||
* Construct an amount from an IOU or string amount
|
||||
*
|
||||
* @param value An Amount, object representing an IOU, or a string representing an integer amount
|
||||
* @param value An Amount, object representing an IOU, or a string
|
||||
* representing an integer amount
|
||||
* @returns An Amount object
|
||||
*/
|
||||
static from(value: Amount | AmountObject | string): Amount {
|
||||
static from<T extends Amount | AmountObject | string>(value: T): Amount {
|
||||
if (value instanceof Amount) {
|
||||
return value;
|
||||
}
|
||||
@@ -63,7 +79,9 @@ class Amount extends SerializedType {
|
||||
amount[0] |= 0x40;
|
||||
|
||||
return new Amount(amount);
|
||||
} else if (typeof value === "object") {
|
||||
}
|
||||
|
||||
if (isAmountObject(value)) {
|
||||
const number = new Decimal(value.value);
|
||||
Amount.assertIouIsValid(number);
|
||||
|
||||
@@ -92,6 +110,7 @@ class Amount extends SerializedType {
|
||||
const issuer = AccountID.from(value.issuer).toBytes();
|
||||
return new Amount(Buffer.concat([amount, currency, issuer]));
|
||||
}
|
||||
|
||||
throw new Error("Invalid type to construct an Amount");
|
||||
}
|
||||
|
||||
@@ -123,8 +142,8 @@ class Amount extends SerializedType {
|
||||
} else {
|
||||
const parser = new BinaryParser(this.toString());
|
||||
const mantissa = parser.read(8);
|
||||
const currency = Currency.fromParser(parser);
|
||||
const issuer = AccountID.fromParser(parser);
|
||||
const currency = Currency.fromParser(parser) as Currency;
|
||||
const issuer = AccountID.fromParser(parser) as AccountID;
|
||||
|
||||
const b1 = mantissa[0];
|
||||
const b2 = mantissa[1];
|
||||
@@ -189,7 +208,8 @@ class Amount extends SerializedType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the value after being multiplied by the exponent does not contain a decimal.
|
||||
* Ensure that the value after being multiplied by the exponent does not
|
||||
* contain a decimal.
|
||||
*
|
||||
* @param decimal a Decimal object
|
||||
* @returns a string of the object without a decimal
|
||||
@@ -215,4 +235,4 @@ class Amount extends SerializedType {
|
||||
}
|
||||
}
|
||||
|
||||
export { Amount };
|
||||
export { Amount, AmountObject };
|
||||
|
||||
@@ -26,8 +26,16 @@ class Blob extends SerializedType {
|
||||
* @param value existing Blob object or a hex-string
|
||||
* @returns A Blob object
|
||||
*/
|
||||
static from(value: Blob | string): Blob {
|
||||
return value instanceof Blob ? value : new Blob(Buffer.from(value, "hex"));
|
||||
static from<T extends Blob | string>(value: T): Blob {
|
||||
if (value instanceof Blob) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
return new Blob(Buffer.from(value, "hex"));
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct Blob from value given");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,10 +113,16 @@ class Currency extends Hash160 {
|
||||
*
|
||||
* @param val Currency object or a string representation of a currency
|
||||
*/
|
||||
static from(val: Currency | string): Currency {
|
||||
return val instanceof this
|
||||
? val
|
||||
: new Currency(bytesFromRepresentation(val));
|
||||
static from<T extends Hash160 | string>(value: T): Currency {
|
||||
if (value instanceof Currency) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
return new Currency(bytesFromRepresentation(value));
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct Currency from value given");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,9 +15,18 @@ class Hash extends Comparable {
|
||||
* 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: Hash | string): Hash {
|
||||
return value instanceof this ? value : new this(Buffer.from(value, "hex"));
|
||||
*/
|
||||
static from<T extends Hash | string>(value: T): Hash {
|
||||
if(value instanceof this) {
|
||||
return value
|
||||
}
|
||||
|
||||
if(typeof value === "string") {
|
||||
return new this(Buffer.from(value, "hex"));
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct Hash from given value");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AccountID } from "./account-id";
|
||||
import { Currency } from "./currency";
|
||||
import { BinaryParser } from "../serdes/binary-parser";
|
||||
import { SerializedType } from "./serialized-type";
|
||||
import { SerializedType, JsonObject } from "./serialized-type";
|
||||
|
||||
/**
|
||||
* Constants for separating Paths in a PathSet
|
||||
@@ -19,12 +19,33 @@ const TYPE_ISSUER = 0x20;
|
||||
/**
|
||||
* The object representation of a Hop, an issuer AccountID, an account AccountID, and a Currency
|
||||
*/
|
||||
interface HopObject {
|
||||
interface HopObject extends JsonObject {
|
||||
issuer?: string;
|
||||
account?: string;
|
||||
currency?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeGuard for HopObject
|
||||
*/
|
||||
function isHopObject(arg): arg is HopObject {
|
||||
return (arg.issuer !== undefined ||
|
||||
arg.account !== undefined ||
|
||||
arg.currency !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeGuard for PathSet
|
||||
*/
|
||||
function isPathSet(arg): arg is Array<Array<HopObject>> {
|
||||
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])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize and Deserialize a Hop
|
||||
*/
|
||||
@@ -96,15 +117,15 @@ class Hop extends SerializedType {
|
||||
|
||||
const result: HopObject = {};
|
||||
if (type & TYPE_ACCOUNT) {
|
||||
result.account = AccountID.fromParser(hopParser).toJSON();
|
||||
result.account = (AccountID.fromParser(hopParser) as AccountID).toJSON();
|
||||
}
|
||||
|
||||
if (type & TYPE_CURRENCY) {
|
||||
result.currency = Currency.fromParser(hopParser).toJSON();
|
||||
result.currency = (Currency.fromParser(hopParser) as Currency).toJSON();
|
||||
}
|
||||
|
||||
if (type & TYPE_ISSUER) {
|
||||
result.issuer = AccountID.fromParser(hopParser).toJSON();
|
||||
result.issuer = (AccountID.fromParser(hopParser) as AccountID).toJSON();
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -169,7 +190,7 @@ class Path extends SerializedType {
|
||||
*
|
||||
* @returns an Array of HopObject constructed from this.bytes
|
||||
*/
|
||||
toJSON() {
|
||||
toJSON(): Array<HopObject> {
|
||||
const json: Array<HopObject> = [];
|
||||
const pathParser = new BinaryParser(this.toString());
|
||||
|
||||
@@ -191,21 +212,25 @@ class PathSet extends SerializedType {
|
||||
* @param value A PathSet or Array of Array of HopObjects
|
||||
* @returns the PathSet constructed from value
|
||||
*/
|
||||
static from(value: PathSet | Array<Array<HopObject>>): PathSet {
|
||||
static from<T extends PathSet | Array<Array<HopObject>>>(value: T): PathSet {
|
||||
if (value instanceof PathSet) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const bytes: Array<Buffer> = [];
|
||||
if (isPathSet(value)) {
|
||||
const bytes: Array<Buffer> = [];
|
||||
|
||||
value.forEach((path: Array<HopObject>) => {
|
||||
bytes.push(Path.from(path).toBytes());
|
||||
bytes.push(Buffer.from([PATH_SEPARATOR_BYTE]));
|
||||
});
|
||||
value.forEach((path: Array<HopObject>) => {
|
||||
bytes.push(Path.from(path).toBytes());
|
||||
bytes.push(Buffer.from([PATH_SEPARATOR_BYTE]));
|
||||
});
|
||||
|
||||
bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE]);
|
||||
bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE]);
|
||||
|
||||
return new PathSet(Buffer.concat(bytes));
|
||||
return new PathSet(Buffer.concat(bytes));
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct PathSet from given value");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { BytesList } from "../serdes/binary-serializer";
|
||||
import { BinaryParser } from "../serdes/binary-parser";
|
||||
|
||||
type JSON = string | number | boolean | null | undefined | JSON[] | JsonObject;
|
||||
|
||||
type JsonObject = { [key: string]: JSON };
|
||||
|
||||
/**
|
||||
* The base class for all binary-codec types
|
||||
*/
|
||||
@@ -16,7 +20,7 @@ class SerializedType {
|
||||
return this.fromParser(parser, hint);
|
||||
}
|
||||
|
||||
static from(value: any): SerializedType {
|
||||
static from(value: SerializedType | JSON | bigint): SerializedType {
|
||||
throw new Error("from not implemented");
|
||||
return this.from(value);
|
||||
}
|
||||
@@ -58,7 +62,7 @@ class SerializedType {
|
||||
*
|
||||
* @returns any type, if not overloaded returns hexString representation of bytes
|
||||
*/
|
||||
toJSON(): any {
|
||||
toJSON(): JSON {
|
||||
return this.toHex();
|
||||
}
|
||||
|
||||
@@ -101,8 +105,10 @@ class Comparable extends SerializedType {
|
||||
* @returns A number denoting the relationship of this and other
|
||||
*/
|
||||
compareTo(other: Comparable): number {
|
||||
throw new Error(`cannot compare ${this} and ${other}`);
|
||||
throw new Error(
|
||||
`cannot compare ${this.toString()} and ${other.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { SerializedType, Comparable };
|
||||
export { SerializedType, Comparable, JSON, JsonObject };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SerializedType } from "./serialized-type";
|
||||
import { SerializedType, JsonObject } from "./serialized-type";
|
||||
import { STObject } from "./st-object";
|
||||
import { BinaryParser } from "../serdes/binary-parser";
|
||||
|
||||
@@ -7,6 +7,15 @@ const ARRAY_END_MARKER_NAME = "ArrayEndMarker";
|
||||
|
||||
const OBJECT_END_MARKER = Buffer.from([0xe1]);
|
||||
|
||||
/**
|
||||
* TypeGuard for Array<JsonObject>
|
||||
*/
|
||||
function isObjects(args): args is Array<JsonObject> {
|
||||
return (
|
||||
Array.isArray(args) && (args.length === 0 || typeof args[0] === "object")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for serializing and deserializing Arrays of Objects
|
||||
*/
|
||||
@@ -43,18 +52,22 @@ class STArray extends SerializedType {
|
||||
* @param value STArray or Array of Objects to parse into an STArray
|
||||
* @returns An STArray object
|
||||
*/
|
||||
static from(value: STArray | Array<object>): STArray {
|
||||
static from<T extends STArray | Array<JsonObject>>(value: T): STArray {
|
||||
if (value instanceof STArray) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const bytes: Array<Buffer> = [];
|
||||
value.forEach((obj) => {
|
||||
bytes.push(STObject.from(obj).toBytes());
|
||||
});
|
||||
if (isObjects(value)) {
|
||||
const bytes: Array<Buffer> = [];
|
||||
value.forEach((obj) => {
|
||||
bytes.push(STObject.from(obj).toBytes());
|
||||
});
|
||||
|
||||
bytes.push(ARRAY_END_MARKER);
|
||||
return new STArray(Buffer.concat(bytes));
|
||||
bytes.push(ARRAY_END_MARKER);
|
||||
return new STArray(Buffer.concat(bytes));
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct Currency from value given");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,8 +75,8 @@ class STArray extends SerializedType {
|
||||
*
|
||||
* @returns An Array of JSON objects
|
||||
*/
|
||||
toJSON(): Array<object> {
|
||||
const result: Array<object> = [];
|
||||
toJSON(): Array<JsonObject> {
|
||||
const result: Array<JsonObject> = [];
|
||||
|
||||
const arrayParser = new BinaryParser(this.toString());
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Field } from "../enums";
|
||||
import { SerializedType } from "./serialized-type";
|
||||
import { Field, FieldInstance } from "../enums";
|
||||
import { SerializedType, JsonObject } from "./serialized-type";
|
||||
import { BinaryParser } from "../serdes/binary-parser";
|
||||
import { BinarySerializer, BytesList } from "../serdes/binary-serializer";
|
||||
|
||||
@@ -45,8 +45,8 @@ class STObject extends SerializedType {
|
||||
* @param filter optional, denote which field to include in serialized object
|
||||
* @returns a STObject object
|
||||
*/
|
||||
static from(
|
||||
value: STObject | object,
|
||||
static from<T extends STObject | JsonObject>(
|
||||
value: T,
|
||||
filter?: (...any) => boolean
|
||||
): STObject {
|
||||
if (value instanceof STObject) {
|
||||
@@ -57,8 +57,8 @@ class STObject extends SerializedType {
|
||||
const bytes: BinarySerializer = new BinarySerializer(list);
|
||||
|
||||
let sorted = Object.keys(value)
|
||||
.map((f) => Field[f])
|
||||
.filter((f) => f !== undefined && f.isSerialized)
|
||||
.map((f: string): FieldInstance => Field[f] as FieldInstance)
|
||||
.filter((f: FieldInstance): boolean => f !== undefined && f.isSerialized)
|
||||
.sort((a, b) => {
|
||||
return a.ordinal - b.ordinal;
|
||||
});
|
||||
@@ -84,7 +84,7 @@ class STObject extends SerializedType {
|
||||
*
|
||||
* @returns a JSON object
|
||||
*/
|
||||
toJSON(): object {
|
||||
toJSON(): JsonObject {
|
||||
const objectParser = new BinaryParser(this.toString());
|
||||
const accumulator = {};
|
||||
|
||||
|
||||
@@ -23,14 +23,18 @@ class UInt16 extends UInt {
|
||||
*
|
||||
* @param val UInt16 object or number
|
||||
*/
|
||||
static from(val: UInt16 | number): UInt16 {
|
||||
static from<T extends UInt16 | number>(val: T): UInt16 {
|
||||
if (val instanceof UInt16) {
|
||||
return val;
|
||||
}
|
||||
|
||||
const buf = Buffer.alloc(UInt16.width);
|
||||
buf.writeUInt16BE(val);
|
||||
return new UInt16(buf);
|
||||
if (typeof val === "number") {
|
||||
const buf = Buffer.alloc(UInt16.width);
|
||||
buf.writeUInt16BE(val);
|
||||
return new UInt16(buf);
|
||||
}
|
||||
|
||||
throw new Error("Can not construct UInt16 with given value");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,14 +23,26 @@ class UInt32 extends UInt {
|
||||
*
|
||||
* @param val UInt32 object or number
|
||||
*/
|
||||
static from(val: UInt32 | number): UInt32 {
|
||||
static from<T extends UInt32 | number | string>(val: T): UInt32 {
|
||||
if (val instanceof UInt32) {
|
||||
return val;
|
||||
}
|
||||
|
||||
const buf = Buffer.alloc(UInt32.width);
|
||||
buf.writeUInt32BE(val);
|
||||
return new UInt32(buf);
|
||||
|
||||
if (typeof val === "string") {
|
||||
const num = Number.parseInt(val);
|
||||
buf.writeUInt32BE(num);
|
||||
return new UInt32(buf);
|
||||
}
|
||||
|
||||
if (typeof val === "number") {
|
||||
buf.writeUInt32BE(val);
|
||||
return new UInt32(buf);
|
||||
}
|
||||
|
||||
console.log(typeof val);
|
||||
throw new Error("Cannot construct UInt32 from given value");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ class UInt64 extends UInt {
|
||||
* @param val A UInt64, hex-string, bigint, or number
|
||||
* @returns A UInt64 object
|
||||
*/
|
||||
static from(val: UInt64 | string | bigint | number): UInt64 {
|
||||
static from<T extends UInt64 | string | bigint | number>(val: T): UInt64 {
|
||||
if (val instanceof UInt64) {
|
||||
return val;
|
||||
}
|
||||
@@ -38,17 +38,23 @@ class UInt64 extends UInt {
|
||||
throw new Error("value must be an unsigned integer");
|
||||
}
|
||||
buf.writeBigUInt64BE(BigInt(val));
|
||||
} else if (typeof val === "string") {
|
||||
if (!HEX_REGEX.test(val)) {
|
||||
throw new Error(val + "is not a valid hex-string");
|
||||
}
|
||||
buf = Buffer.from(val, "hex");
|
||||
} else {
|
||||
// typeof val === bigint
|
||||
buf.writeBigUInt64BE(val);
|
||||
return new UInt64(buf);
|
||||
}
|
||||
|
||||
return new UInt64(buf);
|
||||
if (typeof val === "string") {
|
||||
if (!HEX_REGEX.test(val)) {
|
||||
throw new Error(`${val} is not a valid hex-string`);
|
||||
}
|
||||
buf = Buffer.from(val, "hex");
|
||||
return new UInt64(buf);
|
||||
}
|
||||
|
||||
if (typeof val === "bigint") {
|
||||
buf.writeBigUInt64BE(val);
|
||||
return new UInt64(buf);
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct UInt64 from given value");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,14 +21,18 @@ class UInt8 extends UInt {
|
||||
*
|
||||
* @param val UInt8 object or number
|
||||
*/
|
||||
static from(val: UInt8 | number): UInt8 {
|
||||
static from<T extends UInt8 | number>(val: T): UInt8 {
|
||||
if (val instanceof UInt8) {
|
||||
return val;
|
||||
}
|
||||
|
||||
const buf = Buffer.alloc(UInt8.width);
|
||||
buf.writeUInt8(val);
|
||||
return new UInt8(buf);
|
||||
if (typeof val === "number") {
|
||||
const buf = Buffer.alloc(UInt8.width);
|
||||
buf.writeUInt8(val);
|
||||
return new UInt8(buf);
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct UInt8 from given value");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,13 @@ import { BinaryParser } from "../serdes/binary-parser";
|
||||
import { Hash256 } from "./hash-256";
|
||||
import { BytesList } from "../serdes/binary-serializer";
|
||||
|
||||
/**
|
||||
* TypeGuard for Array<string>
|
||||
*/
|
||||
function isStrings(arg): arg is Array<string> {
|
||||
return Array.isArray(arg) && (arg.length === 0 || typeof arg[0] === "string");
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for serializing and deserializing vectors of Hash256
|
||||
*/
|
||||
@@ -34,16 +41,20 @@ class Vector256 extends SerializedType {
|
||||
* @param value A Vector256 object or array of hex-strings representing Hash256's
|
||||
* @returns a Vector256 object
|
||||
*/
|
||||
static from(value: Vector256 | Array<string>): Vector256 {
|
||||
static from<T extends Vector256 | Array<string>>(value: T): Vector256 {
|
||||
if (value instanceof Vector256) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const bytesList = new BytesList();
|
||||
value.forEach((hash) => {
|
||||
Hash256.from(hash).toBytesSink(bytesList);
|
||||
});
|
||||
return new Vector256(bytesList.toBytes());
|
||||
if (isStrings(value)) {
|
||||
const bytesList = new BytesList();
|
||||
value.forEach((hash) => {
|
||||
Hash256.from(hash).toBytesSink(bytesList);
|
||||
});
|
||||
return new Vector256(bytesList.toBytes());
|
||||
}
|
||||
|
||||
throw new Error("Cannot construct Vector256 from given value");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import { strict as assert } from "assert";
|
||||
|
||||
function signum(a, b) {
|
||||
return a < b ? -1 : a === b ? 0 : 1;
|
||||
}
|
||||
|
||||
const hexLookup = (function () {
|
||||
const res = <any>{};
|
||||
const reverse = (res.reverse = new Array(256));
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const char = i.toString(16).toUpperCase();
|
||||
res[char] = i;
|
||||
|
||||
for (let j = 0; j < 16; j++) {
|
||||
const char2 = j.toString(16).toUpperCase();
|
||||
const byte = (i << 4) + j;
|
||||
const byteHex = char + char2;
|
||||
res[byteHex] = byte;
|
||||
reverse[byte] = byteHex;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
})();
|
||||
|
||||
const reverseHexLookup = hexLookup.reverse;
|
||||
|
||||
function bytesToHex(sequence) {
|
||||
const buf = Array(sequence.length);
|
||||
for (let i = sequence.length - 1; i >= 0; i--) {
|
||||
buf[i] = reverseHexLookup[sequence[i]];
|
||||
}
|
||||
return buf.join("");
|
||||
}
|
||||
|
||||
function byteForHex(hex) {
|
||||
const byte = hexLookup[hex];
|
||||
if (byte === undefined) {
|
||||
throw new Error(`\`${hex}\` is not a valid hex representation of a byte`);
|
||||
}
|
||||
return byte;
|
||||
}
|
||||
|
||||
function parseBytes(val, Output = <any>Array) {
|
||||
if (!val || val.length === undefined) {
|
||||
throw new Error(`${val} is not a sequence`);
|
||||
}
|
||||
|
||||
if (typeof val === "string") {
|
||||
const start = val.length % 2;
|
||||
const res = new Output((val.length + start) / 2);
|
||||
for (let i = val.length, to = res.length - 1; to >= start; i -= 2, to--) {
|
||||
res[to] = byteForHex(val.slice(i - 2, i));
|
||||
}
|
||||
if (start === 1) {
|
||||
res[0] = byteForHex(val[0]);
|
||||
}
|
||||
return res;
|
||||
} else if (val instanceof Output) {
|
||||
return val;
|
||||
} else if (Output === Uint8Array) {
|
||||
return new Output(val);
|
||||
}
|
||||
const res = new Output(val.length);
|
||||
for (let i = val.length - 1; i >= 0; i--) {
|
||||
res[i] = val[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function serializeUIntN(val, width) {
|
||||
const newBytes = new Uint8Array(width);
|
||||
const lastIx = width - 1;
|
||||
for (let i = 0; i < width; i++) {
|
||||
newBytes[lastIx - i] = (val >>> (i * 8)) & 0xff;
|
||||
}
|
||||
return newBytes;
|
||||
}
|
||||
|
||||
function compareBytes(a, b) {
|
||||
assert(a.length === b.length);
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const cmp = signum(a[i], b[i]);
|
||||
if (cmp !== 0) {
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function slice(val, startIx = 0, endIx = val.length, Output = val.constructor) {
|
||||
/* eslint-disable no-param-reassign */
|
||||
if (startIx < 0) {
|
||||
startIx += val.length;
|
||||
}
|
||||
if (endIx < 0) {
|
||||
endIx += val.length;
|
||||
}
|
||||
/* eslint-enable no-param-reassign */
|
||||
const len = endIx - startIx;
|
||||
const res = new Output(len);
|
||||
for (let i = endIx - 1; i >= startIx; i--) {
|
||||
res[i - startIx] = val[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export { parseBytes, bytesToHex, slice, compareBytes, serializeUIntN };
|
||||
@@ -1,85 +0,0 @@
|
||||
import _ = require("lodash");
|
||||
const inherits = require("inherits");
|
||||
|
||||
function forEach(obj, func) {
|
||||
Object.keys(obj || {}).forEach((k) => {
|
||||
func(obj[k], k);
|
||||
});
|
||||
}
|
||||
|
||||
function ensureArray(val) {
|
||||
return Array.isArray(val) ? val : [val];
|
||||
}
|
||||
|
||||
export function makeClass(klass_, definition_) {
|
||||
const definition = definition_ || klass_;
|
||||
let klass = typeof klass_ === "function" ? klass_ : null;
|
||||
if (klass === null) {
|
||||
for (const k in definition) {
|
||||
if (k[0].match(/[A-Z]/)) {
|
||||
klass = definition[k];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const parent = definition.inherits;
|
||||
if (parent) {
|
||||
if (klass === null) {
|
||||
klass = function () {
|
||||
parent.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
inherits(klass, parent);
|
||||
_.defaults(klass, parent);
|
||||
}
|
||||
if (klass === null) {
|
||||
klass = function () {};
|
||||
}
|
||||
const proto = klass.prototype;
|
||||
function addFunc(original, name, wrapper) {
|
||||
proto[name] = wrapper || original;
|
||||
}
|
||||
(definition.getters || []).forEach((k) => {
|
||||
const key = "_" + k;
|
||||
proto[k] = function () {
|
||||
return this[key];
|
||||
};
|
||||
});
|
||||
forEach(definition.virtuals, (f, n) => {
|
||||
addFunc(f, n, function () {
|
||||
throw new Error("unimplemented");
|
||||
});
|
||||
});
|
||||
forEach(definition.methods, addFunc);
|
||||
forEach(definition, (f, n) => {
|
||||
if (_.isFunction(f) && f !== klass) {
|
||||
addFunc(f, n, undefined);
|
||||
}
|
||||
});
|
||||
_.assign(klass, definition.statics);
|
||||
if (typeof klass.init === "function") {
|
||||
klass.init();
|
||||
}
|
||||
forEach(definition.cached, (f, n) => {
|
||||
const key = "_" + n;
|
||||
addFunc(f, n, function () {
|
||||
let value = this[key];
|
||||
if (value === undefined) {
|
||||
value = this[key] = f.call(this);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
});
|
||||
if (definition.mixins) {
|
||||
const mixins = {};
|
||||
// Right-most in the list win
|
||||
ensureArray(definition.mixins)
|
||||
.reverse()
|
||||
.forEach((o) => {
|
||||
_.defaults(mixins, o);
|
||||
});
|
||||
_.defaults(proto, mixins);
|
||||
}
|
||||
|
||||
return klass;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ const { Amount } = coreTypes
|
||||
const fixtures = loadFixture('data-driven-tests.json')
|
||||
|
||||
function amountErrorTests () {
|
||||
_.filter(fixtures.values_tests, { type: 'Amount' }).forEach(f => {
|
||||
fixtures.values_tests.filter(obj => obj.type === 'Amount').forEach(f => {
|
||||
// We only want these with errors
|
||||
if (!f.error) {
|
||||
return
|
||||
|
||||
@@ -10,7 +10,6 @@ const { Amount, Hash160 } = coreTypes
|
||||
const { makeParser, readJSON } = binary
|
||||
const { Field, TransactionType } = require('./../dist/enums')
|
||||
const { parseHexOnly, hexOnly, loadFixture } = require('./utils')
|
||||
const { bytesToHex } = require('../dist/utils/bytes-utils')
|
||||
const fixtures = loadFixture('data-driven-tests.json')
|
||||
const { BytesList } = require('../dist/serdes/binary-serializer')
|
||||
|
||||
@@ -107,9 +106,9 @@ function transactionParsingTests () {
|
||||
expect(parser.read(8)).not.toEqual([])
|
||||
expect(parser.readField()).toEqual(Field.SigningPubKey)
|
||||
expect(parser.readVariableLengthLength()).toBe(33)
|
||||
expect(bytesToHex(parser.read(33))).toEqual(tx_json.SigningPubKey)
|
||||
expect(parser.read(33).toString('hex').toUpperCase()).toEqual(tx_json.SigningPubKey)
|
||||
expect(parser.readField()).toEqual(Field.TxnSignature)
|
||||
expect(bytesToHex(parser.readVariableLength())).toEqual(tx_json.TxnSignature)
|
||||
expect(parser.readVariableLength().toString('hex').toUpperCase()).toEqual(tx_json.TxnSignature)
|
||||
expect(parser.readField()).toEqual(Field.Account)
|
||||
expect(encodeAccountID(parser.readVariableLength())).toEqual(tx_json.Account)
|
||||
expect(parser.end()).toBe(true)
|
||||
@@ -180,12 +179,13 @@ function transactionParsingTests () {
|
||||
const parser = makeParser(transaction.binary)
|
||||
const jsonFromBinary = readJSON(parser)
|
||||
expect(jsonFromBinary instanceof coreTypes.STObject).toBe(false)
|
||||
expect(_.isPlainObject(jsonFromBinary)).toBe(true)
|
||||
expect(jsonFromBinary instanceof Object).toBe(true);
|
||||
expect(jsonFromBinary.prototype).toBe(undefined)
|
||||
})
|
||||
}
|
||||
|
||||
function amountParsingTests () {
|
||||
_.filter(fixtures.values_tests, { type: 'Amount' }).forEach((f, i) => {
|
||||
fixtures.values_tests.filter(obj => obj.type === 'Amount').forEach((f, i) => {
|
||||
if (f.error) {
|
||||
return
|
||||
}
|
||||
@@ -246,7 +246,7 @@ function assertRecyclable (json, forField) {
|
||||
function nestedObjectTests () {
|
||||
function disabled (i) {
|
||||
unused(i)
|
||||
return false // !_.includes([2], i);
|
||||
return false
|
||||
}
|
||||
|
||||
fixtures.whole_objects.forEach((f, i) => {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
const { slice, compareBytes, parseBytes, bytesToHex } = require('../dist/utils/bytes-utils')
|
||||
|
||||
describe('bytes-utils', function () {
|
||||
describe('parseBytes', function () {
|
||||
test('can decode hex', function () {
|
||||
expect(parseBytes('0012')).toEqual([0x00, 0x12])
|
||||
expect(parseBytes('0012')).toEqual([0x00, 0x12])
|
||||
expect(parseBytes('00AA')).toEqual([0x00, 0xaa])
|
||||
})
|
||||
test('can decode hex to a Uint8Array', function () {
|
||||
const result = parseBytes('0012', Uint8Array)
|
||||
expect(result instanceof Uint8Array).toBe(true)
|
||||
expect(result).toEqual(Uint8Array.from([0x00, 0x12]))
|
||||
})
|
||||
test('can convert a list to a Uint8Array', function () {
|
||||
const result = parseBytes([0x00, 0x12], Uint8Array)
|
||||
expect(result instanceof Uint8Array).toBe(true)
|
||||
expect(result).toEqual(Uint8Array.from([0x00, 0x12]))
|
||||
})
|
||||
test('can decode hex to a Buffer', function () {
|
||||
const result = parseBytes('0012', Buffer)
|
||||
expect(result instanceof Buffer).toBe(true)
|
||||
expect(result.toJSON().data).toEqual([0x00, 0x12])
|
||||
})
|
||||
})
|
||||
|
||||
describe('bytesToHex', function () {
|
||||
test('can encode an array as hex', function () {
|
||||
expect(bytesToHex([0x00, 0xaa])).toBe('00AA')
|
||||
expect(bytesToHex([0xaa])).toBe('AA')
|
||||
})
|
||||
test('can encode Uint8Array as hex', function () {
|
||||
expect(bytesToHex(new Uint8Array([0x00, 0xaa]))).toBe('00AA')
|
||||
expect(bytesToHex(new Uint8Array([0xaa]))).toBe('AA')
|
||||
})
|
||||
})
|
||||
|
||||
describe('compareBytes', function () {
|
||||
test('compares the bytes sequence as big endian number', function () {
|
||||
expect(compareBytes([0, 1, 2], [1, 2, 3])).toBe(-1)
|
||||
})
|
||||
test('throws when the bytes sequences are of unlike length', function () {
|
||||
expect(() => compareBytes([0, 1], [1])).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('slice', function () {
|
||||
const val = [1, 2, 3, 4, 5]
|
||||
test('creates a slice of the same type as first arg', function () {
|
||||
expect(Array.isArray(slice(val))).toBe(true)
|
||||
})
|
||||
test('the 2nd arg is the start position [2:]', function () {
|
||||
expect(val.slice(2)).toEqual([3, 4, 5])
|
||||
expect(slice(val, 2)).toEqual([3, 4, 5])
|
||||
})
|
||||
test('the 3rd arg is the end position [2:4]', function () {
|
||||
expect(slice(val, 2, 4)).toEqual([3, 4])
|
||||
})
|
||||
test('can slice using negative numbers [-3:]', function () {
|
||||
expect(slice(val, -3)).toEqual([3, 4, 5])
|
||||
})
|
||||
test('can slice using negative numbers [-3:-1]', function () {
|
||||
expect(slice(val, -3, -1)).toEqual([3, 4])
|
||||
})
|
||||
test('the 4th arg is the output class type', function () {
|
||||
expect(slice(val, 2, 4, Buffer).toJSON().data).toEqual([3, 4])
|
||||
expect(slice(val, 2, 4, Uint8Array)).toEqual(Uint8Array.from([3, 4]))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
const { quality, binary } = require('../dist/coretypes')
|
||||
const { quality } = require('../dist/coretypes')
|
||||
|
||||
describe('Quality encode/decode', function () {
|
||||
const bookDirectory =
|
||||
@@ -10,6 +10,6 @@ describe('Quality encode/decode', function () {
|
||||
})
|
||||
test('can encode', function () {
|
||||
const bytes = quality.encode(expectedQuality)
|
||||
expect(binary.bytesToHex(bytes)).toBe(bookDirectory.slice(-16))
|
||||
expect(bytes.toString('hex').toUpperCase()).toBe(bookDirectory.slice(-16))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,7 +21,7 @@ function makeItem (indexArg) {
|
||||
index.toBytesSink(sink)
|
||||
},
|
||||
hashPrefix () {
|
||||
return [1, 3, 3, 7]
|
||||
return Buffer.from([1, 3, 3, 7])
|
||||
}
|
||||
}
|
||||
return [index, item]
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('Signing data', function () {
|
||||
})
|
||||
test('can create multi signing blobs', function () {
|
||||
const signingAccount = 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN'
|
||||
const signingJson = _.assign({}, tx_json, { SigningPubKey: '' })
|
||||
const signingJson = Object.assign({}, tx_json, { SigningPubKey: '' })
|
||||
const actual = encodeForMultisigning(signingJson, signingAccount)
|
||||
expect(actual).toBe(
|
||||
['534D5400', // signingPrefix
|
||||
|
||||
@@ -3,7 +3,7 @@ const { coreTypes } = require('../dist/types')
|
||||
const { SerializedType } = require('../dist/types/serialized-type')
|
||||
|
||||
describe('SerializedType interfaces', () => {
|
||||
_.forOwn(coreTypes, (Value, name) => {
|
||||
Object.entries(coreTypes).forEach(([name, Value]) => {
|
||||
test(`${name} has a \`from\` static constructor`, () => {
|
||||
expect(Value.from && Value.from !== Array.from).toBe(true)
|
||||
})
|
||||
@@ -27,7 +27,7 @@ describe('SerializedType interfaces', () => {
|
||||
expect(Value.from(newJSON).toJSON()).toEqual(newJSON)
|
||||
})
|
||||
describe(`${name} supports all methods of the SerializedType mixin`, () => {
|
||||
_.keys(SerializedType.prototype).forEach(k => {
|
||||
Object.keys(SerializedType.prototype).forEach(k => {
|
||||
test(`new ${name}.prototype.${k} !== undefined`, () => {
|
||||
expect(Value.prototype[k]).not.toBe(undefined)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const fs = require('fs')
|
||||
const { parseBytes } = require('../dist/utils/bytes-utils')
|
||||
|
||||
function hexOnly (hex) {
|
||||
return hex.replace(/[^a-fA-F0-9]/g, '')
|
||||
@@ -7,8 +6,8 @@ function hexOnly (hex) {
|
||||
|
||||
function unused () {}
|
||||
|
||||
function parseHexOnly (hex, to) {
|
||||
return parseBytes(hexOnly(hex), to)
|
||||
function parseHexOnly (hex) {
|
||||
return Buffer.from(hexOnly(hex), 'hex');
|
||||
}
|
||||
|
||||
function loadFixture (relativePath) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user