Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ TESTNET_MANAGER_URL=http://0.0.0.0:8000
USE_LIT_BINARIES=true
LIT_NODE_BINARY_PATH=/path/to/lit_node/binary
LIT_ACTION_BINARY_PATH=/path/to/lit_action_binary

# ---------- For Health check ----------
LIT_STATUS_BACKEND_URL=CHANGE_THIS
LIT_STATUS_WRITE_KEY=CHANGE_THIS
48 changes: 48 additions & 0 deletions local-tests/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@ export const build = async () => {
inject: [`./${TEST_DIR}/shim.mjs`],
mainFields: ['module', 'main'],
});

await esbuild.build({
entryPoints: [`${TEST_DIR}/health/index.ts`],
outfile: `./${TEST_DIR}/build/health/index.mjs`,
bundle: true,
plugins: [
nodeExternalsPlugin({
allowList: [
'ethers',
'@lit-protocol/accs-schemas',
'@lit-protocol/contracts',
'crypto',
'secp256k1',
],
}),
],
platform: 'node',
target: 'esnext',
format: 'esm',
inject: [`./${TEST_DIR}/shim.mjs`],
mainFields: ['module', 'main'],
});
};

/**
Expand All @@ -56,10 +78,36 @@ try {
}
};

/**
* Adds crypto polyfill to health check build
*/
export const postBuildHealthPolyfill = () => {
try {
const file = fs.readFileSync(
`./${TEST_DIR}/build/health/index.mjs`,
'utf8'
);
const content = `// Additional crypto polyfill check
try {
if (!globalThis.crypto && typeof webcrypto !== 'undefined') {
globalThis.crypto = webcrypto;
}
} catch (error) {
console.error('❌ Error in crypto polyfill', error);
}
`;
const newFile = content + file;
fs.writeFileSync(`./${TEST_DIR}/build/health/index.mjs`, newFile);
} catch (e) {
throw new Error(`Error in postBuildHealthPolyfill: ${e}`);
}
};

// Go!
(async () => {
const start = Date.now();
await build();
postBuildPolyfill();
postBuildHealthPolyfill();
console.log(`[build.mjs] 🚀 Build time: ${Date.now() - start}ms`);
})();
162 changes: 162 additions & 0 deletions local-tests/health/DatilHealthManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { getEoaSessionSigs } from 'local-tests/setup/session-sigs/get-eoa-session-sigs';
import { getPkpSessionSigs } from 'local-tests/setup/session-sigs/get-pkp-session-sigs';
import { TinnyEnvironment } from 'local-tests/setup/tinny-environment';
import { TinnyPerson } from 'local-tests/setup/tinny-person';
import { LIT_ABILITY } from '@lit-protocol/constants';
import { ILitNodeClient } from '@lit-protocol/types';
import { AccessControlConditions } from 'local-tests/setup/accs/accs';
import { LitAccessControlConditionResource } from '@lit-protocol/auth-helpers';
import { encryptString, decryptToString } from '@lit-protocol/encryption';

