UNPKG

@chainlink/functions-toolkit

Version:

An NPM package with collection of functions that can be used for working with Chainlink Functions.

137 lines (136 loc) 5.79 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.simulateScript = void 0; const child_process_1 = require("child_process"); const fs_1 = __importDefault(require("fs")); const os_1 = __importDefault(require("os")); const path_1 = __importDefault(require("path")); const simulationConfig_1 = require("../simulationConfig"); const simulateScript = async ({ source, secrets, args, bytesArgs, maxOnChainResponseBytes = simulationConfig_1.DEFAULT_MAX_ON_CHAIN_RESPONSE_BYTES, maxExecutionTimeMs = simulationConfig_1.DEFAULT_MAX_EXECUTION_DURATION_MS, maxMemoryUsageMb = simulationConfig_1.DEFAULT_MAX_MEMORY_USAGE_MB, numAllowedQueries = simulationConfig_1.DEFAULT_MAX_HTTP_REQUESTS, maxQueryDurationMs = simulationConfig_1.DEFAULT_MAX_HTTP_REQUEST_DURATION_MS, maxQueryUrlLength = simulationConfig_1.DEFAULT_MAX_HTTP_REQUEST_URL_LENGTH, maxQueryRequestBytes = simulationConfig_1.DEFAULT_MAX_HTTP_REQUEST_BYTES, maxQueryResponseBytes = simulationConfig_1.DEFAULT_MAX_HTTP_RESPONSE_BYTES, }) => { if (typeof source !== 'string') { throw Error('source param is missing or invalid'); } if (secrets && (typeof secrets !== 'object' || !Object.values(secrets).every(s => { return typeof s === 'string'; }))) { throw Error('secrets param not a string map'); } if (args) { if (!Array.isArray(args)) { throw Error('args param not an array'); } for (const arg of args) { if (typeof arg !== 'string') { throw Error('args param not a string array'); } } } if (bytesArgs) { if (!Array.isArray(bytesArgs)) { throw Error('bytesArgs param not an array'); } for (const arg of bytesArgs) { if (typeof arg !== 'string' || !/^0x[0-9A-Fa-f]+$/.test(arg)) { throw Error('bytesArgs param contains invalid hex string'); } } } // Check if deno is installed try { (0, child_process_1.execSync)('deno --version', { stdio: 'pipe' }); } catch { throw Error('Deno must be installed and accessible via the PATH environment variable (ie: the `deno --version` command must work).\nVisit https://deno.land/#installation for installation instructions.'); } const scriptPath = createScriptTempFile(source); const simulation = (0, child_process_1.spawn)('deno', [ 'run', '--no-prompt', `--v8-flags=--max-old-space-size=${maxMemoryUsageMb}`, '--allow-net', scriptPath, Buffer.from(JSON.stringify(secrets ?? {})).toString('base64'), Buffer.from(JSON.stringify(args ?? {})).toString('base64'), Buffer.from(JSON.stringify(bytesArgs ?? {})).toString('base64'), numAllowedQueries.toString(), maxQueryDurationMs.toString(), maxQueryUrlLength.toString(), maxQueryRequestBytes.toString(), maxQueryResponseBytes.toString(), ]); const timeout = setTimeout(() => { simulation.kill('SIGKILL'); }, maxExecutionTimeMs); const simulationComplete = new Promise(resolve => { simulation.on('exit', (code, signal) => { clearTimeout(timeout); resolve({ code, signal }); }); }); let output = ''; simulation.stdout.on('data', (data) => { output += data.toString(); }); simulation.stderr.on('data', (data) => { output += data.toString(); }); const { code, signal } = await simulationComplete; try { fs_1.default.rmSync(scriptPath); } catch { // The temp file may have already been deleted } let capturedTerminalOutput; let parsedOutput = {}; try { const outputArr = output.toString().split('\n'); const finalLine = outputArr[outputArr.length - 2]; parsedOutput = JSON.parse(finalLine); capturedTerminalOutput = outputArr.slice(0, -2).join('\n'); } catch { capturedTerminalOutput = output; } const simulationResult = { capturedTerminalOutput, }; if (parsedOutput.success) { simulationResult.responseBytesHexstring = parsedOutput.success; if ((simulationResult.responseBytesHexstring.length - 2) / 2 > maxOnChainResponseBytes) { simulationResult.errorString = `response >${maxOnChainResponseBytes} bytes`; delete simulationResult.responseBytesHexstring; } return simulationResult; } if (parsedOutput.error) { simulationResult.errorString = parsedOutput.error.message; if (parsedOutput.error?.name === 'PermissionDenied') { simulationResult.errorString = 'attempted access to blocked resource detected'; } return simulationResult; } if (signal === 'SIGKILL') { simulationResult.errorString = 'script runtime exceeded'; return simulationResult; } if (code !== 0) { simulationResult.errorString = 'syntax error, RAM exceeded, or other error'; return simulationResult; } return simulationResult; }; exports.simulateScript = simulateScript; const createScriptTempFile = (source) => { const tmpDir = os_1.default.tmpdir(); const sandboxText = fs_1.default.readFileSync(path_1.default.join(__dirname, '/deno-sandbox/sandbox.ts'), 'utf8'); const script = sandboxText.replace('//INJECT_USER_CODE_HERE', source); const tmpFilePath = path_1.default.join(tmpDir, `FunctionsScript-${Date.now()}.ts`); fs_1.default.writeFileSync(tmpFilePath, script); return tmpFilePath; };