Development Artifact Cleanup: ✅ BROTHER_NODE REORGANIZATION: Moved development test node to appropriate location - dev/test-nodes/brother_node/: Moved from root directory for better organization - Contains development configuration, test logs, and test chain data - No impact on production systems - purely development/testing artifact ✅ DEVELOPMENT ARTIFACTS IDENTIFIED: - Chain ID: aitbc-brother-chain (test/development chain) - Ports: 8010 (P2P) and 8011 (RPC) - different from production - Environment: .env file with test configuration - Logs: rpc.log and node.log from development testing session (March 15, 2026) ✅ ROOT DIRECTORY CLEANUP: Removed development clutter from production directory - brother_node/ moved to dev/test-nodes/brother_node/ - Root directory now contains only production-ready components - Development artifacts properly organized in dev/ subdirectory DIRECTORY STRUCTURE IMPROVEMENT: 📁 dev/test-nodes/: Development and testing node configurations 🏗️ Root Directory: Clean production structure with only essential components 🧪 Development Isolation: Test environments separated from production BENEFITS: ✅ Clean Production Directory: No development artifacts in root ✅ Better Organization: Development nodes grouped in dev/ subdirectory ✅ Clear Separation: Production vs development environments clearly distinguished ✅ Maintainability: Easier to identify and manage development components RESULT: Successfully moved brother_node development artifact to dev/test-nodes/ subdirectory, cleaning up the root directory while preserving development testing environment for future use.
310 lines
10 KiB
TypeScript
Executable File
310 lines
10 KiB
TypeScript
Executable File
import { base64, hex } from '@scure/base';
|
|
import * as P from './index.ts';
|
|
const Path = P._TEST.Path; // Internal, debug-only
|
|
|
|
const UNKNOWN = '(???)';
|
|
const codes = { esc: 27, nl: 10 };
|
|
const esc = String.fromCharCode(codes.esc);
|
|
const nl = String.fromCharCode(codes.nl);
|
|
|
|
const bold = esc + '[1m';
|
|
const gray = esc + '[90m';
|
|
const reset = esc + '[0m';
|
|
const red = esc + '[31m';
|
|
const green = esc + '[32m';
|
|
const yellow = esc + '[33m';
|
|
|
|
type DebugPath = { start: number; end?: number; path: string; value?: any };
|
|
class DebugReader extends P._TEST._Reader {
|
|
debugLst: DebugPath[] = [];
|
|
cur?: DebugPath;
|
|
get lastElm() {
|
|
if (this.debugLst.length) return this.debugLst[this.debugLst.length - 1];
|
|
return { start: 0, end: 0, path: '' };
|
|
}
|
|
pushObj(obj: P.StructOut, objFn: P._PathObjFn) {
|
|
return Path.pushObj(this.stack, obj, (cb) => {
|
|
objFn((field: string, fieldFn: Function) => {
|
|
cb(field, () => {
|
|
{
|
|
const last = this.lastElm;
|
|
if (last.end === undefined) last.end = this.pos;
|
|
else if (last.end !== this.pos) {
|
|
this.debugLst.push({
|
|
path: `${Path.path(this.stack)}/${UNKNOWN}`,
|
|
start: last.end,
|
|
end: this.pos,
|
|
});
|
|
}
|
|
this.cur = { path: `${Path.path(this.stack)}/${field}`, start: this.pos };
|
|
}
|
|
fieldFn();
|
|
{
|
|
// happens if pop after pop (exit from nested structure)
|
|
if (!this.cur) {
|
|
const last = this.lastElm;
|
|
if (last.end === undefined) last.end = this.pos;
|
|
else if (last.end !== this.pos) {
|
|
this.debugLst.push({
|
|
start: last.end,
|
|
end: this.pos,
|
|
path: last.path + `/${UNKNOWN}`,
|
|
});
|
|
}
|
|
} else {
|
|
this.cur.end = this.pos;
|
|
const last = this.stack[this.stack.length - 1];
|
|
const lastItem = last.obj;
|
|
const lastField = last.field;
|
|
if (lastItem && lastField !== undefined) this.cur.value = lastItem[lastField];
|
|
this.debugLst.push(this.cur);
|
|
this.cur = undefined;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
finishDebug(): void {
|
|
const end = this.data.length;
|
|
if (this.cur) this.debugLst.push({ end, ...this.cur });
|
|
const last = this.lastElm;
|
|
if (!last || last.end !== end) this.debugLst.push({ start: this.pos, end, path: UNKNOWN });
|
|
}
|
|
}
|
|
|
|
function toBytes(data: string | P.Bytes): P.Bytes {
|
|
if (P.utils.isBytes(data)) return data;
|
|
if (typeof data !== 'string') throw new Error('PD: data should be string or Uint8Array');
|
|
try {
|
|
return base64.decode(data);
|
|
} catch (e) {}
|
|
try {
|
|
return hex.decode(data);
|
|
} catch (e) {}
|
|
throw new Error(`PD: data has unknown string format: ${data}`);
|
|
}
|
|
|
|
type DebugData = { path: string; data: P.Bytes; value?: any };
|
|
function mapData(lst: DebugPath[], data: P.Bytes): DebugData[] {
|
|
let end = 0;
|
|
const res: DebugData[] = [];
|
|
for (const elm of lst) {
|
|
if (elm.start !== end) throw new Error(`PD: elm start=${elm.start} after prev elm end=${end}`);
|
|
if (elm.end === undefined) throw new Error(`PD: elm.end is undefined=${elm}`);
|
|
res.push({ path: elm.path, data: data.slice(elm.start, elm.end), value: elm.value });
|
|
end = elm.end;
|
|
}
|
|
if (end !== data.length) throw new Error('PD: not all data mapped');
|
|
return res;
|
|
}
|
|
|
|
function chrWidth(s: string) {
|
|
/*
|
|
It is almost impossible to find out real characters width in terminal since it depends on terminal itself, current unicode version and moon's phase.
|
|
So, we just stripping ANSI, tabs and unicode supplimental characters. Emoji support requires big tables (and have no guarantee to work), so we ignore it for now.
|
|
Also, no support for full width unicode characters for now.
|
|
*/
|
|
return s
|
|
.replace(
|
|
/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d\/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g,
|
|
''
|
|
)
|
|
.replace('\t', ' ')
|
|
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, ' ').length;
|
|
}
|
|
|
|
function wrap(s: string, padding: number = 0) {
|
|
// @ts-ignore
|
|
const limit = process.stdout.columns - 3 - padding;
|
|
if (chrWidth(s) <= limit) return s;
|
|
while (chrWidth(s) > limit) s = s.slice(0, -1);
|
|
return `${s}${reset}...`;
|
|
}
|
|
|
|
export function table(data: any[]): void {
|
|
let res: string[] = [];
|
|
const str = (v: any) => (v === undefined ? '' : '' + v);
|
|
const pad = (s: string, width: number) =>
|
|
`${s}${''.padEnd(Math.max(0, width - chrWidth(s)), ' ')}`;
|
|
let widths: Record<string, number> = {};
|
|
for (let elm of data) {
|
|
for (let k in elm) {
|
|
widths[k] = Math.max(
|
|
widths[k] || 0,
|
|
chrWidth(str(k)),
|
|
str(elm[k])
|
|
.split(nl)
|
|
.reduce((a, b) => Math.max(a, chrWidth(b)), 0)
|
|
);
|
|
}
|
|
}
|
|
const columns = Object.keys(widths);
|
|
if (!data.length || !columns.length) throw new Error('No data');
|
|
const padding = ` ${reset}${gray}│${reset} `;
|
|
res.push(wrap(` ${columns.map((c) => `${bold}${pad(c, widths[c])}`).join(padding)}${reset}`, 3));
|
|
for (let idx = 0; idx < data.length; idx++) {
|
|
const elm = data[idx];
|
|
const row = columns.map((i) => str(elm[i]).split(nl));
|
|
let message = [...Array(Math.max(...row.map((i) => i.length))).keys()]
|
|
.map((line) => row.map((c, i) => pad(str(c[line]), widths[columns[i]])))
|
|
.map((line, _) => wrap(` ${line.join(padding)} `, 1))
|
|
.join(nl);
|
|
res.push(message);
|
|
}
|
|
for (let i = 0; i < res.length; i++) {
|
|
const border = columns
|
|
.map((c) => ''.padEnd(widths[c], '─'))
|
|
.join(`─${i === res.length - 1 ? '┴' : '┼'}─`);
|
|
res[i] += wrap(`${nl}${reset}${gray}─${border}─${reset}`);
|
|
}
|
|
// @ts-ignore
|
|
console.log(res.join(nl));
|
|
}
|
|
|
|
function fmtData(data: P.Bytes, perLine = 8) {
|
|
const res = [];
|
|
for (let i = 0; i < data.length; i += perLine) {
|
|
res.push(hex.encode(data.slice(i, i + perLine)));
|
|
}
|
|
return res.map((i) => `${bold}${i}${reset}`).join(nl);
|
|
}
|
|
|
|
function fmtValue(value: any) {
|
|
if (P.utils.isBytes(value)) return `b(${green}${hex.encode(value)}${reset} len=${value.length})`;
|
|
if (typeof value === 'string') return `s(${green}"${value}"${reset} len=${value.length})`;
|
|
if (typeof value === 'number' || typeof value === 'bigint') return `n(${value})`;
|
|
// console.log('fmt', value);
|
|
// if (Object.prototype.toString.call(value) === '[object Object]') return inspect(value);
|
|
return '' + value;
|
|
}
|
|
|
|
export function decode(
|
|
coder: P.CoderType<any>,
|
|
data: string | P.Bytes,
|
|
forcePrint = false
|
|
): ReturnType<(typeof coder)['decode']> {
|
|
data = toBytes(data);
|
|
const r = new DebugReader(data);
|
|
let res, e;
|
|
try {
|
|
res = coder.decodeStream(r);
|
|
r.finish();
|
|
} catch (_e) {
|
|
e = _e;
|
|
}
|
|
r.finishDebug();
|
|
if (e || forcePrint) {
|
|
// @ts-ignore
|
|
console.log('==== DECODED BEFORE ERROR ====');
|
|
table(
|
|
mapData(r.debugLst, data).map((elm) => ({
|
|
Data: fmtData(elm.data),
|
|
Len: elm.data.length,
|
|
Path: `${green}${elm.path}${reset}`,
|
|
Value: fmtValue(elm.value),
|
|
}))
|
|
);
|
|
// @ts-ignore
|
|
console.log('==== /DECODED BEFORE ERROR ====');
|
|
}
|
|
if (e) throw e;
|
|
return res;
|
|
}
|
|
|
|
function getMap(coder: P.CoderType<any>, data: string | P.Bytes) {
|
|
data = toBytes(data);
|
|
const r = new DebugReader(data);
|
|
coder.decodeStream(r);
|
|
r.finish();
|
|
r.finishDebug();
|
|
return mapData(r.debugLst, data);
|
|
}
|
|
|
|
function diffData(a: P.Bytes, e: P.Bytes) {
|
|
const len = Math.max(a.length, e.length);
|
|
let outA = '',
|
|
outE = '';
|
|
const charHex = (n: number) => n.toString(16).padStart(2, '0');
|
|
for (let i = 0; i < len; i++) {
|
|
const [aI, eI] = [a[i], e[i]];
|
|
if (i && !(i % 8)) {
|
|
if (aI !== undefined) outA += nl;
|
|
if (eI !== undefined) outE += nl;
|
|
}
|
|
if (aI !== undefined) outA += aI === eI ? charHex(aI) : `${yellow}${charHex(aI)}${reset}`;
|
|
if (eI !== undefined) outE += aI === eI ? charHex(eI) : `${yellow}${charHex(eI)}${reset}`;
|
|
}
|
|
return [outA, outE];
|
|
}
|
|
|
|
function diffPath(a: string, e: string) {
|
|
if (a === e) return a;
|
|
return `A: ${red}${a}${reset}${nl}E: ${green}${e}${reset}`;
|
|
}
|
|
function diffLength(a: P.Bytes, e: P.Bytes) {
|
|
const [aLen, eLen] = [a.length, e.length];
|
|
if (aLen === eLen) return aLen;
|
|
return `A: ${red}${aLen}${reset}${nl}E: ${green}${eLen}${reset}`;
|
|
}
|
|
|
|
function diffValue(a: any, e: any) {
|
|
const [aV, eV] = [a, e].map(fmtValue);
|
|
if (aV === eV) return aV;
|
|
return `A: ${red}${aV}${reset}${nl}E: ${green}${eV}${reset}`;
|
|
}
|
|
|
|
export function diff(
|
|
coder: P.CoderType<any>,
|
|
actual: string | P.Bytes,
|
|
expected: string | P.Bytes,
|
|
skipSame = true
|
|
): void {
|
|
// @ts-ignore
|
|
console.log('==== DIFF ====');
|
|
const [_actual, _expected] = [actual, expected].map((i) => getMap(coder, i)) as [
|
|
DebugData[],
|
|
DebugData[],
|
|
];
|
|
const len = Math.max(_actual.length, _expected.length);
|
|
const data = [];
|
|
const DEF = { data: P.EMPTY, path: '' };
|
|
for (let i = 0; i < len; i++) {
|
|
const [a, e] = [_actual[i] || DEF, _expected[i] || DEF];
|
|
if (P.utils.equalBytes(a.data, e.data) && skipSame) continue;
|
|
const [adata, edata] = diffData(a.data, e.data);
|
|
data.push({
|
|
'Data (A)': adata,
|
|
'Data (E)': edata,
|
|
Len: diffLength(a.data, e.data),
|
|
Path: diffPath(a.path, e.path),
|
|
Value: diffValue(a.value, e.value),
|
|
});
|
|
}
|
|
table(data);
|
|
// @ts-ignore
|
|
console.log('==== /DIFF ====');
|
|
}
|
|
|
|
/**
|
|
* Wraps a CoderType with debug logging for encoding and decoding operations.
|
|
* @param inner - Inner CoderType to wrap.
|
|
* @returns Inner wrapped in debug prints via console.log.
|
|
* @example
|
|
* const debugInt = P.debug(P.U32LE); // Will print info to console on encoding/decoding
|
|
*/
|
|
export function debug<T>(inner: P.CoderType<T>): P.CoderType<T> {
|
|
if (!P.utils.isCoder(inner)) throw new Error(`debug: invalid inner value ${inner}`);
|
|
const log = (name: string, rw: P.Reader | P.Writer, value: any) => {
|
|
// @ts-ignore
|
|
console.log(`DEBUG/${name}(${Path.path(rw.stack)}):`, { type: typeof value, value });
|
|
return value;
|
|
};
|
|
return P.wrap({
|
|
size: inner.size,
|
|
encodeStream: (w: P.Writer, value: T) => inner.encodeStream(w, log('encode', w, value)),
|
|
decodeStream: (r: P.Reader): T => log('decode', r, inner.decodeStream(r)),
|
|
});
|
|
}
|