export class DatilHealthManager {
env: TinnyEnvironment;
alice: TinnyPerson;
eoaSessionSigs: any;

constructor() {
this.env = new TinnyEnvironment();
}

async init() {
await this.env.init();
}

// ========== Person Creation ==========
// create a person
// this action contains chain & rpc interactions
// best to cache it, but for the time being, we will create a new person for each test, since we are only running this test
// once in every 30 minutes.
async initPerson() {
this.alice = await this.env.createNewPerson('Alice');
this.eoaSessionSigs = await getEoaSessionSigs(this.env, this.alice);
}

validatePrerequisites() {
if (!this.alice) {
throw new Error('❌ Person not initialized');
}
if (!this.eoaSessionSigs) {
throw new Error('❌ EOA Session Sigs not initialized');
}
}

// ========== Endpoint Tests ==========
handshakeTest = async () => {
try {
await this.env.setupLitNodeClient();
} catch (e) {
console.error('❌ Failed to setup Lit Node Client');
throw e;
}
};

pkpSignTest = async () => {
this.validatePrerequisites();
try {
await this.env.litNodeClient.pkpSign({
toSign: this.alice.loveLetter,
pubKey: this.alice.pkp.publicKey,
sessionSigs: this.eoaSessionSigs,
});
} catch (e) {
console.error('❌ Failed to run pkpSign');
throw e;
}
};

signSessionKeyTest = async () => {
this.validatePrerequisites();
try {
await getPkpSessionSigs(this.env, this.alice);
} catch (e) {
console.error('❌ Failed to run signSessionKey');
throw e;
}
};

executeJsTest = async () => {
this.validatePrerequisites();
try {
await this.env.litNodeClient.executeJs({
sessionSigs: this.eoaSessionSigs,
code: `(async () => {
const sigShare = await LitActions.signEcdsa({
toSign: dataToSign,
publicKey,
sigName: "sig",
});
})();`,
jsParams: {
dataToSign: this.alice.loveLetter,
publicKey: this.alice.pkp.publicKey,
},
});
} catch (e) {
console.error('❌ Failed to run executeJs');
throw e;
}
};

decryptTest = async () => {
this.validatePrerequisites();
try {
// Set access control conditions for encrypting and decrypting
const accs = AccessControlConditions.getEmvBasicAccessControlConditions({
userAddress: this.alice.wallet.address,
});

// First encrypt some test data
const encryptRes = await encryptString(
{
accessControlConditions: accs,
dataToEncrypt: 'Hello world',
},
this.env.litNodeClient as unknown as ILitNodeClient
);

if (!encryptRes.ciphertext) {
throw new Error(`Expected "ciphertext" in encryptRes`);
}

if (!encryptRes.dataToEncryptHash) {
throw new Error(`Expected "dataToEncryptHash" in encryptRes`);
}

// Generate resource string for the encrypted data
const accsResourceString =
await LitAccessControlConditionResource.generateResourceString(
accs,
encryptRes.dataToEncryptHash
);

// Get session sigs with decryption capability
const eoaSessionSigs = await getEoaSessionSigs(this.env, this.alice, [
{
resource: new LitAccessControlConditionResource(accsResourceString),
ability: LIT_ABILITY.AccessControlConditionDecryption,
},
]);

// Decrypt the encrypted string
const decryptRes = await decryptToString(
{
accessControlConditions: accs,
ciphertext: encryptRes.ciphertext,
dataToEncryptHash: encryptRes.dataToEncryptHash,
sessionSigs: eoaSessionSigs,
chain: 'ethereum',
},
this.env.litNodeClient as unknown as ILitNodeClient
);

if (decryptRes !== 'Hello world') {
throw new Error(
`Expected decryptRes to be 'Hello world' but got ${decryptRes}`
);
}
} catch (e) {
console.error('❌ Failed to run decrypt');
throw e;
}
};
}
74 changes: 74 additions & 0 deletions local-tests/health/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { createLitStatusClient } from '@lit-protocol/lit-status-sdk';
import { DatilHealthManager } from './DatilHealthManager';

// Configuration
const NETWORK = process.env.NETWORK!;
const PRODUCT = 'js-sdk/datil';

async function runHealthCheck() {
if (!NETWORK) {
throw new Error('❌ NETWORK is not set');
}

const statusClient = createLitStatusClient({
url: process.env.LIT_STATUS_BACKEND_URL,
apiKey: process.env.LIT_STATUS_WRITE_KEY,
});

const txs = await statusClient.getOrRegisterFunctions({
network: NETWORK,
product: PRODUCT,
functions: [
'handshake',
'pkpSign',
'signSessionKey',
'executeJs',
'decrypt',
] as const,
});

const healthManager = new DatilHealthManager();
await healthManager.init();

// (test) /web/handshake
console.log('🔄 Running handshake test');
await statusClient.executeAndLog(
txs.handshake.id,
healthManager.handshakeTest
);

// after handshake, we can create a person to test
await healthManager.initPerson();

// (test) /web/pkp/sign
console.log('🔄 Running pkpSign test');
await statusClient.executeAndLog(txs.pkpSign.id, healthManager.pkpSignTest);

// (test) /web/sign_session_key
console.log('🔄 Running signSessionKey test');
await statusClient.executeAndLog(
txs.signSessionKey.id,
healthManager.signSessionKeyTest
);

// (test) /web/execute
console.log('🔄 Running executeJs test');
await statusClient.executeAndLog(
txs.executeJs.id,
healthManager.executeJsTest
);

// (test) /web/encryption/sign
console.log('🔄 Running decryptTest test');
await statusClient.executeAndLog(txs.decrypt.id, healthManager.decryptTest);
}

