import path from 'path' ; import { Injectable } from '@nestjs/common' ; import { isRuntimeDataSourceV0_2_0 , levelFilter } from '@subql/common' ; import { Store , SubqlDatasource } from '@subql/types' ; import { NodeVM , NodeVMOptions , VMScript } from '@subql/x-vm2' ; import { merge } from 'lodash' ; import { NodeConfig } from '../configure/NodeConfig' ; import { SubqueryProject } from '../configure/project.model' ; import { getLogger } from '../utils/logger' ; import { getProjectEntry } from '../utils/project' ; import { timeout } from '../utils/promise' ; import { getYargsOption } from '../yargs' ; import { ApiService } from './api.service' ; import { StoreService } from './store.service' ; import { ApiAt } from './types' ; const { argv } = getYargsOption ( ) ; export interface SandboxOption { store ? : Store ; root : string ; entry : string ; } const DEFAULT_OPTION = { console : 'redirect' , wasm : argv . unsafe , sandbox : { } , require : { builtin : argv . unsafe ? [ '*' ] : [ 'assert' , 'buffer' , 'crypto' , 'util' , 'path' ] , external : true , context : 'sandbox' , } , wrapper : 'commonjs' , sourceExtensions : [ 'js' , 'cjs' ] , } ; const logger = getLogger ( 'sandbox' ) ; export class Sandbox extends NodeVM { constructor ( option , protected readonly script ) { super ( merge ( DEFAULT_OPTION , { require : { root : option . root , resolve : ( moduleName ) => { return require . resolve ( moduleName , { paths : [ option . root ] } ) ; } , } , } ) ) ; } async runTimeout < T = unknown > ( duration ) { return timeout ( this . run ( this . script ) , duration ) ; } } export class IndexerSandbox extends Sandbox { constructor ( option , private readonly config ) { super ( option , new VMScript ( `
      const mappingFunctions = require('${ option . entry }');
      module.exports = mappingFunctions[funcName](...args);
    ` , path . join ( option . root , 'sandbox' ) ) ) ; this . injectGlobals ( option ) ; } async securedExec ( funcName , args ) { this . setGlobal ( 'args' , args ) ; this . setGlobal ( 'funcName' , funcName ) ; try { await this . runTimeout ( this . config . timeout ) ; } catch ( e ) { e . handler = funcName ; if ( this . config . logLevel && levelFilter ( 'debug' , this . config . logLevel ) ) { e . handlerArgs = JSON . stringify ( args ) ; } throw e ; } finally { this . setGlobal ( 'args' , [ ] ) ; this . setGlobal ( 'funcName' , '' ) ; } } private injectGlobals ( { store } ) { if ( store ) { this . freeze ( store , 'store' ) ; } this . freeze ( logger , 'logger' ) ; } } @ Injectable ( ) export class SandboxService { private processorCache : Record < string , IndexerSandbox > = { } ; constructor ( private readonly apiService , private readonly storeService , private readonly nodeConfig , private readonly project ) { } getDsProcessor ( ds , api ) { const entry = this . getDataSourceEntry ( ds ) ; let processor = this . processorCache [ entry ] ; if ( ! processor ) { processor = new IndexerSandbox ( { entry , root : this . project . path , store : this . storeService . getStore ( ) , } , this . nodeConfig ) ; this . processorCache [ entry ] = processor ; } processor . freeze ( api , 'api' ) ; if ( argv . unsafe ) { processor . freeze ( this . apiService . getApi ( ) , 'unsafeApi' ) ; } return processor ; } private getDataSourceEntry ( ds ) { if ( isRuntimeDataSourceV0_2_0 ( ds ) ) { return ds . mapping . file ; } else { return getProjectEntry ( this . project . path ) ; } } }