*Use custom json flatten algorithm to ensure deterministic hashing

This commit is contained in:
Derrick Hammer 2022-07-31 23:00:17 -04:00
parent 29271df627
commit 5cefe245af
3 changed files with 57 additions and 6 deletions

View File

@ -15,7 +15,6 @@
},
"dependencies": {
"@hyperswarm/dht": "^6.0.1",
"json-stable-stringify": "^1.0.1",
"libskynet": "^0.0.61",
"msgpackr": "^1.6.1"
}

View File

@ -4,8 +4,7 @@ import { pack, unpack } from "msgpackr";
import { RPCRequest, RPCResponse } from "./types";
import { Buffer } from "buffer";
import { blake2b } from "libskynet";
import jsonStringify from "json-stable-stringify";
import { flatten } from "./util.js";
export default class RpcQuery {
private _network: RpcNetwork;
@ -100,22 +99,24 @@ export default class RpcQuery {
private checkResponses() {
const responseStore = this._responses;
const responseStoreData = Object.values(responseStore);
type ResponseGroup = { [response: string]: number };
const responseObjects = responseStoreData.reduce((output: any, item) => {
const itemFlattened = flatten(item?.data).sort();
const hash = Buffer.from(
blake2b(Buffer.from(jsonStringify(item?.data)))
blake2b(Buffer.from(JSON.stringify(itemFlattened)))
).toString("hex");
output[hash] = item?.data;
return output;
}, {});
const responses: ResponseGroup = responseStoreData.reduce(
(output: ResponseGroup, item) => {
const itemFlattened = flatten(item?.data).sort();
const hash = Buffer.from(
blake2b(Buffer.from(jsonStringify(item?.data)))
blake2b(Buffer.from(JSON.stringify(itemFlattened)))
).toString("hex");
output[hash] = output[hash] ?? 0;
output[hash]++;

51
src/util.ts Normal file
View File

@ -0,0 +1,51 @@
function isBuffer(obj: any): boolean {
return (
obj &&
obj.constructor &&
typeof obj.constructor.isBuffer === "function" &&
obj.constructor.isBuffer(obj)
);
}
/*
Forked from https://github.com/hughsk/flat
*/
export function flatten(target: any, opts: any = {}): any[] {
opts = opts || {};
const delimiter = opts.delimiter || ".";
const maxDepth = opts.maxDepth;
const transformKey =
opts.transformKey || ((key: any) => (isNaN(parseInt(key)) ? key : ""));
const output: any[] = [];
function step(object: any, prev?: any, currentDepth?: any) {
currentDepth = currentDepth || 1;
Object.keys(object).forEach(function (key) {
const value = object[key];
const isarray = opts.safe && Array.isArray(value);
const type = Object.prototype.toString.call(value);
const isbuffer = isBuffer(value);
const isobject = type === "[object Object]" || type === "[object Array]";
const newKey = prev
? prev + delimiter + transformKey(key)
: transformKey(key);
if (
!isarray &&
!isbuffer &&
isobject &&
Object.keys(value).length &&
(!opts.maxDepth || currentDepth < maxDepth)
) {
return step(value, newKey, currentDepth + 1);
}
output.push(`${newKey}=${value}`);
});
}
step(target);
return output;
}