Skip to content

Commit

Permalink
feat(debugger): supported dep_group in TestContext (#357)
Browse files Browse the repository at this point in the history
* feat(debugger): supported dep_group in TestContext

* refactor(debugger): rename group to dep_group to avoid confused
  • Loading branch information
homura authored Jun 22, 2022
1 parent 6a90b01 commit 0c57197
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 53 deletions.
4 changes: 2 additions & 2 deletions packages/debugger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ helping you to debug your transaction without lunching a full node
import { createTestContext } from "@ckb-lumos/debugger";

const { executor, scriptConfigs } = createTestContext({
contract1: path.join("path/to/contracts", "contract1"),
contract2: path.join("path/to/contracts", "contract2"),
contract1: { path: path.join("path/to/contracts", "contract1") },
contract2: { path: path.join("path/to/contracts", "contract2") },
});

// ...
Expand Down
104 changes: 64 additions & 40 deletions packages/debugger/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Header, OutPoint } from "@ckb-lumos/base";
import * as crypto from "crypto";
import { ScriptConfig } from "@ckb-lumos/config-manager";
import { loadCode, LoadedCode } from "./loader";
import { OutputDataLoader } from "./loader";
import { DataLoader, TestContext } from "./types";
import { OutPointValue } from "@ckb-lumos/base/lib/values";
import { CKBDebuggerDownloader } from "./download";
import { CKBDebugger } from "./executor";
import * as fs from "fs";
Expand All @@ -17,8 +16,21 @@ export function mockOutPoint(): OutPoint {
};
}

// TODO implement dep_group
type LocaleConfig = { path: string } /*| { group: string[] }*/;
type DepCodePath = { dep_type?: "code"; path: string };
type DepGroupPath = { dep_type: "dep_group"; path: string; includes: string[] };
type LocaleConfig = DepCodePath | DepGroupPath;

function isDepCode(obj: LocaleConfig): obj is DepCodePath {
return (
obj.dep_type === "code" ||
(typeof obj.dep_type === "undefined" && typeof obj.path === "string")
);
}

function isDepGroup(obj: LocaleConfig): obj is DepGroupPath {
return obj.dep_type === "dep_group";
}

export type LocaleCode = { [key: string]: LocaleConfig };

export type CreateContextOptions = {
Expand Down Expand Up @@ -48,53 +60,65 @@ function createCKBDebugger(loader: DataLoader): CKBDebugger {
});
}