(async () => {
try {
await runHealthCheck();
} catch (error) {
console.error(error);
} finally {
process.exit();
}
})();
16 changes: 13 additions & 3 deletions local-tests/setup/tinny-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,10 @@ export class TinnyEnvironment {
async setupBareEthAuthSig() {
const privateKey = await this.getAvailablePrivateKey();
try {
const provider = new ethers.providers.JsonRpcBatchProvider(this.rpc);
const provider = new ethers.providers.StaticJsonRpcProvider({
url: this.rpc,
skipFetchSetup: true,
});
const wallet = new ethers.Wallet(privateKey.privateKey, provider);

const toSign = await createSiweMessage({
Expand Down Expand Up @@ -450,7 +453,10 @@ export class TinnyEnvironment {
const privateKey = await this.getAvailablePrivateKey();

try {
const provider = new ethers.providers.JsonRpcBatchProvider(this.rpc);
const provider = new ethers.providers.StaticJsonRpcProvider({
url: this.rpc,
skipFetchSetup: true,
});
const wallet = new ethers.Wallet(privateKey.privateKey, provider);

const tx = await wallet.sendTransaction({
Expand All @@ -473,7 +479,10 @@ export class TinnyEnvironment {
*/
setupSuperCapacityDelegationAuthSig = async () => {
const privateKey = await this.getAvailablePrivateKey();
const provider = new ethers.providers.JsonRpcBatchProvider(this.rpc);
const provider = new ethers.providers.StaticJsonRpcProvider({
url: this.rpc,
skipFetchSetup: true,
});
const wallet = new ethers.Wallet(privateKey.privateKey, provider);

/**
Expand All @@ -498,6 +507,7 @@ export class TinnyEnvironment {
this.contractsClient = new LitContracts({
signer: wallet,
debug: this.processEnvs.DEBUG,
rpc: this.rpc,
network: this.network,
});
}
Expand Down
7 changes: 7 additions & 0 deletions local-tests/shim.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import { createRequire } from 'module';
import { webcrypto } from 'node:crypto';

const require = createRequire(import.meta.url);
global.require = require;

// Add crypto polyfill for Node.js
if (!globalThis.crypto) {
globalThis.crypto = webcrypto;
}
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"test:unit": "nx run-many --target=test",
"test:unit:watch": "nx run-many --target=test --watch",
"test:unit:bun": "bun ./tools/scripts/unit-test-with-bun.mjs",
"test:health": "DEBUG=false node ./local-tests/build.mjs && dotenvx run --env-file=.env -- node ./local-tests/build/health/index.mjs",
"publish:packages": "yarn node ./tools/scripts/pub.mjs --prod",
"publish:beta": "yarn node ./tools/scripts/pub.mjs --tag beta",
"publish:staging": "yarn node ./tools/scripts/pub.mjs --tag staging",
Expand All @@ -42,6 +43,7 @@
"@dotenvx/dotenvx": "^1.6.4",
"@lit-protocol/accs-schemas": "^0.0.31",
"@lit-protocol/contracts": "^0.0.74",
"@lit-protocol/lit-status-sdk": "^0.1.8",
"@metamask/eth-sig-util": "5.0.2",
"@mysten/sui.js": "^0.37.1",
"@openagenda/verror": "^3.1.4",
Expand Down Expand Up @@ -97,9 +99,6 @@
"babel-jest": "27.5.1",
"buffer": "^6.0.3",
"chalk": "^5.3.0",
"cypress": "11.0.1",
"cypress-metamask": "^1.0.5-development",
"cypress-metamask-v2": "^1.7.2",
"esbuild": "^0.17.3",
"esbuild-node-builtins": "^0.1.0",
"esbuild-node-externals": "^1.14.0",
Expand Down
Loading
Loading