// TODO support load dep_group
export function createTestContext<Code extends LocaleCode>(config: {
deps: Code;
}): TestContext<Code> {
const { deps } = config;

const loadedCodes: Record<keyof Code, LoadedCode> = Object.entries(
deps
).reduce(
(scriptConfigs, [key, configItem]) =>
Object.assign(scriptConfigs, { [key]: loadCode(configItem.path) }),
{} as Record<keyof Code, LoadedCode>
);

const scriptConfigs: Record<keyof Code, ScriptConfig> = Object.entries(
loadedCodes
).reduce((scriptConfigs, [key, loaded]) => {
const { index, tx_hash } = mockOutPoint();
const scriptConfigs = {} as Record<keyof Code, ScriptConfig>;
const outputDataLoader = new OutputDataLoader();

Object.entries(deps).forEach(([key, depConfig]) => {
const entryCodeOutPoint = mockOutPoint();
const entryCode = outputDataLoader.setCode(
entryCodeOutPoint,
depConfig.path
);

outputDataLoader.setCode(entryCodeOutPoint, depConfig.path);

if (isDepCode(depConfig)) {
const entryScriptConfig: ScriptConfig = {
CODE_HASH: entryCode.codeHash,
DEP_TYPE: "code",
INDEX: entryCodeOutPoint.index,
HASH_TYPE: "data",
TX_HASH: entryCodeOutPoint.tx_hash,
};

const configItem: ScriptConfig = {
CODE_HASH: loaded.codeHash,
DEP_TYPE: "code",
TX_HASH: tx_hash,
INDEX: index,
HASH_TYPE: "data",
};
Object.assign(scriptConfigs, { [key]: entryScriptConfig });
}

return Object.assign(scriptConfigs, { [key]: configItem });
}, {} as Record<keyof Code, ScriptConfig>);
if (isDepGroup(depConfig)) {
const depGroupOutPoint = mockOutPoint();

const scriptConfigEntries = Object.entries(scriptConfigs);
const getCellData: DataLoader["getCellData"] = (outPoint) => {
const found = scriptConfigEntries.find(([, configItem]) => {
const configOutPoint = {
index: configItem.INDEX,
tx_hash: configItem.TX_HASH,
};
const includedOutPoints = Array.from({
length: depConfig.includes.length,
}).map(mockOutPoint);
outputDataLoader.setOutpointVec(depGroupOutPoint, [
entryCodeOutPoint,
...includedOutPoints,
]);

const actual = new OutPointValue(configOutPoint, {});
const expected = new OutPointValue(outPoint, {});
includedOutPoints.forEach((includedOutPoint, i) =>
outputDataLoader.setCode(includedOutPoint, depConfig.includes[i])
);

return actual.equals(expected);
});
const depGroupScriptConfig: ScriptConfig = {
CODE_HASH: entryCode.codeHash,
DEP_TYPE: "dep_group",
TX_HASH: depGroupOutPoint.tx_hash,
INDEX: depGroupOutPoint.index,
HASH_TYPE: "data",
};
Object.assign(scriptConfigs, { [key]: depGroupScriptConfig });
}
});

if (!found) throw new Error("OutPoint cannot be found");
const [key] = found;
return loadedCodes[key].binary;
const getCellData: DataLoader["getCellData"] = (outPoint) => {
const foundData = outputDataLoader.getOutputData(outPoint);
if (!foundData) throw new Error(`Unknown OutPoint(${outPoint})`);
return foundData;
};

const executor = createCKBDebugger({
Expand Down
28 changes: 27 additions & 1 deletion packages/debugger/src/loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { HexString } from "@ckb-lumos/base";
import { HexString, OutPoint } from "@ckb-lumos/base";
import * as fs from "fs";
import { ckbHash } from "@ckb-lumos/base/lib/utils";
import { hexify } from "@ckb-lumos/codec/lib/bytes";
import { OutPoint as OutPointCodec, OutPointVec } from "./codecs";

export type LoadedCode = { codeHash: HexString; binary: HexString };

Expand All @@ -12,3 +13,28 @@ export function loadCode(binaryPath: string): LoadedCode {
binary: hexify(buf),
};
}

export class OutputDataLoader {
private readonly cache: Map<HexString /* PackedOutPoint */, HexString>;

constructor() {
this.cache = new Map();
}

setCode(outPoint: OutPoint, path: string): LoadedCode {
const loadedCode = loadCode(path);
this.cache.set(hexify(OutPointCodec.pack(outPoint)), loadedCode.binary);
return loadedCode;
}

setOutpointVec(outPoint: OutPoint, outPoints: OutPoint[]): void {
this.cache.set(
hexify(OutPointCodec.pack(outPoint)),
hexify(OutPointVec.pack(outPoints))
);
}

getOutputData(outPoint: OutPoint): HexString | undefined {
return this.cache.get(hexify(OutPointCodec.pack(outPoint)));
}
}
71 changes: 61 additions & 10 deletions packages/debugger/tests/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,31 @@ import {
import * as path from "path";
import { computeScriptHash } from "@ckb-lumos/base/lib/utils";
import { HexString } from "@ckb-lumos/base";
import { CKBDebugger, CKBDebuggerDownloader } from "../src";
import { CKBDebugger, CKBDebuggerDownloader, DataLoader } from "../src";
import { TransactionSkeleton } from "@ckb-lumos/helpers";
import { createTestContext, mockOutPoint } from "../src/context";
import { DataLoader } from "../src/types";
import { randomBytes } from "crypto";
import { privateKeyToBlake160, signRecoverable } from "@ckb-lumos/hd/lib/key";
import { hexify } from "@ckb-lumos/codec/lib/bytes";
import { createP2PKHMessageGroup } from "@ckb-lumos/common-scripts";
import { WitnessArgs } from "@ckb-lumos/codec/lib/blockchain";

const downloader = new CKBDebuggerDownloader();
const context = createTestContext({
deps: {
ALWAYS_SUCCESS: { path: path.join(__dirname, "deps/always_success") },
ALWAYS_FAILURE: { path: path.join(__dirname, "deps/always_failure") },
ALWAYS_SUCCESS: {
dep_type: "code",
path: path.join(__dirname, "deps/always_success"),
},
ALWAYS_FAILURE: {
dep_type: "code",
path: path.join(__dirname, "deps/always_failure"),
},
SECP256K1_BLAKE160: {
dep_type: "dep_group",
path: path.join(__dirname, "deps/secp256k1_blake160"),
includes: [path.join(__dirname, "deps/secp256k1_data_info")],
},
},
});

Expand All @@ -35,7 +50,7 @@ test.before(async () => {
await downloader.downloadIfNotExists();
});

test("debugger#CKBDebugger without debugger path", (t) => {
test("context#CKBDebugger without debugger path", (t) => {
const origin = process.env.CKB_DEBUGGER_PATH;
delete process.env.CKB_DEBUGGER_PATH;

Expand All @@ -45,7 +60,7 @@ test("debugger#CKBDebugger without debugger path", (t) => {
});

// TODO uncomment the skip when ci is ready
test("debugger#CKBDebugger with always_success", async (t) => {
test("context#CKBDebugger with always_success", async (t) => {
let txSkeleton = TransactionSkeleton({});
const alwaysSuccessLock = registry.newScript("ALWAYS_SUCCESS", "0x");

Expand All @@ -72,7 +87,7 @@ test("debugger#CKBDebugger with always_success", async (t) => {
t.regex(result.message, /Total cycles consumed: 539/);
});

test("debugger#CKBDebugger with always_fail", async (t) => {
test("context#CKBDebugger with always_fail", async (t) => {
let txSkeleton = TransactionSkeleton({});
const alwaysFailureLock = registry.newScript("ALWAYS_FAILURE", "0x");

Expand All @@ -98,6 +113,42 @@ test("debugger#CKBDebugger with always_fail", async (t) => {
t.regex(result.message, /Run result: -1/);
});

test.todo("debugger#CKBDebugger with secp256k1 with correct signature");
test.todo("debugger#CKBDebugger with secp256k1 with wrong signature");
test.todo("debugger#CKBDebugger with transfer sUDT");
test("context#CKBDebugger with secp256k1 with correct signature", async (t) => {
let txSkeleton = TransactionSkeleton({});
const pk = hexify(randomBytes(32));
const blake160 = privateKeyToBlake160(pk);

const secp256k1Lock = registry.newScript("SECP256K1_BLAKE160", blake160);

txSkeleton = txSkeleton.update("inputs", (inputs) =>
inputs.push({
out_point: mockOutPoint(),
...createCellWithMinimalCapacity({ lock: secp256k1Lock }),
})
);
txSkeleton.update("outputs", (outputs) =>
outputs.push(createCellWithMinimalCapacity({ lock: secp256k1Lock }))
);
txSkeleton = txSkeleton.update("cellDeps", (cellDeps) =>
cellDeps.push(registry.newCellDep("SECP256K1_BLAKE160"))
);
txSkeleton = txSkeleton.update("witnesses", (witnesses) =>
witnesses.push(hexify(WitnessArgs.pack({ lock: "0x" + "00".repeat(65) })))
);
const signingGroup = createP2PKHMessageGroup(txSkeleton, [secp256k1Lock]);
const signedMessage = signRecoverable(signingGroup[0].message, pk);
txSkeleton = txSkeleton.update("witnesses", (witnesses) =>
witnesses.set(0, hexify(WitnessArgs.pack({ lock: signedMessage })))
);

const result = await context.executor.execute(txSkeleton, {
scriptGroupType: "lock",
scriptHash: computeScriptHash(secp256k1Lock),
});

t.is(result.code, 0);
t.true(result.cycles > 0);
});

test.todo("context#CKBDebugger with secp256k1 with wrong signature");
test.todo("context#CKBDebugger with transfer sUDT");
Binary file added packages/debugger/tests/deps/secp256k1_blake160
Binary file not shown.
Binary file not shown.

1 comment on commit 0c57197

@vercel
Copy link

@vercel vercel bot commented on 0c57197 Jun 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

lumos-website – ./

lumos-website.vercel.app
lumos-website-git-develop-cryptape.vercel.app
lumos-website-cryptape.vercel.app

Please sign in to comment.