├── .nvmrc ├── .eslintignore ├── src ├── @types │ ├── stream-concat.d.ts │ ├── rdf-validate-shacl.d.ts │ ├── lzma-purejs-requirejs.d.ts │ ├── DDO │ │ ├── Credentials.ts │ │ ├── IDdoStateQuery.ts │ │ ├── IMetadataQuery.ts │ │ ├── ConsumerParameter.ts │ │ └── SearchQuery.ts │ ├── express.ts │ ├── index.ts │ ├── Asset.ts │ ├── Purgatory.ts │ ├── policyServer.ts │ ├── humanhash.d.ts │ ├── Escrow.ts │ ├── IndexedMetadata.ts │ ├── blockchain.ts │ ├── fileObject.ts │ ├── Fees.ts │ └── C2D │ │ └── C2D_OPFK8.ts ├── components │ ├── P2P │ │ ├── hyperdiff.d.ts │ │ └── handlers.ts │ ├── core │ │ ├── base58-js.d.ts │ │ ├── compute │ │ │ ├── index.ts │ │ │ └── environments.ts │ │ ├── utils │ │ │ └── validateDdoHandler.ts │ │ ├── handler │ │ │ ├── nonceHandler.ts │ │ │ ├── getJobs.ts │ │ │ ├── statusHandler.ts │ │ │ └── queryHandler.ts │ │ └── admin │ │ │ ├── stopNodeHandler.ts │ │ │ ├── fetchConfigHandler.ts │ │ │ ├── adminHandler.ts │ │ │ ├── reindexChainHandler.ts │ │ │ ├── reindexTxHandler.ts │ │ │ └── IndexingThreadHandler.ts │ ├── Provider │ │ └── index.ts │ ├── database │ │ ├── TypesenseDdoStateQuery.ts │ │ ├── ElasticSearchDdoStateQuery.ts │ │ ├── typesenseConfig.ts │ │ ├── AuthTokenDatabase.ts │ │ ├── ElasticSearchMetadataQuery.ts │ │ ├── SQLLiteConfigDatabase.ts │ │ └── TypesenseMetadataQuery.ts │ ├── Indexer │ │ ├── processors │ │ │ └── index.ts │ │ └── version.ts │ ├── httpRoutes │ │ ├── rootEndpoint.ts │ │ ├── queue.ts │ │ ├── jobs.ts │ │ ├── dids.ts │ │ ├── requestValidator.ts │ │ ├── adminConfig.ts │ │ ├── auth.ts │ │ ├── policyServer.ts │ │ └── index.ts │ └── c2d │ │ └── index.ts ├── test │ ├── data │ │ ├── organizations-100.aes │ │ ├── nonceSchema.ts │ │ ├── commands.ts │ │ └── ddoSchema.ts │ ├── .env.test2 │ ├── .env.test │ ├── utils │ │ └── addresses.ts │ ├── unit │ │ ├── crypt.test.ts │ │ ├── indexer │ │ │ ├── version.test.ts │ │ │ └── indexer.test.ts │ │ ├── auth │ │ │ └── token.test.ts │ │ └── ocean.test.ts │ └── integration │ │ ├── elasticsearch.test.ts │ │ ├── nonce.test.ts │ │ └── purgatory.test.ts ├── utils │ ├── config │ │ ├── index.ts │ │ └── transforms.ts │ ├── index.ts │ ├── config.ts │ ├── ip.ts │ ├── url.ts │ ├── cronjobs │ │ └── p2pAnnounceDDOS.ts │ ├── database.ts │ ├── logging │ │ └── common.ts │ ├── conversions.ts │ ├── file.ts │ └── address.ts └── helpers │ └── scripts │ └── generatePK.js ├── .dockerignore ├── .github └── CODEOWNERS ├── controlpanel ├── .DS_Store ├── src │ ├── components │ │ ├── ErrorCheck │ │ │ ├── index.module.css │ │ │ └── index.tsx │ │ ├── Spinner │ │ │ ├── index.tsx │ │ │ └── style.module.css │ │ ├── ControlPanel │ │ │ ├── Menu.tsx │ │ │ ├── Menu.module.css │ │ │ ├── AdminAccounts.tsx │ │ │ ├── NodePlatform.tsx │ │ │ ├── SupportedNetworks.tsx │ │ │ ├── SupportedStorage.tsx │ │ │ └── Indexer.tsx │ │ ├── Copy │ │ │ ├── index.module.css │ │ │ └── index.tsx │ │ ├── Footer │ │ │ ├── style.module.css │ │ │ └── index.tsx │ │ ├── Navigation │ │ │ ├── index.tsx │ │ │ └── style.module.css │ │ ├── JobStatusPanel │ │ │ └── index.tsx │ │ ├── Table │ │ │ ├── index.module.css │ │ │ ├── _styles.ts │ │ │ └── index.tsx │ │ ├── NodeDetails │ │ │ ├── index.module.css │ │ │ └── index.tsx │ │ ├── shared │ │ │ └── NetworkSelector.tsx │ │ ├── NodePeers │ │ │ ├── style.module.css │ │ │ └── index.tsx │ │ └── Admin │ │ │ ├── StopNode.tsx │ │ │ ├── index.tsx │ │ │ └── index.module.css │ ├── .DS_Store │ ├── assets │ │ ├── chevron.svg │ │ ├── download.svg │ │ ├── copy.svg │ │ ├── no-error.svg │ │ ├── search-icon.svg │ │ └── error.svg │ ├── pages │ │ ├── _document.tsx │ │ ├── api │ │ │ └── hello.ts │ │ ├── index.tsx │ │ └── _app.tsx │ └── shared │ │ ├── types │ │ ├── JobTypes.ts │ │ ├── RowDataType.ts │ │ └── dataTypes.ts │ │ └── utils │ │ └── jobs.ts ├── additional.d.ts ├── next-env.d.ts ├── next.config.js ├── tsconfig.json ├── package.json └── README.md ├── .vscode ├── extensions.json └── settings.json ├── .prettierrc ├── dist └── controlpanel │ └── _next │ └── static │ ├── chunks │ ├── 5883.e4477e9126daa625.js │ ├── 6878.5657c32e06476a2e.js │ ├── 3688.d161b107f65da93d.js │ ├── pages │ │ └── _error-e4216aab802f5810.js │ ├── 5710.5bdbdbf21f1c3db3.js │ ├── 1711.ae2b84d9f5645069.js │ ├── 4583.205bbdd6677d7c00.js │ ├── 1950.c8039f3dc9bb92f5.js │ ├── 704.484bcd9e0a7f5626.js │ ├── 5488.ea86c6ce443ba3bd.js │ ├── 135.67fab15ebc7d852e.js │ ├── 6237.f7b1d24c812922e4.js │ ├── 5939.0a433dc6f963fc41.js │ ├── 8881.8c985300b37d631a.js │ ├── 7682.b0a3567fac8e0052.js │ ├── 1727.af62bd633f21ee69.js │ ├── 4253.6be69df622e36e45.js │ ├── 3525.53072abba3ca74b8.js │ ├── 9941.44044767831d9eb0.js │ ├── 5119.33e08a0525159056.js │ ├── 1424.c15d7e6321ca35d6.js │ └── 1748.f63b451fd93f590b.js │ ├── dbdm9KL_uDQpXQXjY0ctm │ ├── _ssgManifest.js │ └── _buildManifest.js │ └── media │ ├── download.0a4876ec.svg │ └── copy.63713a04.svg ├── docs ├── imgs │ └── OceanNode-arhitecture.drawio.png ├── database.md ├── PolicyServer.md └── networking.md ├── schemas ├── op_ddo_v7.json ├── op_ddo_v1.json ├── op_ddo_v3.json ├── op_ddo_v5.json └── op_ddo_short.json ├── .mocharc.json ├── typesense-compose.yml ├── tsoa.json ├── elasticsearch-compose.yml ├── tsconfig.json ├── .eslintrc ├── scripts └── logs.sh ├── Dockerfile └── .env.example /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.19.0 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.js 2 | dist/ 3 | .eslintrc 4 | 5 | -------------------------------------------------------------------------------- /src/@types/stream-concat.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'stream-concat' 2 | -------------------------------------------------------------------------------- /src/components/P2P/hyperdiff.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'hyperdiff' 2 | -------------------------------------------------------------------------------- /src/components/core/base58-js.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'base58-js' 2 | -------------------------------------------------------------------------------- /src/@types/rdf-validate-shacl.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'rdf-validate-shacl' 2 | -------------------------------------------------------------------------------- /src/components/P2P/handlers.ts: -------------------------------------------------------------------------------- 1 | export * from './handleProtocolCommands.js' 2 | -------------------------------------------------------------------------------- /src/@types/lzma-purejs-requirejs.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'lzma-purejs-requirejs' 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | logs 4 | c2d_storage 5 | .env.local 6 | .env -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @alexcos20 @bogdanfazakas @giurgiur99 @denisiuriet @ndrpp @andreip136 2 | -------------------------------------------------------------------------------- /src/@types/DDO/Credentials.ts: -------------------------------------------------------------------------------- 1 | export const KNOWN_CREDENTIALS_TYPES = ['address', 'accessList'] 2 | -------------------------------------------------------------------------------- /src/@types/express.ts: -------------------------------------------------------------------------------- 1 | export interface RouteOptions { 2 | path: string 3 | method: string 4 | } 5 | -------------------------------------------------------------------------------- /controlpanel/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanprotocol/ocean-node/HEAD/controlpanel/.DS_Store -------------------------------------------------------------------------------- /controlpanel/src/components/ErrorCheck/index.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | width: 30px; 3 | height: 30px; 4 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /controlpanel/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanprotocol/ocean-node/HEAD/controlpanel/src/.DS_Store -------------------------------------------------------------------------------- /src/@types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './OceanNode' 2 | export * from './C2D/C2D' 3 | export * from './Typesense' 4 | -------------------------------------------------------------------------------- /src/@types/Asset.ts: -------------------------------------------------------------------------------- 1 | export interface OrdableAssetResponse { 2 | isOrdable: boolean 3 | reason?: string 4 | } 5 | -------------------------------------------------------------------------------- /src/test/data/organizations-100.aes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanprotocol/ocean-node/HEAD/src/test/data/organizations-100.aes -------------------------------------------------------------------------------- /src/test/.env.test2: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY='0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58' 2 | CONFIG_PATH="$HOME/config.json" 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "printWidth": 90, 5 | "trailingComma": "none", 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/5883.e4477e9126daa625.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[5883],{35883:function(){}}]); -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/dbdm9KL_uDQpXQXjY0ctm/_ssgManifest.js: -------------------------------------------------------------------------------- 1 | self.__SSG_MANIFEST=new Set,self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB(); -------------------------------------------------------------------------------- /docs/imgs/OceanNode-arhitecture.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanprotocol/ocean-node/HEAD/docs/imgs/OceanNode-arhitecture.drawio.png -------------------------------------------------------------------------------- /src/@types/DDO/IDdoStateQuery.ts: -------------------------------------------------------------------------------- 1 | export interface IDdoStateQuery { 2 | buildQuery(did?: string, nft?: string, txId?: string): Record 3 | } 4 | -------------------------------------------------------------------------------- /controlpanel/additional.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | interface Window { 3 | ethereum?: import('ethers').providers.ExternalProvider 4 | } 5 | -------------------------------------------------------------------------------- /schemas/op_ddo_v7.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "op_ddo_v4.7.0", 3 | "enable_nested_fields": true, 4 | "fields": [{ "name": ".*", "type": "auto", "optional": true }] 5 | } 6 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "full-trace": true, 3 | "bail": true, 4 | "exit": true, 5 | "timeout": 20000, 6 | "recursive": true, 7 | "require": ["./dist/test/utils/hooks.js"] 8 | } 9 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/6878.5657c32e06476a2e.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[6878],{46601:function(){},29120:function(){},46586:function(){}}]); -------------------------------------------------------------------------------- /schemas/op_ddo_v1.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "op_ddo_v4.1.0", 3 | "enable_nested_fields": true, 4 | "fields": [ 5 | { "name": ".*", "type": "auto", "optional": true } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /schemas/op_ddo_v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "op_ddo_v4.3.0", 3 | "enable_nested_fields": true, 4 | "fields": [ 5 | { "name": ".*", "type": "auto", "optional": true } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /schemas/op_ddo_v5.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "op_ddo_v4.5.0", 3 | "enable_nested_fields": true, 4 | "fields": [ 5 | { "name": ".*", "type": "auto", "optional": true } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/@types/DDO/IMetadataQuery.ts: -------------------------------------------------------------------------------- 1 | import { SearchQuery } from './SearchQuery' 2 | 3 | export interface IMetadataQuery { 4 | buildQuery(query: SearchQuery): Record 5 | } 6 | -------------------------------------------------------------------------------- /src/@types/Purgatory.ts: -------------------------------------------------------------------------------- 1 | export interface PurgatoryAssets { 2 | did: string 3 | reason: string 4 | } 5 | 6 | export interface PurgatoryAccounts { 7 | address: string 8 | reason: string 9 | } 10 | -------------------------------------------------------------------------------- /controlpanel/src/components/Spinner/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import styles from './style.module.css' 4 | 5 | export default function Spinner() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/3688.d161b107f65da93d.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3688],{93688:function(u,e,n){n.r(e),n.d(e,{default:function(){return t.I}});var t=n(93763)}}]); -------------------------------------------------------------------------------- /schemas/op_ddo_short.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "op_ddo_short", 3 | "enable_nested_fields": true, 4 | "fields": [ 5 | { 6 | "name": ".*", 7 | "type": "auto", 8 | "optional": true 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/@types/policyServer.ts: -------------------------------------------------------------------------------- 1 | export interface PolicyServerResult { 2 | success: boolean // true - allowed, false not allowed 3 | message?: string // error message, if any 4 | httpStatus?: number // status returned by server 5 | } 6 | -------------------------------------------------------------------------------- /controlpanel/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /src/utils/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './schemas.js' 2 | export * from './transforms.js' 3 | export * from './constants.js' 4 | export * from './builder.js' 5 | 6 | export { getConfiguration, getConfigFilePath, printCurrentConfig } from './builder.js' 7 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './conversions.js' 2 | export * from './config.js' 3 | export * from './blockchain.js' 4 | export * from './constants.js' 5 | export * from './asset.js' 6 | export * from './attestation.js' 7 | export { isDefined } from './util.js' 8 | -------------------------------------------------------------------------------- /typesense-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | typesense: 3 | image: typesense/typesense:0.25.1 4 | restart: on-failure 5 | ports: 6 | - "8108:8108" 7 | volumes: 8 | - ./typesense-data:/data 9 | command: '--data-dir /data --api-key=xyz' 10 | -------------------------------------------------------------------------------- /controlpanel/src/assets/chevron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Provider/index.ts: -------------------------------------------------------------------------------- 1 | import { Database } from '../database' 2 | 3 | export class OceanProvider { 4 | private db: Database 5 | constructor(db: Database) { 6 | this.db = db 7 | } 8 | 9 | public getDatabase(): Database { 10 | return this.db 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/core/compute/index.ts: -------------------------------------------------------------------------------- 1 | export * from './environments.js' 2 | export * from './startCompute.js' 3 | export * from './stopCompute.js' 4 | export * from './getStatus.js' 5 | export * from './getResults.js' 6 | export * from './initialize.js' 7 | export * from './getStreamableLogs.js' 8 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/pages/_error-e4216aab802f5810.js: -------------------------------------------------------------------------------- 1 | (self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4820],{81981:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return u(66908)}])}},function(n){n.O(0,[9774,2888,179],function(){return n(n.s=81981)}),_N_E=n.O()}]); -------------------------------------------------------------------------------- /controlpanel/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /controlpanel/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | staticPageGenerationTimeout: 30000, 5 | reactStrictMode: true, 6 | images: { 7 | unoptimized: true 8 | }, 9 | output: 'export', 10 | distDir: '../dist/controlpanel' 11 | } 12 | 13 | module.exports = nextConfig 14 | -------------------------------------------------------------------------------- /tsoa.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryFile": "src/index.ts", 3 | "noImplicitAdditionalProperties": "throw-on-extras", 4 | "controllerPathGlobs": ["src/components/httpRoutes/**/*.ts"], 5 | "spec": { 6 | "outputDirectory": "build", 7 | "specVersion": 3 8 | }, 9 | "routes": { 10 | "routesDir": "build" 11 | } 12 | } -------------------------------------------------------------------------------- /src/test/data/nonceSchema.ts: -------------------------------------------------------------------------------- 1 | import { TypesenseCollectionCreateSchema } from '../../@types' 2 | 3 | export const nonceSchema: TypesenseCollectionCreateSchema = { 4 | name: 'nonce', 5 | enable_nested_fields: true, 6 | fields: [ 7 | { name: 'id', type: 'string' }, 8 | { name: 'nonce', type: 'int64', sort: true } // store nonce as string 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /controlpanel/src/components/ControlPanel/Menu.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AdminActions from '../Admin' 3 | import styles from './Menu.module.css' 4 | 5 | export default function Menu() { 6 | return ( 7 |
8 |
STATUS ADMIN
9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /controlpanel/src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler(req: NextApiRequest, res: NextApiResponse) { 9 | res.status(200).json({ name: 'John Doe' }) 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | import { isDefined } from './util.js' 2 | import { getConfiguration } from './config/builder.js' 3 | 4 | export * from './config/index.js' 5 | 6 | export function isPolicyServerConfigured(): boolean { 7 | return isDefined(process.env.POLICY_SERVER_URL) 8 | } 9 | 10 | export const hasP2PInterface = (await (await getConfiguration())?.hasP2P) || false 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": "explicit" 4 | }, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.formatOnSave": true, 7 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], 8 | "search.exclude": { 9 | "**/.next": true, 10 | "**/out": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/@types/humanhash.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'humanhash' { 2 | class HumanHasher { 3 | constructor(wordlist?: string[]) 4 | humanize(hexdigest: string, words?: number, separator?: string): string 5 | uuid( 6 | words?: number, 7 | separator?: string, 8 | version?: number 9 | ): { humanhash: string; uuid: string } 10 | } 11 | 12 | export = HumanHasher 13 | } 14 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/dbdm9KL_uDQpXQXjY0ctm/_buildManifest.js: -------------------------------------------------------------------------------- 1 | self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/5679-bec633b225238aa1.js","static/css/8b9f4a8789776f9a.css","static/chunks/pages/index-68352c78cef0caeb.js"],"/_error":["static/chunks/pages/_error-e4216aab802f5810.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB(); -------------------------------------------------------------------------------- /src/@types/Escrow.ts: -------------------------------------------------------------------------------- 1 | export interface EscrowAuthorization { 2 | address: string 3 | maxLockedAmount: BigInt 4 | currentLockedAmount: BigInt 5 | maxLockSeconds: BigInt 6 | maxLockCounts: BigInt 7 | currentLocks: BigInt 8 | } 9 | 10 | export interface EscrowLock { 11 | jobId: BigInt 12 | payer: string 13 | payee: string 14 | amount: BigInt 15 | expiry: BigInt 16 | token: string 17 | } 18 | -------------------------------------------------------------------------------- /src/@types/IndexedMetadata.ts: -------------------------------------------------------------------------------- 1 | export type PriceType = 'fixedrate' | 'dispenser' 2 | 3 | export interface ServicePrice { 4 | type: PriceType 5 | price: string 6 | contract: string 7 | token?: string 8 | exchangeId?: string 9 | } 10 | 11 | export interface ServiceStats { 12 | datatokenAddress: string 13 | name: string 14 | symbol: string 15 | serviceId: string 16 | orders?: number 17 | prices?: ServicePrice[] 18 | } 19 | -------------------------------------------------------------------------------- /controlpanel/src/components/Copy/index.module.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | background-color: transparent; 3 | } 4 | 5 | .feedback { 6 | color: black; 7 | font-size: 10px; 8 | } 9 | 10 | .action { 11 | display: flex; 12 | flex-direction: row; 13 | align-items: center; 14 | gap: 10px; 15 | } 16 | 17 | .action:hover { 18 | cursor: pointer; 19 | } 20 | 21 | .button { 22 | background-color: transparent; 23 | border: none; 24 | } -------------------------------------------------------------------------------- /controlpanel/src/assets/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/media/download.0a4876ec.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /elasticsearch-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | elasticsearch: 3 | image: elasticsearch:8.5.1 4 | ports: 5 | - 9200:9200 6 | - 9300:9300 7 | volumes: 8 | - esdata:/usr/share/elasticsearch/data 9 | environment: 10 | ES_JAVA_OPTS: "-Xms512m -Xmx512m" 11 | MAX_MAP_COUNT: "64000" 12 | discovery.type: "single-node" 13 | ELASTIC_PASSWORD: "changeme" 14 | xpack.security.enabled: "false" 15 | xpack.security.http.ssl.enabled: "false" 16 | volumes: 17 | esdata: 18 | -------------------------------------------------------------------------------- /controlpanel/src/shared/types/JobTypes.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export enum CommandStatus { 3 | DELIVERED = 'DELIVERED', // command was delivered successfully 4 | PENDING = 'PENDING', // command is pending excution or still running 5 | FAILURE = 'FAILURE', // command execution failed 6 | SUCCESS = 'SUCCESS' // command execution succeeded 7 | } 8 | export type JobStatus = { 9 | command: string 10 | timestamp: string 11 | jobId: string 12 | status: CommandStatus 13 | hash: string 14 | } 15 | -------------------------------------------------------------------------------- /controlpanel/src/shared/types/RowDataType.ts: -------------------------------------------------------------------------------- 1 | export type NodeDetailsType = { 2 | node: string 3 | host: string 4 | port: string 5 | last_seen: string 6 | enode: string 7 | client_type: string 8 | client_version: string 9 | os: string 10 | country: string 11 | city: string 12 | } 13 | 14 | export type DataRowType = { 15 | nodeId: string 16 | network: string 17 | chainId: string 18 | components: string 19 | blockNumber: string 20 | errors: string 21 | downloadLogs: string 22 | nodeDetails: NodeDetailsType[] 23 | } 24 | -------------------------------------------------------------------------------- /controlpanel/src/components/Footer/style.module.css: -------------------------------------------------------------------------------- 1 | .footerContainer { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | max-width: 1244px; 6 | margin: 0 auto; 7 | } 8 | 9 | .footerLinks { 10 | display: flex; 11 | justify-content: space-between; 12 | align-items: center; 13 | gap: 60px; 14 | } 15 | 16 | @media screen and (max-width: 700px) { 17 | .footerContainer { 18 | flex-direction: column; 19 | } 20 | 21 | .footerLinks { 22 | flex-direction: column; 23 | margin-top: 12px; 24 | gap: 12px; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /controlpanel/src/components/ErrorCheck/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import styles from './index.module.css' 4 | 5 | import ErrorSVG from '../../assets/error.svg' 6 | import NoErrorSVG from '../../assets/no-error.svg' 7 | import Image from 'next/image' 8 | 9 | export default function ErrorCheck({ status }: { status: string }) { 10 | return ( 11 |
12 | {status === 'None' ? ( 13 | no error 14 | ) : ( 15 | error 16 | )} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /controlpanel/src/components/ControlPanel/Menu.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | border-radius: 12px; 3 | background: #FFF; 4 | max-width: 260px; 5 | display: flex; 6 | flex-direction: column; 7 | padding: 40px 28px; 8 | min-width: 260px; 9 | } 10 | 11 | .title { 12 | color: #3D4551; 13 | font-family: Helvetica; 14 | font-size: 20px; 15 | font-style: normal; 16 | font-weight: 700; 17 | line-height: 140%; 18 | margin-bottom: 47px; 19 | } 20 | 21 | @media screen and (max-width: 700px) { 22 | .root { 23 | max-width: none; 24 | width: 90vw; 25 | margin: 0 auto; 26 | padding: 20px; 27 | } 28 | } -------------------------------------------------------------------------------- /controlpanel/src/components/Navigation/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import logo from '../../assets/logo-nodes.svg' 3 | import styles from './style.module.css' 4 | import { ConnectButton } from '@rainbow-me/rainbowkit' 5 | 6 | const NavBar = () => { 7 | return ( 8 |
9 |
10 | Ocean Node Logo 11 |
12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | 19 | export default NavBar 20 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/5710.5bdbdbf21f1c3db3.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[5710],{35710:function(I,j,c){c.r(j),c.d(j,{default:function(){return M}});var M="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgZmlsbD0ibm9uZSI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBmaWxsPSIjMDA3OEQ0IiBkPSJNMCAwaDIyLjc1NXYyMi43NDVIMFYwWm0yNS4yNDUgMEg0OHYyMi43NDVIMjUuMjQ1VjBaTTAgMjUuMjQ1aDIyLjc1NVY0OEgwVjI1LjI0NVptMjUuMjQ1IDBINDhWNDhIMjUuMjQ1Ii8+PC9nPjxkZWZzPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTAgMGg0OHY0OEgweiIvPjwvY2xpcFBhdGg+PC9kZWZzPjwvc3ZnPg=="}}]); -------------------------------------------------------------------------------- /src/utils/ip.ts: -------------------------------------------------------------------------------- 1 | export async function getIPv4() { 2 | return await getIPFromAPI('https://api.ipify.org?format=json') 3 | } 4 | 5 | export async function getIPv6() { 6 | return await getIPFromAPI('https://api6.ipify.org?format=json') 7 | } 8 | 9 | export async function getIPFromAPI(url: string): Promise { 10 | try { 11 | const res = await fetch(url, { 12 | headers: { 13 | Accept: 'application/json', 14 | 'Content-Type': 'application/json' 15 | }, 16 | method: 'GET' 17 | }) 18 | const data = await res.json() 19 | return data.ip 20 | } catch (e) { 21 | return null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /controlpanel/src/components/ControlPanel/AdminAccounts.tsx: -------------------------------------------------------------------------------- 1 | import styles from './index.module.css' 2 | import { useAdminContext } from '@/context/AdminProvider' 3 | 4 | export default function AdminAccounts() { 5 | const { allAdmins } = useAdminContext() 6 | 7 | return ( 8 |
9 |
Admin Accounts
10 |
11 | {allAdmins.map((admin, i) => { 12 | return ( 13 |
14 | {admin} 15 |
16 | ) 17 | })} 18 |
19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/components/database/TypesenseDdoStateQuery.ts: -------------------------------------------------------------------------------- 1 | import { IDdoStateQuery } from '../../@types/DDO/IDdoStateQuery.js' 2 | 3 | export class TypesenseDdoStateQuery implements IDdoStateQuery { 4 | buildQuery(did?: string, nft?: string, txId?: string): Record { 5 | let query: Record = {} 6 | 7 | if (did) { 8 | query = { 9 | q: did, 10 | query_by: 'did' 11 | } 12 | } 13 | 14 | if (nft) { 15 | query = { 16 | q: nft, 17 | query_by: 'nft' 18 | } 19 | } 20 | 21 | if (txId) { 22 | query = { 23 | q: txId, 24 | query_by: 'txId' 25 | } 26 | } 27 | 28 | return query 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/url.ts: -------------------------------------------------------------------------------- 1 | // Url utility functions 2 | export const URLUtils = { 3 | // basic url check using URL constructor 4 | isValidUrl(urlString: string, hyperTextProtocolOnly: boolean = true): boolean { 5 | let url 6 | try { 7 | url = new URL(urlString) 8 | } catch (e) { 9 | return false 10 | } 11 | // by default only care about http:// and https:// 12 | return hyperTextProtocolOnly 13 | ? url.protocol === 'http:' || url.protocol === 'https:' 14 | : true 15 | }, 16 | 17 | // adds the forward slash if missing 18 | sanitizeURLPath(url: string): string { 19 | if (!url.endsWith('/')) { 20 | url = url + '/' 21 | } 22 | return url 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /controlpanel/src/assets/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /controlpanel/src/components/JobStatusPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import { getStatusColors } from '@/shared/utils/jobs' 2 | import Alert from '@mui/material/Alert' 3 | 4 | export default function JobStatusPanel(props: any) { 5 | const color: string = props.job ? getStatusColors(props.job.status) : 'black' 6 | return ( 7 |
8 | {props.job !== null && ( 9 | {}} 14 | > 15 | Job with id {props.job.jobId} has status{' '} 16 | {props.job.status} 17 | 18 | )} 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/1711.ae2b84d9f5645069.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1711],{41711:function(N,I,g){g.r(I),g.d(I,{default:function(){return j}});var j="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBmaWxsPSIjMDA1MkZGIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGQ9Ik0xNCAyOGExNCAxNCAwIDEgMCAwLTI4IDE0IDE0IDAgMCAwIDAgMjhaIi8+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTEzLjk2NyAyMy44NmM1LjQ0NSAwIDkuODYtNC40MTUgOS44Ni05Ljg2IDAtNS40NDUtNC40MTUtOS44Ni05Ljg2LTkuODYtNS4xNjYgMC05LjQwMyAzLjk3NC05LjgyNSA5LjAzaDE0LjYzdjEuNjQySDQuMTQyYy40MTMgNS4wNjUgNC42NTQgOS4wNDcgOS44MjYgOS4wNDdaIi8+PC9nPjwvc3ZnPg=="}}]); -------------------------------------------------------------------------------- /src/test/.env.test: -------------------------------------------------------------------------------- 1 | HTTP_API_PORT=8001 2 | P2P_ipV4BindTcpPort=8000 3 | PRIVATE_KEY=0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58 4 | RPCS='{ "8996": {"rpc": "http://127.0.0.1:8545", "chainId": 8996, "network": "development", "chunkSize": 100}}' 5 | INDEXER_NETWORKS='[8996]' 6 | DB_URL=http://localhost:9200 7 | IPFS_GATEWAY=https://ipfs.io/ 8 | ARWEAVE_GATEWAY=https://arweave.net/ 9 | NODE1_PRIVATE_KEY=0xcb345bd2b11264d523ddaf383094e2675c420a17511c3102a53817f13474a7ff 10 | NODE2_PRIVATE_KEY=0x3634cc4a3d2694a1186a7ce545f149e022eea103cc254d18d08675104bb4b5ac 11 | INDEXER_INTERVAL=9000 12 | #ADDRESS_FILE=${HOME}/.ocean/ocean-contracts/artifacts/address.json 13 | LOG_LEVEL=debug 14 | DB_TYPE=elasticsearch 15 | -------------------------------------------------------------------------------- /controlpanel/src/components/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './style.module.css' 2 | const Footer = () => { 3 | const currentYear = new Date().getFullYear() 4 | return ( 5 |
6 |

@ {currentYear}, Ocean Nodes

7 | 18 |
19 | ) 20 | } 21 | 22 | export default Footer 23 | -------------------------------------------------------------------------------- /src/@types/blockchain.ts: -------------------------------------------------------------------------------- 1 | export interface SupportedNetwork { 2 | chainId: number 3 | rpc: string 4 | network?: string 5 | chunkSize?: number 6 | startBlock?: number 7 | fallbackRPCs?: string[] 8 | } 9 | 10 | export interface RPCS { 11 | [chainId: string]: SupportedNetwork 12 | } 13 | 14 | export interface NetworkEvent { 15 | type: string 16 | text: string 17 | } 18 | 19 | export interface Hashes { 20 | [hash: string]: NetworkEvent 21 | } 22 | 23 | export interface BlocksEvents { 24 | [event: string]: any 25 | } 26 | export interface ProcessingEvents { 27 | lastBlock: number 28 | foundEvents: BlocksEvents 29 | } 30 | 31 | export interface ConnectionStatus { 32 | ready: boolean 33 | error?: string 34 | } 35 | -------------------------------------------------------------------------------- /src/components/database/ElasticSearchDdoStateQuery.ts: -------------------------------------------------------------------------------- 1 | import { IDdoStateQuery } from '../../@types/DDO/IDdoStateQuery.js' 2 | 3 | export class ElasticSearchDdoStateQuery implements IDdoStateQuery { 4 | buildQuery(did?: string, nft?: string, txId?: string): Record { 5 | let query: Record = {} 6 | 7 | if (did) { 8 | query = { 9 | term: { 10 | did 11 | } 12 | } 13 | } 14 | 15 | if (nft) { 16 | query = { 17 | term: { 18 | nft 19 | } 20 | } 21 | } 22 | 23 | if (txId) { 24 | query = { 25 | term: { 26 | txId 27 | } 28 | } 29 | } 30 | 31 | return query 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/media/copy.63713a04.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /controlpanel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "paths": { 17 | "@/*": ["./src/*"], 18 | "@context/*": ["./src/context/*"], 19 | "@Types/*": ["./src/shared/types/*"], 20 | "@utils/*": ["./src/shared/utils/*"] 21 | } 22 | }, 23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "ES2022", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "noImplicitAny": true, 9 | "removeComments": true, 10 | "preserveConstEnums": true, 11 | "sourceMap": true, 12 | "allowSyntheticDefaultImports": true, 13 | "outDir": "./dist/", 14 | "declaration": true, 15 | "declarationDir": "./dist/", 16 | "ignoreDeprecations": "5.0", 17 | "experimentalDecorators": true, 18 | "emitDecoratorMetadata": true, 19 | "allowJs": true, 20 | "rootDir": "./src", 21 | "skipLibCheck": true 22 | }, 23 | "ts-node": { 24 | "esm": true 25 | }, 26 | "include": ["src/**/*"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /controlpanel/src/components/ControlPanel/NodePlatform.tsx: -------------------------------------------------------------------------------- 1 | import styles from './index.module.css' 2 | 3 | export default function NodePlatform({ 4 | platformData 5 | }: { 6 | platformData: { key: string; value: string | number }[] 7 | }) { 8 | return ( 9 |
10 |
PLATFORM
11 |
12 | {platformData.map((item) => { 13 | return ( 14 |
15 |
16 | {item.key}: 17 |
18 |
{item.value}
19 |
20 | ) 21 | })} 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /controlpanel/src/components/ControlPanel/SupportedNetworks.tsx: -------------------------------------------------------------------------------- 1 | import styles from './index.module.css' 2 | import { NodeDataType } from '@Types/dataTypes' 3 | 4 | export default function SupportedStorage({ data }: { data: NodeDataType | undefined }) { 5 | return ( 6 |
7 |
SUPPORTED Networks
8 |
9 | {data?.provider.map((item) => { 10 | return ( 11 |
12 |
13 | {item.chainId} 14 |
15 |
{item.network}
16 |
17 | ) 18 | })} 19 |
20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Indexer/processors/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseEventProcessor } from './BaseProcessor' 2 | 3 | export * from './DispenserActivatedEventProcessor.js' 4 | export * from './DispenserCreatedEventProcessor.js' 5 | export * from './DispenserDeactivatedEventProcessor.js' 6 | export * from './ExchangeActivatedEventProcessor.js' 7 | export * from './ExchangeCreatedEventProcessor.js' 8 | export * from './ExchangeDeactivatedEventProcessor.js' 9 | export * from './ExchangeRateChangedEventProcessor.js' 10 | export * from './MetadataEventProcessor.js' 11 | export * from './MetadataStateEventProcessor.js' 12 | export * from './OrderReusedEventProcessor.js' 13 | export * from './OrderStartedEventProcessor.js' 14 | export * from './BaseProcessor.js' 15 | 16 | export type ProcessorConstructor = new (chainId: number) => BaseEventProcessor 17 | -------------------------------------------------------------------------------- /src/components/httpRoutes/rootEndpoint.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { HTTP_LOGGER } from '../../utils/logging/common.js' 3 | import { getConfiguration } from '../../utils/index.js' 4 | import { getAllServiceEndpoints } from './index.js' 5 | export const rootEndpointRoutes = express.Router() 6 | 7 | rootEndpointRoutes.get('/', async (req, res) => { 8 | const config = await getConfiguration() 9 | if (!config.supportedNetworks) { 10 | HTTP_LOGGER.warn(`Supported networks not defined`) 11 | } 12 | res.json({ 13 | nodeId: config.keys.peerId, 14 | chainIds: config.supportedNetworks ? Object.keys(config.supportedNetworks) : [], 15 | providerAddress: config.keys.ethAddress, 16 | serviceEndpoints: getAllServiceEndpoints(), 17 | software: 'Ocean-Node', 18 | version: '0.0.1' 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/utils/config/transforms.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | import { CONFIG_LOGGER } from '../logging/common.js' 3 | 4 | export const booleanFromString = z.union([z.boolean(), z.string()]).transform((v) => { 5 | if (typeof v === 'string') { 6 | return v === 'true' || v === '1' || v.toLowerCase() === 'yes' 7 | } 8 | return v 9 | }) 10 | 11 | export const jsonFromString = (schema: z.ZodType) => 12 | z.union([schema, z.string(), z.undefined()]).transform((v) => { 13 | if (v === undefined || v === 'undefined') { 14 | return undefined 15 | } 16 | if (typeof v === 'string') { 17 | try { 18 | return JSON.parse(v) 19 | } catch (error) { 20 | CONFIG_LOGGER.warn(`Failed to parse JSON: ${error.message}`) 21 | return v 22 | } 23 | } 24 | return v 25 | }) 26 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaFeatures": { 6 | "jsx": false 7 | } 8 | }, 9 | "extends": ["oceanprotocol", "plugin:prettier/recommended"], 10 | "plugins": ["@typescript-eslint"], 11 | "rules": { 12 | "no-empty": ["error", { "allowEmptyCatch": true }], 13 | "prefer-destructuring": ["warn", { "object": true, "array": false }], 14 | "no-dupe-class-members": ["warn"], 15 | "no-useless-constructor": ["warn"], 16 | "constructor-super": ["warn"], 17 | "require-await": "error", 18 | "no-unused-vars": ["error"] 19 | }, 20 | "env": { 21 | "es6": true, 22 | "browser": true, 23 | "mocha": true, 24 | "node": true, 25 | "jest": true 26 | }, 27 | "globals": { 28 | "NodeJS": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/4583.205bbdd6677d7c00.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4583],{94583:function(M,N,C){C.r(N),C.d(N,{default:function(){return j}});var j="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyOCAyOCI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBmaWxsPSIjRjlGN0VDIiBkPSJNMTQgMjhjNy43MzIgMCAxNC02LjI2OCAxNC0xNFMyMS43MzIgMCAxNCAwIDAgNi4yNjggMCAxNHM2LjI2OCAxNCAxNCAxNFoiLz48cGF0aCBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0yMi4xMiAxMy45My00LjY2OS00LjY0OHYzLjQwMmwtNC42MzQgMy40MDloNC42MzR2Mi40ODVsNC42NjktNC42NDhaTTUuNjcgMTMuOTNsNC42NjkgNC42NDh2LTMuMzgxbDQuNjM0LTMuNDM3aC00LjYzNFY5LjI3NUw1LjY3IDEzLjkzWiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9nPjxkZWZzPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTAgMGgyOHYyOEgweiIvPjwvY2xpcFBhdGg+PC9kZWZzPjwvc3ZnPg=="}}]); -------------------------------------------------------------------------------- /controlpanel/src/components/Navigation/style.module.css: -------------------------------------------------------------------------------- 1 | .navbarParent { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | color: var(--gray-700); 6 | background-color: #ffffff; 7 | border-radius: 15px; 8 | box-shadow: 0px 7px 23px 0px rgba(0, 0, 0, 0.05); 9 | backdrop-filter: blur(10.5px); 10 | padding: 11px 24px; 11 | width: 100%; 12 | margin: 0 auto; 13 | } 14 | 15 | .logoWrapper { 16 | flex-shrink: 0; /* Prevent the logo from shrinking */ 17 | min-width: 108px; /* Minimum width for the logo */ 18 | } 19 | 20 | .connectButtonWrapper { 21 | flex-shrink: 0; /* Prevent the connect button from shrinking */ 22 | /* Optionally, you can set a min-width here as well */ 23 | } 24 | 25 | @media screen and (max-width: 700px) { 26 | .navbarParent { 27 | width: 90vw; 28 | margin: 0 auto; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /controlpanel/src/components/Table/index.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | width: 100%; 3 | max-width: 1244px; 4 | margin: 0 auto; 5 | border-radius: 15px; 6 | background: var(--white); 7 | box-shadow: 0px 3.5px 5.5px 0px rgba(0, 0, 0, 0.02); 8 | padding: 32px; 9 | display: flex; 10 | flex-direction: column; 11 | gap: 24px; 12 | } 13 | 14 | .title { 15 | color: var(--gray-700); 16 | font-family: Helvetica; 17 | font-size: 18px; 18 | font-style: normal; 19 | font-weight: 700; 20 | line-height: 140%; 21 | } 22 | 23 | .dropdownTriggerBox { 24 | width: 100%; 25 | display: flex; 26 | flex-direction: row; 27 | justify-content: center; 28 | align-items: center; 29 | } 30 | 31 | .dropdown { 32 | background-color: transparent; 33 | border: 0; 34 | outline: none; 35 | } 36 | 37 | .download:hover { 38 | background-color: transparent; 39 | } 40 | -------------------------------------------------------------------------------- /controlpanel/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | 3 | // import Table from '../components/Table' 4 | import NavBar from '../components/Navigation' 5 | import Footer from '../components/Footer' 6 | import ControlPanel from '../components/ControlPanel' 7 | 8 | export default function Home() { 9 | return ( 10 | <> 11 | 12 | Ocean Node Control Panel 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 |
21 | 22 | {/* */} 23 | 24 |
25 |
26 |
27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /controlpanel/src/components/Spinner/style.module.css: -------------------------------------------------------------------------------- 1 | .loader { 2 | width: 48px; 3 | height: 48px; 4 | border: 2px solid #FFF; 5 | border-radius: 50%; 6 | display: inline-block; 7 | position: relative; 8 | box-sizing: border-box; 9 | animation: rotation 1s linear infinite; 10 | } 11 | 12 | .loader::after, 13 | .loader::before { 14 | content: ''; 15 | box-sizing: border-box; 16 | position: absolute; 17 | left: 0; 18 | top: 0; 19 | background: #FF3D00; 20 | width: 6px; 21 | height: 6px; 22 | transform: translate(150%, 150%); 23 | border-radius: 50%; 24 | } 25 | 26 | .loader::before { 27 | left: auto; 28 | top: auto; 29 | right: 0; 30 | bottom: 0; 31 | transform: translate(-150%, -150%); 32 | } 33 | 34 | @keyframes rotation { 35 | 0% { 36 | transform: rotate(0deg); 37 | } 38 | 100% { 39 | transform: rotate(360deg); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /controlpanel/src/components/NodeDetails/index.module.css: -------------------------------------------------------------------------------- 1 | .root { 2 | width: calc(100% - 64px); 3 | padding: 32px 42px; 4 | margin-top: 24px; 5 | margin-left: 32px; 6 | margin-right: 32px; 7 | border-radius: 12px; 8 | display: flex; 9 | flex-direction: column; 10 | flex-wrap: wrap; 11 | max-height: 350px; 12 | gap: 24px; 13 | background-color: var(--background-secondary); 14 | } 15 | 16 | .item { 17 | min-width: 220px; 18 | border-bottom: 1px solid var(--border-color); 19 | padding-bottom: 12px; 20 | display: flex; 21 | flex-direction: row; 22 | justify-content: start; 23 | align-items: center; 24 | gap: 24px; 25 | } 26 | 27 | .key { 28 | width: 25%; 29 | color: var(--color-secondary); 30 | font-weight: 700; 31 | text-transform: capitalize; 32 | } 33 | 34 | .value { 35 | width: 75%; 36 | color: var(--gray-500); 37 | font-weight: 400; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/httpRoutes/queue.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { OceanIndexer } from '../Indexer' 3 | import { HTTP_LOGGER } from '../../utils/logging/common.js' 4 | import { SERVICES_API_BASE_PATH } from '../../utils/constants.js' 5 | 6 | export const queueRoutes = express.Router() 7 | 8 | queueRoutes.get(`${SERVICES_API_BASE_PATH}/indexQueue`, (req, res) => { 9 | try { 10 | const indexer: OceanIndexer = req.oceanNode.getIndexer() 11 | if (indexer) { 12 | const queue = indexer.getIndexingQueue() 13 | res.header('Content-Type', 'application/json') 14 | res.status(200).send(JSON.stringify({ queue })) 15 | } else { 16 | res.status(400).send('Indexer queue not found!') 17 | } 18 | } catch (error) { 19 | HTTP_LOGGER.error(`Error getting indexer queue: ${error.message}`) 20 | res.status(500).send('Error getting indexer queue') 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/1950.c8039f3dc9bb92f5.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1950],{41950:function(M,I,j){j.r(I),j.d(I,{default:function(){return g}});var g="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI4IiBoZWlnaHQ9IjI4IiBmaWxsPSIjMkM1RkY2Ii8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNMTQgMjMuOEMxOS40MTI0IDIzLjggMjMuOCAxOS40MTI0IDIzLjggMTRDMjMuOCA4LjU4NzYxIDE5LjQxMjQgNC4yIDE0IDQuMkM4LjU4NzYxIDQuMiA0LjIgOC41ODc2MSA0LjIgMTRDNC4yIDE5LjQxMjQgOC41ODc2MSAyMy44IDE0IDIzLjhaTTExLjU1IDEwLjhDMTEuMTM1OCAxMC44IDEwLjggMTEuMTM1OCAxMC44IDExLjU1VjE2LjQ1QzEwLjggMTYuODY0MiAxMS4xMzU4IDE3LjIgMTEuNTUgMTcuMkgxNi40NUMxNi44NjQyIDE3LjIgMTcuMiAxNi44NjQyIDE3LjIgMTYuNDVWMTEuNTVDMTcuMiAxMS4xMzU4IDE2Ljg2NDIgMTAuOCAxNi40NSAxMC44SDExLjU1WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=="}}]); -------------------------------------------------------------------------------- /src/components/httpRoutes/jobs.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { OceanIndexer } from '../Indexer' 3 | import { HTTP_LOGGER } from '../../utils/logging/common.js' 4 | import { SERVICES_API_BASE_PATH } from '../../utils/constants.js' 5 | 6 | export const jobsRoutes = express.Router() 7 | 8 | jobsRoutes.get(`${SERVICES_API_BASE_PATH}/jobs/:job`, (req, res) => { 9 | try { 10 | const indexer: OceanIndexer = req.oceanNode.getIndexer() 11 | if (indexer) { 12 | const jobs = indexer.getJobsPool((req.params.job as string) || null) 13 | res.header('Content-Type', 'application/json') 14 | res.status(200).send(JSON.stringify({ jobs })) 15 | } else { 16 | res.status(400).send('Indexer jobs not available!') 17 | } 18 | } catch (error) { 19 | HTTP_LOGGER.error(`Error getting indexer jobs pool: ${error.message}`) 20 | res.status(500).send('Error getting indexer jobs pool') 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /src/test/data/commands.ts: -------------------------------------------------------------------------------- 1 | export const freeComputeStartPayload = { 2 | command: 'freeStartCompute', 3 | consumerAddress: '0xeB5ae11175008E8f178d57d0152678a863FbB887', 4 | environment: '', 5 | nonce: '1', 6 | signature: '0x123', 7 | datasets: [ 8 | { 9 | fileObject: { 10 | type: 'url', 11 | url: 'https://raw.githubusercontent.com/oceanprotocol/ocean-cli/refs/heads/main/metadata/simpleComputeDataset.json', 12 | method: 'GET' 13 | } 14 | } 15 | ], 16 | algorithm: { 17 | fileObject: { 18 | type: 'url', 19 | url: 'https://raw.githubusercontent.com/oceanprotocol/ocean-cli/refs/heads/main/metadata/pythonAlgo.json', 20 | method: 'GET' 21 | }, 22 | meta: { 23 | container: { 24 | image: 'my-compute-test', 25 | tag: 'latest', 26 | entrypoint: 'python $ALGO', 27 | checksum: 'my-compute-checksum' 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/@types/DDO/ConsumerParameter.ts: -------------------------------------------------------------------------------- 1 | export interface ConsumerParameter { 2 | /** 3 | * Parameter name. 4 | * @type {string} 5 | */ 6 | name: string 7 | 8 | /** 9 | * Field type. 10 | * @type {'text' | 'number' | 'boolean' | 'select'} 11 | */ 12 | type: 'text' | 'number' | 'boolean' | 'select' 13 | 14 | /** 15 | * Displayed field label. 16 | * @type {string} 17 | */ 18 | label: string 19 | 20 | /** 21 | * Defines if customer input for this field is mandatory. 22 | * @type {boolean} 23 | */ 24 | required: boolean 25 | 26 | /** 27 | * Field description. 28 | * @type {string} 29 | */ 30 | description: string 31 | 32 | /** 33 | * Field default value. For select types, string key of default option. 34 | * @type {string} 35 | */ 36 | default: string | boolean | number 37 | 38 | /** 39 | * For select types, a list of options. 40 | * @type {string} 41 | */ 42 | options?: Array> 43 | } 44 | -------------------------------------------------------------------------------- /controlpanel/src/shared/types/dataTypes.ts: -------------------------------------------------------------------------------- 1 | export type IndexerType = { 2 | block: string 3 | chainId: string 4 | network: string 5 | delayed?: boolean 6 | } 7 | 8 | export type ProviderType = { 9 | chainId: string 10 | network: string 11 | } 12 | 13 | export type SupportedStorageType = { 14 | arwave: boolean 15 | ipfs: boolean 16 | url: boolean 17 | } 18 | 19 | export type PlatformType = { 20 | arch: string 21 | cpus: number 22 | freemem: number 23 | loadavg: number[] 24 | machine: string 25 | node: string 26 | osType: string 27 | osVersion: string 28 | platform: string 29 | release: string 30 | totalmem: number 31 | } 32 | 33 | export type NodeDataType = { 34 | address: string 35 | id: string 36 | publicKey: string 37 | uptime: string 38 | version: string 39 | http: boolean 40 | p2p: boolean 41 | indexer: IndexerType[] 42 | platform: PlatformType 43 | provider: ProviderType[] 44 | supportedStorage: SupportedStorageType 45 | } 46 | -------------------------------------------------------------------------------- /controlpanel/src/components/NodeDetails/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ExpanderComponentProps } from 'react-data-table-component' 3 | import { DataRowType } from '@Types/RowDataType' 4 | import styles from './index.module.css' 5 | 6 | const NodeDetails: React.FC> = ({ data }) => { 7 | const keyValuePairs = Object.keys(data.nodeDetails).map((key) => { 8 | // @ts-expect-error - error is shown here because the key is used as an index. 9 | return { key: `${key}`, value: `${data.nodeDetails[key]}` } 10 | }) 11 | 12 | return ( 13 |
14 | {keyValuePairs.map((item) => { 15 | return ( 16 |
17 |
{item.key}
18 |
{item.value}
19 |
20 | ) 21 | })} 22 |
23 | ) 24 | } 25 | 26 | export default NodeDetails 27 | -------------------------------------------------------------------------------- /controlpanel/src/assets/no-error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/database/typesenseConfig.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TypesenseAbstractLogger, 3 | TypesenseConfigOptions, 4 | TypesenseNode 5 | } from '../../@types' 6 | 7 | /** 8 | * TypesenseConfig class is used to specify configuration parameters for Typesense 9 | * as well as handling optional cases 10 | */ 11 | export class TypesenseConfig { 12 | apiKey: string 13 | nodes: TypesenseNode[] 14 | numRetries: number 15 | retryIntervalSeconds: number 16 | connectionTimeoutSeconds: number 17 | logLevel: string 18 | logger: TypesenseAbstractLogger 19 | 20 | constructor(options: TypesenseConfigOptions) { 21 | this.apiKey = options.apiKey 22 | this.nodes = options.nodes || [] 23 | this.numRetries = options.numRetries || 3 24 | this.connectionTimeoutSeconds = options.connectionTimeoutSeconds || 5 25 | this.retryIntervalSeconds = options.retryIntervalSeconds || 0.1 26 | this.logLevel = options.logLevel || 'debug' 27 | this.logger = options.logger || { debug: (log: any) => console.log(log) } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /controlpanel/src/components/shared/NetworkSelector.tsx: -------------------------------------------------------------------------------- 1 | import { Select, MenuItem, InputLabel, FormControl } from '@mui/material' 2 | import { useAdminContext } from '@/context/AdminProvider' 3 | 4 | interface NetworkSelectorProps { 5 | chainId?: string 6 | setChainId: (chainId: string) => void 7 | } 8 | 9 | export default function NetworkSelector({ chainId, setChainId }: NetworkSelectorProps) { 10 | const { networks } = useAdminContext() 11 | 12 | return ( 13 | 14 | Network 15 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/@types/DDO/SearchQuery.ts: -------------------------------------------------------------------------------- 1 | export interface FilterTerm { 2 | term?: { [key: string]: string | number | boolean } 3 | terms?: { [key: string]: (string | number | boolean)[] } 4 | range?: { [key: string]: { gte?: number; lte?: number } } 5 | bool?: any 6 | exists?: { field: string } 7 | match?: { [key: string]: string | number } 8 | } 9 | 10 | export interface BoolQuery { 11 | bool: { 12 | must?: FilterTerm[] 13 | must_not?: (FilterTerm | false | null | undefined)[] 14 | should?: FilterTerm[] 15 | filter?: FilterTerm[] 16 | } 17 | } 18 | 19 | export interface SearchQuery { 20 | q?: string 21 | filter_by?: any 22 | num_hits?: number 23 | start?: number 24 | sort_by?: string 25 | from?: number 26 | size?: number 27 | query?: any 28 | sort?: { [jsonPath: string]: string } 29 | aggs?: any 30 | } 31 | 32 | export interface BaseQueryParams { 33 | esPaginationOptions?: { 34 | from?: number 35 | size?: number 36 | } 37 | nestedQuery?: Partial 38 | filters?: FilterTerm[] 39 | chainIds?: number[] 40 | ignorePurgatory?: boolean 41 | ignoreState?: boolean 42 | } 43 | -------------------------------------------------------------------------------- /controlpanel/src/assets/search-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /controlpanel/src/components/ControlPanel/SupportedStorage.tsx: -------------------------------------------------------------------------------- 1 | import styles from './index.module.css' 2 | import { NodeDataType } from '@Types/dataTypes' 3 | 4 | export default function SupportedStorage({ data }: { data: NodeDataType | undefined }) { 5 | return ( 6 |
7 |
SUPPORTED STORAGE
8 |
9 |
10 |
11 | arwave: 12 |
13 |
{data?.supportedStorage.arwave.toString()}
14 |
15 |
16 |
17 | ipfs: 18 |
19 |
{data?.supportedStorage.ipfs.toString()}
20 |
21 |
22 |
23 | url: 24 |
25 |
{data?.supportedStorage.url.toString()}
26 |
27 |
28 |
29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /controlpanel/src/assets/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /controlpanel/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '@/styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | import { AdminProvider } from '@context/AdminProvider' 4 | import '@rainbow-me/rainbowkit/styles.css' 5 | import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit' 6 | import { WagmiProvider } from 'wagmi' 7 | import { QueryClientProvider, QueryClient } from '@tanstack/react-query' 8 | import { chains } from '@utils/chains' 9 | 10 | export default function App({ Component, pageProps }: AppProps) { 11 | const config = getDefaultConfig({ 12 | appName: 'Ocean Node Control Panel', 13 | projectId: process.env.NEXT_PUBLIC_WALLET_CONNECT_ID 14 | ? process.env.NEXT_PUBLIC_WALLET_CONNECT_ID 15 | : 'da267f7e1897e2cf92a7710f92e8f660', 16 | chains, 17 | ssr: true 18 | }) 19 | 20 | const queryClient = new QueryClient() 21 | 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /controlpanel/src/components/NodePeers/style.module.css: -------------------------------------------------------------------------------- 1 | .title24 { 2 | color: #3D4551; 3 | font-family: Helvetica; 4 | font-size: 18px; 5 | font-style: normal; 6 | font-weight: 700; 7 | line-height: 140%; /* 33.6px */ 8 | } 9 | 10 | .loaderContainer { 11 | position: absolute; 12 | width: 100%; 13 | height: 100%; 14 | background-color: rgba(51, 51, 51, 0.2); 15 | display: flex; 16 | flex-direction: row; 17 | justify-content: center; 18 | align-items: center; 19 | border-radius: 12px; 20 | } 21 | 22 | .nodes { 23 | display: flex; 24 | flex-direction: column; 25 | gap: 15px; 26 | position: relative; 27 | 28 | color: var(--Gray-Gray-500, #718096); 29 | font-family: Helvetica; 30 | font-size: 18px; 31 | font-style: normal; 32 | font-weight: 400; 33 | line-height: 140%; /* 25.2px */ 34 | } 35 | 36 | .nodeAddress { 37 | display: flex; 38 | flex-direction: row; 39 | gap: 18px; 40 | } 41 | 42 | .nodeAddress:hover { 43 | color: #333; 44 | cursor: pointer; 45 | } 46 | 47 | .nodeAddress > h5 { 48 | color: #3D4551; 49 | font-family: Helvetica; 50 | font-size: 18px; 51 | font-style: normal; 52 | font-weight: 700; 53 | line-height: 150%; /* 30px */ 54 | min-width: 55px; 55 | } 56 | -------------------------------------------------------------------------------- /src/components/core/utils/validateDdoHandler.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { CORE_LOGGER } from '../../../utils/logging/common.js' 3 | import { create256Hash } from '../../../utils/crypt.js' 4 | import { getProviderWallet } from './feesHandler.js' 5 | 6 | export async function getValidationSignature(ddo: string): Promise { 7 | try { 8 | const hashedDDO = create256Hash(ddo) 9 | const providerWallet = await getProviderWallet() 10 | const messageHash = ethers.solidityPackedKeccak256( 11 | ['bytes'], 12 | [ethers.hexlify(ethers.toUtf8Bytes(hashedDDO))] 13 | ) 14 | const signed32Bytes = await providerWallet.signMessage( 15 | new Uint8Array(ethers.toBeArray(messageHash)) 16 | ) 17 | const signatureSplitted = ethers.Signature.from(signed32Bytes) 18 | const v = signatureSplitted.v <= 1 ? signatureSplitted.v + 27 : signatureSplitted.v 19 | const r = ethers.hexlify(signatureSplitted.r) // 32 bytes 20 | const s = ethers.hexlify(signatureSplitted.s) 21 | return { hash: hashedDDO, publicKey: providerWallet.address, r, s, v } 22 | } catch (error) { 23 | CORE_LOGGER.logMessage(`Validation signature error: ${error}`, true) 24 | return { hash: '', publicKey: '', r: '', s: '', v: '' } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/704.484bcd9e0a7f5626.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[704],{60704:function(I,M,j){j.r(M),j.d(M,{default:function(){return L}});var L="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0ibm9uZSI+PHJlY3Qgd2lkdGg9IjI4IiBoZWlnaHQ9IjI4IiBmaWxsPSIjRkYzMTMxIiByeD0iMTQiLz48cmVjdCB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIGZpbGw9InVybCgjYSkiIGZpbGwtb3BhY2l0eT0iLjMiIHJ4PSIxNCIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik05LjIyIDE4LjM1YzIuNyAwIDQuODYtMi4yIDQuODYtNS4zOCAwLTIuMTktMS40Ny0zLjgtMy45OC0zLjgtMi43MiAwLTQuODUgMi4yLTQuODUgNS4zOCAwIDIuMiAxLjUgMy44IDMuOTcgMy44Wm0uODMtNy4zNWMxLjA2IDAgMS43NC44MSAxLjc0IDIuMSAwIDEuOS0xLjExIDMuNDItMi41MSAzLjQyLTEuMDYgMC0xLjc0LS44Mi0xLjc0LTIuMSAwLTEuODkgMS4xMS0zLjQyIDIuNS0zLjQyWm02LjM4LTEuNjgtMS44OCA4Ljg4aDIuMjZsLjU1LTIuNmgxLjQ3YzIuNDMgMCA0LjAxLTEuMzggNC4wMS0zLjYgMC0xLjYxLTEuMTctMi42OC0zLjEtMi42OGgtMy4zWm0xLjkgMS43NGguOTRjLjgzIDAgMS4zLjM4IDEuMyAxLjE0IDAgMS0uNjggMS43LTEuNzQgMS43aC0xLjExbC42LTIuODRaIi8+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMCIgeDI9IjE0IiB5MT0iMCIgeTI9IjI4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PC9zdmc+Cg=="}}]); -------------------------------------------------------------------------------- /src/helpers/scripts/generatePK.js: -------------------------------------------------------------------------------- 1 | //simple script to generate a new private key 2 | import * as ethCrypto from 'eth-crypto' 3 | import fs from 'node:fs' 4 | 5 | const argv = process.argv.slice(2) 6 | const saveToFile = argv.length === 1 && argv[0] === '--save' 7 | const pair = ethCrypto.createIdentity() 8 | if (saveToFile) { 9 | try { 10 | console.log('Saving private key to file ".pk.out"') 11 | const pkFile = fs.createWriteStream('.pk.out', { flags: 'w' }) 12 | pkFile.write(pair.privateKey) 13 | pkFile.end() 14 | console.log('Saving wallet address to file ".wallet.out"') 15 | const walletFile = fs.createWriteStream('.wallet.out', { flags: 'w' }) 16 | walletFile.write(pair.address) 17 | walletFile.end() 18 | } catch (error) { 19 | console.error('Error saving data to file: ', error.message) 20 | } 21 | } else { 22 | console.log('\n#########################################\n') 23 | console.log('\tWARNING: SENSITIVE DATA!\n') 24 | console.log('#########################################\n') 25 | console.log('\nYour Node Private Key: \n', pair.privateKey) 26 | console.log('\nYour Node Public Key:\n', pair.publicKey) 27 | console.log('\nYour Node Eth/Wallet Address:\n', pair.address) 28 | console.log('\n-------------------------------------------\n') 29 | } 30 | -------------------------------------------------------------------------------- /controlpanel/src/components/ControlPanel/Indexer.tsx: -------------------------------------------------------------------------------- 1 | import cs from 'classnames' 2 | import styles from './index.module.css' 3 | import IndexQueue from '../IndexQueue' 4 | import { NodeDataType } from '@Types/dataTypes' 5 | import { Card, Grid } from '@mui/material' 6 | 7 | export default function Indexer({ data }: { data: NodeDataType | undefined }) { 8 | return ( 9 |
10 |
INDEXER
11 | 12 | {data?.indexer.map((item) => { 13 | return ( 14 | 15 | 24 |
{item.network}
25 |
ChainID: {item.chainId}
26 |
BLOCK: {item.block}
27 |
28 |
29 | ) 30 | })} 31 |
32 | 33 | 34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /controlpanel/src/components/Copy/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement, useEffect, useState } from 'react' 2 | import Image from 'next/image' 3 | 4 | import styles from './index.module.css' 5 | 6 | import IconCopy from '../../assets/copy.svg' 7 | 8 | type CopyPropsType = { 9 | text: string 10 | } 11 | 12 | export default function Copy({ text }: CopyPropsType): ReactElement { 13 | const [isCopied, setIsCopied] = useState(false) 14 | 15 | const copyToClipboard = (text: string) => { 16 | const element = document.createElement('textarea') 17 | element.value = text 18 | document.body.appendChild(element) 19 | element.select() 20 | document.execCommand('copy') 21 | document.body.removeChild(element) 22 | } 23 | 24 | useEffect(() => { 25 | if (!isCopied) return 26 | 27 | const timeout = setTimeout(() => { 28 | setIsCopied(false) 29 | }, 1000) 30 | 31 | return () => clearTimeout(timeout) 32 | }, [isCopied]) 33 | 34 | return ( 35 |
{ 38 | copyToClipboard(text) 39 | setIsCopied(true) 40 | }} 41 | > 42 | 43 | {isCopied &&
Copied!
} 44 |
45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/5488.ea86c6ce443ba3bd.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[5488],{85488:function(I,i,g){g.r(i),g.d(i,{default:function(){return M}});var M="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzI1MjkyRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQgMjhhMTQgMTQgMCAxIDAgMC0yOCAxNCAxNCAwIDAgMCAwIDI4WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PHBhdGggZmlsbD0idXJsKCNhKSIgZmlsbC1vcGFjaXR5PSIuMyIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQgMjhhMTQgMTQgMCAxIDAgMC0yOCAxNCAxNCAwIDAgMCAwIDI4WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PHBhdGggZmlsbD0idXJsKCNiKSIgZD0iTTguMTkgMTQuNzcgMTQgMTguMjFsNS44LTMuNDQtNS44IDguMTktNS44MS04LjE5WiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Im0xNCAxNi45My01LjgxLTMuNDRMMTQgNC4zNGw1LjgxIDkuMTVMMTQgMTYuOTNaIi8+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4MT0iMCIgeDI9IjE0IiB5MT0iMCIgeTI9IjI4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIHgxPSIxNCIgeDI9IjE0IiB5MT0iMTQuNzciIHkyPSIyMi45NiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiNmZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iLjkiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4K"}}]); -------------------------------------------------------------------------------- /scripts/logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Default parameters 4 | API_URL=${1:-"http://localhost:8000"} 5 | START_TIME=$(date -u -d '-24 hour' +"%Y-%m-%dT%H:%M:%SZ") # for Linux 6 | # START_TIME=$(date -u -v-24H +"%Y-%m-%dT%H:%M:%SZ") # for macOS 7 | END_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") # current time 8 | MAX_LOGS=100 9 | MODULE_NAME="" 10 | LEVEL="" 11 | 12 | # Check if specific parameters are provided and override the defaults 13 | if [ ! -z "$2" ]; then 14 | START_TIME=$2 15 | fi 16 | 17 | if [ ! -z "$3" ]; then 18 | END_TIME=$3 19 | fi 20 | 21 | if [ ! -z "$4" ]; then 22 | MAX_LOGS=$4 23 | fi 24 | 25 | if [ ! -z "$5" ]; then 26 | MODULE_NAME=$5 27 | fi 28 | 29 | if [ ! -z "$6" ]; then 30 | LEVEL=$6 31 | fi 32 | 33 | # Prepare the data for the GET request 34 | DATA=( 35 | --data-urlencode "startTime=$START_TIME" 36 | --data-urlencode "endTime=$END_TIME" 37 | --data-urlencode "maxLogs=$MAX_LOGS" 38 | ) 39 | 40 | # Include moduleName and level in the request if they are provided 41 | if [ ! -z "$MODULE_NAME" ]; then 42 | DATA+=(--data-urlencode "moduleName=$MODULE_NAME") 43 | fi 44 | 45 | if [ ! -z "$LEVEL" ]; then 46 | DATA+=(--data-urlencode "level=$LEVEL") 47 | fi 48 | 49 | # Make API call to retrieve logs 50 | curl -G "$API_URL/logs" \ 51 | -H "Content-Type: application/json" \ 52 | "${DATA[@]}" | jq 53 | -------------------------------------------------------------------------------- /src/components/core/handler/nonceHandler.ts: -------------------------------------------------------------------------------- 1 | import { CommandHandler } from './handler.js' 2 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 3 | import { NonceCommand } from '../../../@types/commands.js' 4 | import { getNonce } from '../utils/nonceHandler.js' 5 | import { 6 | ValidateParams, 7 | buildInvalidRequestMessage, 8 | validateCommandParameters 9 | } from '../../httpRoutes/validateCommands.js' 10 | import { isAddress } from 'ethers' 11 | 12 | export class NonceHandler extends CommandHandler { 13 | validate(command: NonceCommand): ValidateParams { 14 | const validation = validateCommandParameters(command, ['address']) 15 | if (validation.valid) { 16 | if (!isAddress(command.address)) { 17 | return buildInvalidRequestMessage( 18 | 'Parameter : "address" is not a valid web3 address' 19 | ) 20 | } 21 | } 22 | return validation 23 | } 24 | 25 | // eslint-disable-next-line require-await 26 | async handle(task: NonceCommand): Promise { 27 | const validationResponse = await this.verifyParamsAndRateLimits(task) 28 | if (this.shouldDenyTaskHandling(validationResponse)) { 29 | return validationResponse 30 | } 31 | const { address } = task 32 | return getNonce(this.getOceanNode().getDatabase().nonce, address) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/core/admin/stopNodeHandler.ts: -------------------------------------------------------------------------------- 1 | import { AdminCommandHandler } from './adminHandler.js' 2 | import { AdminStopNodeCommand } from '../../../@types/commands.js' 3 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 4 | import { 5 | ValidateParams, 6 | buildInvalidParametersResponse 7 | } from '../../httpRoutes/validateCommands.js' 8 | import { CORE_LOGGER } from '../../../utils/logging/common.js' 9 | import { ReadableString } from '../../P2P/handleProtocolCommands.js' 10 | 11 | export class StopNodeHandler extends AdminCommandHandler { 12 | async validate(command: AdminStopNodeCommand): Promise { 13 | return await super.validate(command) 14 | } 15 | 16 | async handle(task: AdminStopNodeCommand): Promise { 17 | const validation = await this.validate(task) 18 | if (!validation.valid) { 19 | return new Promise((resolve, reject) => { 20 | resolve(buildInvalidParametersResponse(validation)) 21 | }) 22 | } 23 | CORE_LOGGER.logMessage(`Stopping node execution...`) 24 | setTimeout(() => { 25 | process.exit() 26 | }, 2000) 27 | return new Promise((resolve, reject) => { 28 | resolve({ 29 | status: { httpStatus: 200 }, 30 | stream: new ReadableString('EXIT OK') 31 | }) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/cronjobs/p2pAnnounceDDOS.ts: -------------------------------------------------------------------------------- 1 | // republish the ddos we have 2 | // related: https://github.com/libp2p/go-libp2p-kad-dht/issues/323 3 | 4 | import { OceanNode } from '../../OceanNode.js' 5 | import { P2P_LOGGER } from '../logging/common.js' 6 | 7 | export async function p2pAnnounceDDOS(node: OceanNode) { 8 | try { 9 | const db = node.getDatabase() 10 | const p2pNode = node.getP2PNode() 11 | if (!db || !db.ddo) { 12 | P2P_LOGGER.info( 13 | `republishStoredDDOS() attempt aborted because there is no database!` 14 | ) 15 | return 16 | } 17 | const ddoDb = db.ddo 18 | const searchParameters = { 19 | q: '*' 20 | } 21 | 22 | const result: any = await ddoDb.search(searchParameters) 23 | if (result && result.length > 0 && result[0].found) { 24 | P2P_LOGGER.logMessage(`Will republish cid for ${result[0].found} documents`, true) 25 | result[0].hits.forEach((hit: any) => { 26 | const ddo = hit.document 27 | p2pNode.advertiseString(ddo.id) 28 | p2pNode.cacheDDO(ddo) 29 | 30 | // todo check stuff like purgatory 31 | }) 32 | // update time 33 | } else { 34 | P2P_LOGGER.logMessage('There is nothing to republish, skipping...', true) 35 | } 36 | } catch (err) { 37 | P2P_LOGGER.error(`Caught "${err.message}" on republishStoredDDOS()`) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /controlpanel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ocean-node-control-panel", 3 | "author": "Ocean Protocol Foundation", 4 | "license": "Apache-2.0", 5 | "version": "0.1.0", 6 | "private": true, 7 | "scripts": { 8 | "dev": "next dev", 9 | "build": "NODE_ENV=production next build", 10 | "start": "next start", 11 | "lint": "next lint" 12 | }, 13 | "dependencies": { 14 | "@emotion/react": "^11.11.4", 15 | "@emotion/styled": "^11.11.0", 16 | "@mui/material": "^5.15.14", 17 | "@mui/x-date-pickers": "^7.2.0", 18 | "@rainbow-me/rainbowkit": "^2.0.2", 19 | "@tanstack/react-query": "^5.28.4", 20 | "classnames": "^2.5.0", 21 | "dayjs": "^1.11.10", 22 | "ethers": "^6.10.0", 23 | "micromodal": "^0.4.10", 24 | "next": "^13.5.6", 25 | "react": "^18", 26 | "react-data-table-component": "^7.5.4", 27 | "react-dom": "^18", 28 | "react-paginate": "^8.2.0", 29 | "react-query": "^3.39.3", 30 | "styled-components": "^6.1.1", 31 | "viem": "^2.8.14", 32 | "wagmi": "^2.5.11" 33 | }, 34 | "devDependencies": { 35 | "@types/node": "^20", 36 | "@types/react": "^18", 37 | "@types/react-dom": "^18", 38 | "@typescript-eslint/eslint-plugin": "^6.13.2", 39 | "eslint": "^8", 40 | "eslint-config-next": "14.0.4", 41 | "eslint-config-prettier": "^9.1.0", 42 | "prettier": "^3.1.1", 43 | "typescript": "^5" 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/utils/addresses.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Addresses for testing available from ganache 3 | */ 4 | 5 | export const ganachePrivateKeys: Record = { 6 | '0xe2DD09d719Da89e5a3D0F2549c7E24566e947260': 7 | '0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58', 8 | '0xBE5449a6A97aD46c8558A3356267Ee5D2731ab5e': 9 | '0xef4b441145c1d0f3b4bc6d61d29f5c6e502359481152f869247c7a4244d45209', 10 | '0xA78deb2Fa79463945C247991075E2a0e98Ba7A09': 11 | '0x5d75837394b078ce97bc289fa8d75e21000573520bfa7784a9d28ccaae602bf8', 12 | '0x02354A1F160A3fd7ac8b02ee91F04104440B28E7': 13 | '0x8467415bb2ba7c91084d932276214b11a3dd9bdb2930fefa194b666dd8020b99', 14 | '0xe17D2A07EFD5b112F4d675ea2d122ddb145d117B': 15 | '0x1f990f8b013fc5c7955e0f8746f11ded231721b9cf3f99ff06cdc03492b28090', 16 | '0xA32C84D2B44C041F3a56afC07a33f8AC5BF1A071': 17 | '0x732fbb7c355aa8898f4cff92fa7a6a947339eaf026a08a51f171199e35a18ae0', 18 | '0xFF3fE9eb218EAe9ae1eF9cC6C4db238B770B65CC': 19 | '0x8683d6511213ac949e093ca8e9179514d4c56ce5ea9b83068f723593f913b1ab', 20 | '0x529043886F21D9bc1AE0feDb751e34265a246e47': 21 | '0x1d751ded5a32226054cd2e71261039b65afb9ee1c746d055dd699b1150a5befc', 22 | '0xe08A1dAe983BC701D05E492DB80e0144f8f4b909': 23 | '0xfd5c1ccea015b6d663618850824154a3b3fb2882c46cefb05b9a93fea8c3d215', 24 | '0xbcE5A3468386C64507D30136685A99cFD5603135': 25 | '0x1263dc73bef43a9da06149c7e598f52025bf4027f1d6c13896b71e81bb9233fb' 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/database.ts: -------------------------------------------------------------------------------- 1 | import { OceanNodeDBConfig } from '../@types/OceanNode.js' 2 | import { Database } from '../components/database/index.js' 3 | import { getConfiguration } from './config.js' 4 | import { DB_TYPES } from './constants.js' 5 | import { URLUtils } from './url.js' 6 | 7 | // lazy loading 8 | let dbConnection: Database = null 9 | 10 | // lazy load env configuration and then db configuration 11 | // we should be able to use this every where without dep cycle issues 12 | export async function getDatabase(forceReload: boolean = false): Promise { 13 | if (!dbConnection || forceReload) { 14 | const { dbConfig } = await getConfiguration(true) 15 | if (dbConfig && dbConfig.url) { 16 | dbConnection = await Database.init(dbConfig) 17 | } 18 | } 19 | return dbConnection 20 | } 21 | 22 | export function hasValidDBConfiguration(configuration: OceanNodeDBConfig): boolean { 23 | if (!configuration || !configuration.dbType) { 24 | return false 25 | } 26 | return ( 27 | configuration.url && 28 | URLUtils.isValidUrl(configuration.url) && 29 | [DB_TYPES.ELASTIC_SEARCH, DB_TYPES.TYPESENSE].includes(configuration.dbType) 30 | ) 31 | } 32 | 33 | // we can use this to check if DB connection is available 34 | export async function isReachableConnection(url: string): Promise { 35 | try { 36 | await fetch(url) 37 | return true 38 | } catch (error) { 39 | return false 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /controlpanel/src/components/Admin/StopNode.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import styles from './index.module.css' 3 | import { useAdminContext } from '@context/AdminProvider' 4 | import Button from '@mui/material/Button' 5 | 6 | export default function StopNode() { 7 | const [isLoading, setLoading] = useState(false) 8 | const { signature, expiryTimestamp } = useAdminContext() 9 | 10 | async function stopNode() { 11 | setLoading(true) 12 | try { 13 | const apiUrl = '/directCommand' 14 | if (expiryTimestamp && signature) { 15 | await fetch(apiUrl, { 16 | headers: { 17 | Accept: 'application/json', 18 | 'Content-Type': 'application/json' 19 | }, 20 | method: 'POST', 21 | body: JSON.stringify({ 22 | command: 'stopNode', 23 | expiryTimestamp, 24 | signature 25 | }) 26 | }) 27 | } 28 | alert('The node has been stopped. The control panel will no longer be displayed.') 29 | window.location.reload() 30 | } catch (error) { 31 | console.error('error', error) 32 | } finally { 33 | setLoading(false) 34 | } 35 | } 36 | 37 | const Spinner = () => { 38 | return 39 | } 40 | 41 | return ( 42 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/135.67fab15ebc7d852e.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[135],{70135:function(M,N,I){I.r(N),I.d(N,{default:function(){return j}});var j="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyOCAyOCI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBmaWxsPSIjQjdCNUIxIiBkPSJNOCA4aDEydjEySDh6Ii8+PHBhdGggZmlsbD0iI0I3QjVCMSIgZD0iTTI4IDEyLjY2N0MyNC43ODYtNS45Ny40NDgtMi4zNjMuMDExIDEyLjY2N2MxLjQuNzI4IDIuMjg1IDEuMTc2IDIuMjg1IDEuMTc2cy0uNzQuNDQ4LTIuMjk2IDEuNDM0YzIuOCAxOC4yNzggMjYuNzIzIDE1LjYyNCAyOC0uMDIzLTEuNTIzLS45My0yLjM1Mi0xLjQyMi0yLjM1Mi0xLjQyMnMuNzE3LS4zMzYgMi4zNTItMS4xNjVabS0xMS45NzMgNi41MDctMi4yODUtMy45Mi0yLjMxOCAzLjkyLTEuNzU4LS4xMjMgMy4zMDQtNS41NjZMOS45OSA4LjY4bDEuNzkyLS4xNTcgMi4xMTcgMy41NjIgMi4xMTctMy40MDUgMS42NjkuMDQ1LTIuNzc4IDQuNzI2IDMuMDU4IDUuNjktMS45Ni4wNDUuMDIyLS4wMTJaIi8+PHBhdGggZmlsbD0iIzI0NEI4MSIgZD0iTTI2Ljg2OSAxMS45NEMyMi41MTItNC42MjcgMi41Mi0uMTQ3IDEuMTU0IDExLjk0YTI0OS41MTQgMjQ5LjUxNCAwIDAgMSAzLjQwNCAxLjkyNmwtMy40MTYgMi4xNzJjMi45OCAxNS45MjcgMjQuNTQgMTIuODU4IDI1LjcyNy0uMDIyLTIuMTczLTEuMzY2LTMuNDYxLTIuMTYyLTMuNDYxLTIuMTYyczIuOTM0LTEuNjM1IDMuNDYtMS45MTVabS0xMC44NDIgNy4yNDYtMi4yODUtMy45Mi0yLjMxOCAzLjkyLTEuNzQ3LS4xMjQgMy4zMDQtNS41NjZMMTAgOC42OTFsMS43OTMtLjE1NyAyLjExNiAzLjU2MiAyLjExNy0zLjQwNSAxLjY2OS4wNDUtMi43NjYgNC43MjYgMy4wNTcgNS42OS0xLjk2LjA0NXYtLjAxMVoiLz48L2c+PGRlZnM+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDI4djI4SDB6Ii8+PC9jbGlwUGF0aD48L2RlZnM+PC9zdmc+"}}]); -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/6237.f7b1d24c812922e4.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[6237],{36237:function(M,j,L){L.r(j),L.d(j,{default:function(){return D}});var D="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTIzIDVINXYxOGgxOFY1WiIvPjxwYXRoIGZpbGw9IiNFODQxNDIiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTE0IDI4Yy03LjUxMy4wMDgtMTQtNi40ODctMTQtMTRDMCA2LjE5NiA2LjA0My0uMDA4IDE0IDBjNy45NS4wMDggMTQgNi4xOTYgMTQgMTQgMCA3LjUwNS02LjQ5NSAxMy45OTItMTQgMTRabS0zLjk3MS03LjQzNkg3LjMxNWMtLjU3IDAtLjg1MSAwLTEuMDIzLS4xMWEuNjkuNjkgMCAwIDEtLjMxMy0uNTRjLS4wMS0uMjAyLjEzLS40NS40MTItLjk0NGw2LjctMTEuODA5Yy4yODUtLjUwMS40My0uNzUyLjYxMi0uODQ1LjE5NS0uMS40MjktLjEuNjI1IDAgLjE4Mi4wOTMuMzI2LjM0NC42MTEuODQ1bDEuMzc3IDIuNDA0LjAwNy4wMTNjLjMwOC41MzguNDY0LjgxLjUzMyAxLjA5N2EyLjA0IDIuMDQgMCAwIDEgMCAuOTU0Yy0uMDcuMjg5LS4yMjQuNTY0LS41MzYgMS4xMWwtMy41MiA2LjIyLS4wMDkuMDE3Yy0uMzEuNTQyLS40NjcuODE3LS42ODQgMS4wMjRhMi4wNDggMi4wNDggMCAwIDEtLjgzNS40ODVjLS4yODUuMDc5LS42MDQuMDc5LTEuMjQzLjA3OVptNi44NTIgMGgzLjg4OGMuNTc0IDAgLjg2MiAwIDEuMDM0LS4xMTNhLjY4Ny42ODcgMCAwIDAgLjMxMy0uNTQzYy4wMS0uMTk2LS4xMjgtLjQzNC0uMzk4LS45YTguMTk4IDguMTk4IDAgMCAxLS4wMjgtLjA0OGwtMS45NDgtMy4zMzItLjAyMi0uMDM3Yy0uMjc0LS40NjMtLjQxMi0uNjk3LS41OS0uNzg3YS42ODQuNjg0IDAgMCAwLS42MjEgMGMtLjE3OS4wOTMtLjMyMy4zMzctLjYwOC44MjhsLTEuOTQgMy4zMzEtLjAwNy4wMTJjLS4yODQuNDktLjQyNi43MzUtLjQxNi45MzYuMDE0LjIyLjEyNy40MjMuMzEzLjU0My4xNjguMTEuNDU2LjExIDEuMDMuMTFaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4K"}}]); -------------------------------------------------------------------------------- /controlpanel/src/components/Table/_styles.ts: -------------------------------------------------------------------------------- 1 | import { createTheme, TableStyles, Theme } from 'react-data-table-component' 2 | 3 | // https://github.com/jbetancur/react-data-table-component/blob/master/src/DataTable/themes.ts 4 | const theme: Partial = { 5 | text: { 6 | primary: 'var(-gray-gray-500)', 7 | secondary: 'var(--color-secondary)', 8 | disabled: 'var(--color-secondary)' 9 | }, 10 | background: { 11 | default: '#fff' 12 | }, 13 | divider: { 14 | default: 'var(--border-color)' 15 | } 16 | } 17 | 18 | createTheme('custom', theme) 19 | 20 | // https://github.com/jbetancur/react-data-table-component/blob/master/src/DataTable/styles.ts 21 | export const customStyles: TableStyles = { 22 | expanderButton: { 23 | style: { 24 | WebkitAppearance: 'none', 25 | width: '30px !important', 26 | height: '30px !important', 27 | background: 'transparent !important', 28 | border: 'transparent !important', 29 | color: '#A0AEC0 !important' 30 | } 31 | }, 32 | table: { 33 | style: { 34 | scrollbarWidth: 'thin' 35 | } 36 | }, 37 | head: { 38 | style: { 39 | fontWeight: '700' 40 | } 41 | }, 42 | headCells: { 43 | style: { 44 | textTransform: 'uppercase', 45 | color: 'var(--color-secondary)', 46 | fontSize: 'var(--font-size-small)' 47 | } 48 | }, 49 | rows: { 50 | style: { 51 | color: 'var(--gray-500)', 52 | paddingTop: '24px', 53 | paddingBottom: '24px' 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/@types/fileObject.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { Readable } from 'stream' 3 | 4 | export interface HeadersObject { 5 | [key: string]: string 6 | } 7 | 8 | export enum EncryptMethod { 9 | AES = 'AES', 10 | ECIES = 'ECIES' 11 | } 12 | 13 | export interface BaseFileObject { 14 | type: string 15 | encryptedBy?: string 16 | encryptMethod?: EncryptMethod 17 | } 18 | 19 | export interface UrlFileObject extends BaseFileObject { 20 | url: string 21 | method: string 22 | headers?: [HeadersObject] 23 | } 24 | 25 | export interface IpfsFileObject extends BaseFileObject { 26 | hash: string 27 | } 28 | 29 | export interface ArweaveFileObject extends BaseFileObject { 30 | transactionId: string 31 | } 32 | 33 | export interface StorageReadable { 34 | stream: Readable 35 | httpStatus?: number 36 | headers?: [any] 37 | } 38 | 39 | export enum FileObjectType { 40 | URL = 'url', 41 | IPFS = 'ipfs', 42 | ARWEAVE = 'arweave' 43 | } 44 | 45 | export interface FileInfoRequest { 46 | type: FileObjectType 47 | fileIndex?: number 48 | } 49 | 50 | export interface FileInfoResponse { 51 | valid: boolean 52 | contentLength: string 53 | contentType: string 54 | checksum?: string 55 | name: string 56 | type: string 57 | encryptedBy?: string 58 | encryptMethod?: EncryptMethod 59 | } 60 | 61 | export interface FileInfoHttpRequest { 62 | type?: 'ipfs' | 'url' | 'arweave' 63 | did?: string 64 | hash?: string 65 | url?: string 66 | transactionId?: string 67 | serviceId?: string 68 | } 69 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 AS base 2 | RUN apt-get update && apt-get -y install bash curl git wget libatomic1 python3 build-essential 3 | COPY .nvmrc /usr/src/app/ 4 | RUN rm /bin/sh && ln -s /bin/bash /bin/sh 5 | ENV NVM_DIR=/usr/local/nvm 6 | RUN mkdir $NVM_DIR 7 | ENV NODE_VERSION=v20.19.0 8 | # Install nvm with node and npm 9 | RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \ 10 | && source $NVM_DIR/nvm.sh \ 11 | && nvm install $NODE_VERSION \ 12 | && nvm alias default $NODE_VERSION \ 13 | && nvm use default 14 | ENV NODE_PATH=$NVM_DIR/$NODE_VERSION/lib/node_modules 15 | ENV PATH=$NVM_DIR/versions/node/$NODE_VERSION/bin:$PATH 16 | ENV IPFS_GATEWAY='https://ipfs.io/' 17 | ENV ARWEAVE_GATEWAY='https://arweave.net/' 18 | 19 | FROM base AS builder 20 | COPY package*.json /usr/src/app/ 21 | WORKDIR /usr/src/app/ 22 | RUN npm ci --maxsockets 1 23 | 24 | 25 | FROM base AS runner 26 | COPY . /usr/src/app 27 | WORKDIR /usr/src/app/ 28 | COPY --from=builder /usr/src/app/node_modules/ /usr/src/app/node_modules/ 29 | RUN npm run build 30 | # Remove the controlpanel folder to reduce the image size and avoid shipping development files 31 | RUN rm -rf controlpanel 32 | ENV P2P_ipV4BindTcpPort=9000 33 | EXPOSE 9000 34 | ENV P2P_ipV4BindWsPort=9001 35 | EXPOSE 9001 36 | ENV P2P_ipV6BindTcpPort=9002 37 | EXPOSE 9002 38 | ENV P2P_ipV6BindWsPort=9003 39 | EXPOSE 9003 40 | ENV P2P_ipV4BindWssPort=9005 41 | EXPOSE 9005 42 | ENV HTTP_API_PORT=8000 43 | EXPOSE 8000 44 | ENV NODE_ENV='production' 45 | CMD ["npm","run","start"] 46 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/5939.0a433dc6f963fc41.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[5939],{95939:function(N,M,I){I.r(M),I.d(M,{default:function(){return j}});var j="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjgiIGhlaWdodD0iMjgiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iQSIgeDE9Ii0xOC4yNzUlIiB4Mj0iODQuOTU5JSIgeTE9IjguMjE5JSIgeTI9IjcxLjM5MyUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMwMDJkNzQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMwMDEyNDYiLz48L2xpbmVhckdyYWRpZW50PjxjaXJjbGUgaWQ9IkIiIGN4PSIxNCIgY3k9IjE0IiByPSIxNCIvPjwvZGVmcz48ZyBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxtYXNrIGlkPSJDIiBmaWxsPSIjZmZmIj48dXNlIHhsaW5rOmhyZWY9IiNCIi8+PC9tYXNrPjxnIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZmlsbD0idXJsKCNBKSIgZD0iTS0xLjMyNi0xLjMyNmgzMC42NTF2MzAuNjUxSC0xLjMyNnoiIG1hc2s9InVybCgjQykiLz48ZyBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMTQuMTg3IDZMNyAxMC4xNzV2OC4zNWw3LjE4NyA0LjE3NSA3LjE3NS00LjE3NXYtOC4zNUwxNC4xODcgNnptNS4wNDYgMTEuMjg2bC01LjA1OCAyLjkzNi01LjA0Ni0yLjkzNnYtNS44NzFsNS4wNTgtMi45MzYgNS4wNDYgMi45MzZ2NS44NzF6Ii8+PHBhdGggZD0iTTE0LjE4NyAyMi43bDcuMTc1LTQuMTc1di04LjM1TDE0LjE4NyA2djIuNDc5bDUuMDQ2IDIuOTM2djUuODgzbC01LjA1OCAyLjkzNlYyMi43aC4wMTJ6Ii8+PHBhdGggZD0iTTE0LjE3NSA2TDcgMTAuMTc1djguMzVsNy4xNzUgNC4xNzV2LTIuNDc5bC01LjA0Ni0yLjkzNnYtNS44ODNsNS4wNDYtMi45MjRWNnptMy4zNiAxMC4yOTlsLTMuMzQ4IDEuOTQ5LTMuMzYtMS45NDl2LTMuODk4bDMuMzYtMS45NDkgMy4zNDggMS45NDktMS4zOTkuODE4LTEuOTYxLTEuMTQzLTEuOTQ5IDEuMTQzdjIuMjc0bDEuOTYxIDEuMTQzIDEuOTYxLTEuMTQzIDEuMzg3LjgwNnoiLz48L2c+PC9nPjwvZz48L3N2Zz4="}}]); -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/8881.8c985300b37d631a.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[8881],{48881:function(j,I,M){M.r(I),M.d(I,{default:function(){return N}});var N="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjgiIGhlaWdodD0iMjgiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0iQSIgeDE9Ii0xOC4yNzUlIiB4Mj0iODQuOTU5JSIgeTE9IjguMjE5JSIgeTI9IjcxLjM5MyUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNhMjI5YzUiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM3YjNmZTQiLz48L2xpbmVhckdyYWRpZW50PjxjaXJjbGUgaWQ9IkIiIGN4PSIxNCIgY3k9IjE0IiByPSIxNCIvPjwvZGVmcz48ZyBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxtYXNrIGlkPSJDIiBmaWxsPSIjZmZmIj48dXNlIHhsaW5rOmhyZWY9IiNCIi8+PC9tYXNrPjxnIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZmlsbD0idXJsKCNBKSIgZD0iTS0xLjMyNi0xLjMyNmgzMC42NTF2MzAuNjUxSC0xLjMyNnoiIG1hc2s9InVybCgjQykiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTguMDQ5IDE3LjAyMWwzLjk2LTIuMjg3YS42ODEuNjgxIDAgMCAwIC4zNC0uNTg5VjkuNTcyYS42ODMuNjgzIDAgMCAwLS4zNC0uNTlsLTMuOTYtMi4yODZhLjY4Mi42ODIgMCAwIDAtLjY4IDBsLTMuOTYgMi4yODdhLjY4Mi42ODIgMCAwIDAtLjM0LjU4OXY4LjE3M0wxMC4yOSAxOS4zNWwtMi43NzctMS42MDR2LTMuMjA3bDIuNzc3LTEuNjA0IDEuODMyIDEuMDU4VjExLjg0bC0xLjQ5Mi0uODYxYS42ODEuNjgxIDAgMCAwLS42OCAwbC0zLjk2IDIuMjg3YS42ODEuNjgxIDAgMCAwLS4zNC41ODl2NC41NzNjMCAuMjQyLjEzLjQ2OC4zNC41OWwzLjk2IDIuMjg2YS42OC42OCAwIDAgMCAuNjggMGwzLjk2LTIuMjg2YS42ODIuNjgyIDAgMCAwIC4zNC0uNTg5di04LjE3NGwuMDUtLjAyOCAyLjcyOC0xLjU3NSAyLjc3NyAxLjYwM3YzLjIwOGwtMi43NzcgMS42MDMtMS44My0xLjA1NnYyLjE1MWwxLjQ5Ljg2YS42OC42OCAwIDAgMCAuNjggMHoiLz48L2c+PC9nPjwvc3ZnPg=="}}]); -------------------------------------------------------------------------------- /controlpanel/src/components/Admin/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './index.module.css' 2 | import DownloadLogs from './DownloadLogs' 3 | import StopNode from './StopNode' 4 | import { useAdminContext } from '@/context/AdminProvider' 5 | import { useAccount } from 'wagmi' 6 | import { ConnectButton } from '@rainbow-me/rainbowkit' 7 | import Stack from '@mui/material/Stack' 8 | import ReIndexChain from './ReindexChain' 9 | import ReIndexTransaction from './ReindexTransaction' 10 | import TransferFees from './TransferFees' 11 | 12 | export default function AdminActions() { 13 | const { generateSignature, signature, validTimestamp, admin } = useAdminContext() 14 | const { isConnected } = useAccount() 15 | 16 | return ( 17 |
18 |
ADMIN ACTIONS
19 | {!isConnected && } 20 | {isConnected && !admin && ( 21 |
Your account does not have admin access
22 | )} 23 | 24 | {(!signature || !validTimestamp) && isConnected && admin && ( 25 | 28 | )} 29 | {isConnected && signature && validTimestamp && isConnected && admin && ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | )} 38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Indexer/version.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Compares two semantic version strings 3 | * @param v1 First version 4 | * @param v2 Second version 5 | * @returns -1 if v1 < v2, 0 if v1 = v2, 1 if v1 > v2 6 | */ 7 | export function compareVersions(v1: string, v2: string): number { 8 | const parts1 = v1.split('.').map(Number) 9 | const parts2 = v2.split('.').map(Number) 10 | 11 | for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { 12 | const part1 = i < parts1.length ? parts1[i] : 0 13 | const part2 = i < parts2.length ? parts2[i] : 0 14 | 15 | if (part1 < part2) return -1 16 | if (part1 > part2) return 1 17 | } 18 | 19 | return 0 20 | } 21 | 22 | /** 23 | * Checks if reindexing is needed based on version comparison 24 | * @param currentVersion Current node version 25 | * @param dbVersion Version stored in database 26 | * @param minVersion Minimum version that requires reindexing 27 | * @returns boolean indicating if reindexing is needed 28 | */ 29 | export function isReindexingNeeded( 30 | currentVersion: string, 31 | dbVersion: string | null, 32 | minVersion: string 33 | ): boolean { 34 | // If no DB version exists, reindexing is needed 35 | if (!dbVersion) return true 36 | 37 | // If current version is less than min version, something is wrong 38 | if (compareVersions(currentVersion, minVersion) < 0) { 39 | throw new Error( 40 | `Current version ${currentVersion} is less than minimum required version ${minVersion}` 41 | ) 42 | } 43 | 44 | // If DB version is less than min version, reindexing is needed 45 | return compareVersions(dbVersion, minVersion) < 0 46 | } 47 | -------------------------------------------------------------------------------- /src/components/c2d/index.ts: -------------------------------------------------------------------------------- 1 | import { deleteKeysFromObject, sanitizeServiceFiles } from '../../utils/util.js' 2 | 3 | import { decrypt } from '../../utils/crypt.js' 4 | import { BaseFileObject, EncryptMethod } from '../../@types/fileObject.js' 5 | import { CORE_LOGGER } from '../../utils/logging/common.js' 6 | import { ComputeJob, DBComputeJob } from '../../@types/index.js' 7 | export { C2DEngine } from './compute_engine_base.js' 8 | 9 | export async function decryptFilesObject( 10 | serviceFiles: any 11 | ): Promise { 12 | try { 13 | // 2. Decrypt the url 14 | const decryptedUrlBytes = await decrypt( 15 | Uint8Array.from(Buffer.from(sanitizeServiceFiles(serviceFiles), 'hex')), 16 | EncryptMethod.ECIES 17 | ) 18 | 19 | // 3. Convert the decrypted bytes back to a string 20 | const decryptedFilesString = Buffer.from(decryptedUrlBytes).toString() 21 | const decryptedFileArray = JSON.parse(decryptedFilesString) 22 | 23 | console.log('decryptedFileArray: ', decryptedFileArray) 24 | return decryptedFileArray.files[0] 25 | } catch (err) { 26 | CORE_LOGGER.error('Error decrypting files object: ' + err.message) 27 | return null 28 | } 29 | } 30 | 31 | export function omitDBComputeFieldsFromComputeJob(dbCompute: DBComputeJob): ComputeJob { 32 | const job: ComputeJob = deleteKeysFromObject(dbCompute, [ 33 | 'clusterHash', 34 | 'configlogURL', 35 | 'publishlogURL', 36 | 'algologURL', 37 | 'outputsURL', 38 | 'algorithm', 39 | 'assets', 40 | 'isRunning', 41 | 'isStarted', 42 | 'containerImage' 43 | ]) as ComputeJob 44 | return job 45 | } 46 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/7682.b0a3567fac8e0052.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[7682],{57682:function(I,M,N){N.r(M),N.d(M,{default:function(){return j}});var j="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA3OCA3OCI+PHBhdGggZmlsbD0idXJsKCNhKSIgZD0iTTcxLjAzNCAyMC41YTM3LjAwMSAzNy4wMDEgMCAwIDAtNjQuMDg0IDBsMi4yMiAzOS45Nkw3MS4wMzQgMjAuNVoiLz48cGF0aCBmaWxsPSJ1cmwoI2IpIiBkPSJNMjIuOTc5IDQ4LjI1IDYuOTU4IDIwLjVBMzcgMzcgMCAwIDAgMzkgNzZsMzYuMjYtMzctNTIuMjgxIDkuMjVaIi8+PHBhdGggZmlsbD0idXJsKCNjKSIgZD0iTTU1LjAyMSA0OC4yNSAzOSA3NmEzNy4wMDEgMzcuMDAxIDAgMCAwIDMyLjAzNS01NS41SDM5bDE2LjAyMSAyNy43NVoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMzkgNTcuNWExOC41IDE4LjUgMCAxIDAgMC0zNyAxOC41IDE4LjUgMCAwIDAgMCAzN1oiLz48cGF0aCBmaWxsPSIjMUE3M0U4IiBkPSJNMzkgNTMuNjUyYTE0LjY1IDE0LjY1IDAgMCAwIDEzLjUzNi0yMC4yNkExNC42NTMgMTQuNjUzIDAgMSAwIDM5IDUzLjY1M1oiLz48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSI2Ljk1OCIgeDI9IjcxLjAzNCIgeTE9IjI1LjEyNSIgeTI9IjI1LjEyNSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiNEOTMwMjUiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNFQTQzMzUiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYiIgeDE9IjQzLjAwMyIgeDI9IjEwLjk2MSIgeTE9IjczLjY4NCIgeTI9IjE4LjE4NCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiMxRThFM0UiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNEE4NTMiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iYyIgeDE9IjMzLjU5OCIgeDI9IjY1LjY0IiB5MT0iNzYiIHkyPSIyMC41OTYiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBzdG9wLWNvbG9yPSIjRkNDOTM0Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkJCQzA0Ii8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PC9zdmc+"}}]); -------------------------------------------------------------------------------- /src/utils/logging/common.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CustomNodeLogger, 3 | getCustomLoggerForModule, 4 | LOGGER_MODULE_NAMES 5 | } from './Logger.js' 6 | 7 | // TODO gather all the logger instances used here 8 | // right now they are scatered all hover the place 9 | // should keep Max 10 10 | // 1 11 | // Ocean Node 12 | export const OCEAN_NODE_LOGGER: CustomNodeLogger = getCustomLoggerForModule( 13 | LOGGER_MODULE_NAMES.OCEAN_NODE 14 | ) 15 | // 2 16 | // Core stuff 17 | export const CORE_LOGGER: CustomNodeLogger = getCustomLoggerForModule( 18 | LOGGER_MODULE_NAMES.CORE 19 | ) 20 | // 3 21 | // DB 22 | export const DATABASE_LOGGER: CustomNodeLogger = getCustomLoggerForModule( 23 | LOGGER_MODULE_NAMES.DATABASE 24 | ) 25 | // 4 26 | // http 27 | // use this logger instance on all HTTP related stuff 28 | export const HTTP_LOGGER: CustomNodeLogger = getCustomLoggerForModule( 29 | LOGGER_MODULE_NAMES.HTTP 30 | ) 31 | // 5 32 | // indexer 33 | export const INDEXER_LOGGER: CustomNodeLogger = getCustomLoggerForModule( 34 | LOGGER_MODULE_NAMES.INDEXER 35 | ) 36 | // 6 37 | // P2P 38 | // just use the default logger with default transports 39 | // Bellow is just an example usage, only logging to console here 40 | export const P2P_LOGGER: CustomNodeLogger = getCustomLoggerForModule( 41 | LOGGER_MODULE_NAMES.P2P 42 | ) 43 | // 7 44 | // provider 45 | // this should be actually part of provider, so lets put this as module name 46 | export const PROVIDER_LOGGER: CustomNodeLogger = getCustomLoggerForModule( 47 | LOGGER_MODULE_NAMES.PROVIDER 48 | ) 49 | // 8 50 | // config 51 | export const CONFIG_LOGGER: CustomNodeLogger = getCustomLoggerForModule( 52 | LOGGER_MODULE_NAMES.CONFIG 53 | ) 54 | -------------------------------------------------------------------------------- /src/components/core/admin/fetchConfigHandler.ts: -------------------------------------------------------------------------------- 1 | import { AdminCommandHandler } from './adminHandler.js' 2 | import { AdminFetchConfigCommand } from '../../../@types/commands.js' 3 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 4 | import { 5 | ValidateParams, 6 | buildInvalidParametersResponse 7 | } from '../../httpRoutes/validateCommands.js' 8 | import { ReadableString } from '../../P2P/handleProtocolCommands.js' 9 | import { loadConfigFromFile } from '../../../utils/config/index.js' 10 | 11 | export class FetchConfigHandler extends AdminCommandHandler { 12 | async validate(command: AdminFetchConfigCommand): Promise { 13 | return await super.validate(command) 14 | } 15 | 16 | async handle(task: AdminFetchConfigCommand): Promise { 17 | const validation = await this.validate(task) 18 | if (!validation.valid) { 19 | return new Promise((resolve) => { 20 | resolve(buildInvalidParametersResponse(validation)) 21 | }) 22 | } 23 | 24 | try { 25 | const config = loadConfigFromFile() 26 | config.keys.privateKey = '[*** HIDDEN CONTENT ***]' 27 | 28 | return new Promise((resolve) => { 29 | resolve({ 30 | status: { httpStatus: 200 }, 31 | stream: new ReadableString(JSON.stringify(config)) 32 | }) 33 | }) 34 | } catch (error) { 35 | return new Promise((resolve) => { 36 | resolve({ 37 | status: { httpStatus: 500, error: `Error fetching config: ${error.message}` }, 38 | stream: null 39 | }) 40 | }) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/data/ddoSchema.ts: -------------------------------------------------------------------------------- 1 | import { TypesenseCollectionCreateSchema } from '../../@types' 2 | 3 | export const ddoSchema: TypesenseCollectionCreateSchema = { 4 | name: 'ddo', 5 | enable_nested_fields: true, 6 | fields: [ 7 | { name: '@context', type: 'string[]', optional: true }, 8 | { name: 'chainId', type: 'int64', optional: true }, 9 | { name: 'version', type: 'string', sort: true, optional: true }, 10 | { name: 'nftAddress', type: 'string' }, 11 | 12 | { name: 'metadata.description', type: 'string' }, 13 | { name: 'metadata.copyrightHolder', type: 'string', optional: true }, 14 | { name: 'metadata.name', type: 'string', optional: true }, 15 | { name: 'metadata.type', type: 'string', optional: true }, 16 | { name: 'metadata.author', type: 'string', optional: true }, 17 | { name: 'metadata.license', type: 'string', optional: true }, 18 | { name: 'metadata.links', type: 'string', optional: true }, 19 | { name: 'metadata.tags', type: 'string[]', optional: true }, 20 | { name: 'metadata.categories', type: 'string', optional: true }, 21 | { name: 'metadata.contentLanguage', type: 'string', optional: true }, 22 | { name: 'metadata.algorithm.version', type: 'string', optional: true }, 23 | { name: 'metadata.algorithm.language', type: 'string', optional: true }, 24 | { name: 'metadata.algorithm.container.entrypoint', type: 'string', optional: true }, 25 | { name: 'metadata.algorithm.container.image', type: 'string', optional: true }, 26 | { name: 'metadata.algorithm.container.tag', type: 'string', optional: true }, 27 | { name: 'metadata.algorithm.container.checksum', type: 'string', optional: true } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/1727.af62bd633f21ee69.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1727],{1727:function(I,M,i){i.r(M),i.d(M,{default:function(){return j}});var j="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0ibm9uZSI+PHJlY3Qgd2lkdGg9IjI2LjYiIGhlaWdodD0iMjYuNiIgeD0iLjciIHk9Ii43IiBmaWxsPSIjMkQzNzRCIiBzdHJva2U9IiM5NkJFREMiIHN0cm9rZS13aWR0aD0iMS40IiByeD0iMTMuMyIvPjxtYXNrIGlkPSJhIiB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHg9IjAiIHk9IjAiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHN0eWxlPSJtYXNrLXR5cGU6YWxwaGEiPjxyZWN0IHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0iI0M0QzRDNCIgcng9IjE0Ii8+PC9tYXNrPjxnIG1hc2s9InVybCgjYSkiPjxwYXRoIGZpbGw9IiMyOEEwRjAiIGQ9Im0xNC4wODYxIDE4LjYwNDEgNi41MDE0IDEwLjIyMzkgNC4wMDU3LTIuMzIxMy03Ljg2LTEyLjM5NDMtMi42NDcxIDQuNDkxN1ptMTMuMDc0NCAzLjQ2OTItLjAwMy0xLjg1OTktNy4zMDY0LTExLjQwNy0yLjMwODcgMy45MTczIDcuMDkxIDExLjQzMDMgMi4xNzItMS4yNTg2YS45NjI4Ljk2MjggMCAwIDAgLjM1NTUtLjcwMDlsLS4wMDA0LS4xMjEyWiIvPjxyZWN0IHdpZHRoPSIyNS45IiBoZWlnaHQ9IjI1LjkiIHg9IjEuMDUiIHk9IjEuMDUiIGZpbGw9InVybCgjYikiIGZpbGwtb3BhY2l0eT0iLjMiIHN0cm9rZT0iIzk2QkVEQyIgc3Ryb2tlLXdpZHRoPSIyLjEiIHJ4PSIxMi45NSIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Im0uMzYzNCAyOC4yMjA3LTMuMDctMS43Njc0LS4yMzQtLjgzMzNMNy43NDYxIDkuMDE5NGMuNzI5OC0xLjE5MTMgMi4zMTk3LTEuNTc1IDMuNzk1Ny0xLjU1NDFsMS43MzIzLjA0NTdMLjM2MzQgMjguMjIwN1pNMTkuMTY1NSA3LjUxMWwtNC41NjUzLjAxNjZMMi4yNCAyNy45NTMzbDMuNjEwMyAyLjA3ODguOTgxOC0xLjY2NTJMMTkuMTY1NSA3LjUxMVoiLz48L2c+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJiIiB4MT0iMCIgeDI9IjE0IiB5MT0iMCIgeTI9IjI4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iI2ZmZiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIgc3RvcC1vcGFjaXR5PSIwIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PC9zdmc+Cg=="}}]); -------------------------------------------------------------------------------- /src/utils/conversions.ts: -------------------------------------------------------------------------------- 1 | import { CID } from 'multiformats/cid' 2 | import { sha256 } from 'multiformats/hashes/sha2' 3 | import * as multiFormatRaw from 'multiformats/codecs/raw' 4 | import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' 5 | 6 | export function hexStringToByteArray(hexString: any) { 7 | if (hexString.length % 2 !== 0) { 8 | throw new Error('Must have an even number of hex digits to convert to bytes') 9 | } /* w w w. jav a2 s . c o m */ 10 | const numBytes = hexString.length / 2 11 | const byteArray = new Uint8Array(numBytes) 12 | for (let i = 0; i < numBytes; i++) { 13 | byteArray[i] = parseInt(hexString.substr(i * 2, 2), 16) 14 | } 15 | return byteArray 16 | } 17 | 18 | export async function cidFromRawString(data: string) { 19 | const hash = await sha256.digest(uint8ArrayFromString(data)) 20 | const cid = CID.create(1, multiFormatRaw.code, hash) 21 | return cid 22 | } 23 | 24 | export function getRandomInt(min: number, max: number) { 25 | min = Math.ceil(min) 26 | max = Math.floor(max) 27 | return Math.floor(Math.random() * (max - min) + min) // The maximum is exclusive and the minimum is inclusive 28 | } 29 | 30 | export function timestampToDateTime(timestamp: number) { 31 | const date = new Date(timestamp * 1000) 32 | const year = date.getFullYear() 33 | const month = String(date.getMonth() + 1).padStart(2, '0') 34 | const day = String(date.getDate()).padStart(2, '0') 35 | const hours = String(date.getHours()).padStart(2, '0') 36 | const minutes = String(date.getMinutes()).padStart(2, '0') 37 | const seconds = String(date.getSeconds()).padStart(2, '0') 38 | return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}Z` 39 | } 40 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/4253.6be69df622e36e45.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[4253],{84253:function(M,j,g){g.r(j),g.d(j,{default:function(){return L}});var L="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0ibm9uZSI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBmaWxsPSIjRjBCOTBCIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNCAwYzcuNzMzIDAgMTQgNi4yNjcgMTQgMTRzLTYuMjY3IDE0LTE0IDE0UzAgMjEuNzMzIDAgMTQgNi4yNjcgMCAxNCAwWiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0ibTcuNjk0IDE0IC4wMSAzLjcwMiAzLjE0NiAxLjg1djIuMTY4bC00Ljk4Ni0yLjkyNHYtNS44NzhMNy42OTQgMTRabTAtMy43MDJ2Mi4xNTdsLTEuODMyLTEuMDgzVjkuMjE0bDEuODMyLTEuMDgzIDEuODQxIDEuMDgzLTEuODQgMS4wODRabTQuNDctMS4wODQgMS44MzItMS4wODMgMS44NCAxLjA4My0xLjg0IDEuMDg0LTEuODMyLTEuMDg0WiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik05LjAxOCAxNi45MzV2LTIuMTY4bDEuODMyIDEuMDg0djIuMTU3bC0xLjgzMi0xLjA3M1ptMy4xNDYgMy4zOTQgMS44MzIgMS4wODQgMS44NC0xLjA4NHYyLjE1N2wtMS44NCAxLjA4NC0xLjgzMi0xLjA4NFYyMC4zM1ptNi4zLTExLjExNSAxLjgzMi0xLjA4MyAxLjg0IDEuMDgzdjIuMTU4bC0xLjg0IDEuMDgzdi0yLjE1N2wtMS44MzItMS4wODRabTEuODMyIDguNDg4LjAxLTMuNzAyIDEuODMxLTEuMDg0djUuODc5bC00Ljk4NiAyLjkyNHYtMi4xNjdsMy4xNDUtMS44NVoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJtMTguOTgyIDE2LjkzNS0xLjgzMiAxLjA3M3YtMi4xNTdsMS44MzItMS4wODR2Mi4xNjhaIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0ibTE4Ljk4MiAxMS4wNjUuMDEgMi4xNjgtMy4xNTUgMS44NXYzLjcxMmwtMS44MzEgMS4wNzMtMS44MzItMS4wNzN2LTMuNzExbC0zLjE1NS0xLjg1MXYtMi4xNjhsMS44NC0xLjA4MyAzLjEzNSAxLjg2IDMuMTU1LTEuODYgMS44NCAxLjA4M2gtLjAwN1ptLTkuOTY0LTMuNyA0Ljk3Ny0yLjkzNSA0Ljk4NyAyLjkzNS0xLjgzMiAxLjA4My0zLjE1NC0xLjg2LTMuMTQ2IDEuODYtMS44MzItMS4wODNaIi8+PC9nPjxkZWZzPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTAgMGgyOHYyOEgweiIvPjwvY2xpcFBhdGg+PC9kZWZzPjwvc3ZnPg=="}}]); -------------------------------------------------------------------------------- /src/test/unit/crypt.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { decrypt, encrypt } from '../../utils/crypt.js' 3 | import { EncryptMethod } from '../../@types/fileObject.js' 4 | import { 5 | buildEnvOverrideConfig, 6 | OverrideEnvConfig, 7 | setupEnvironment, 8 | tearDownEnvironment, 9 | TEST_ENV_CONFIG_FILE 10 | } from '../utils/utils.js' 11 | import { ENVIRONMENT_VARIABLES } from '../../utils/constants.js' 12 | import { homedir } from 'os' 13 | 14 | describe('crypt', () => { 15 | let envOverrides: OverrideEnvConfig[] 16 | before(async () => { 17 | envOverrides = buildEnvOverrideConfig( 18 | [ENVIRONMENT_VARIABLES.RPCS, ENVIRONMENT_VARIABLES.ADDRESS_FILE], 19 | [ 20 | '{ "8996":{ "rpc":"http://172.0.0.1:8545", "chainId": 8996, "network": "development", "chunkSize": 100 }}', 21 | `${homedir}/.ocean/ocean-contracts/artifacts/address.json` 22 | ] 23 | ) 24 | envOverrides = await setupEnvironment(TEST_ENV_CONFIG_FILE, envOverrides) 25 | }) 26 | it('should encrypt/decrypt AES', async () => { 27 | const data = Uint8Array.from(Buffer.from('ocean')) 28 | const encryptedData = await encrypt(data, EncryptMethod.AES) 29 | const decryptedData = await decrypt(encryptedData, EncryptMethod.AES) 30 | expect(Uint8Array.from(decryptedData)).to.deep.equal(data) 31 | }) 32 | it('should encrypt/decrypt ECIES', async () => { 33 | const data = Uint8Array.from(Buffer.from('ocean')) 34 | const encryptedData = await encrypt(data, EncryptMethod.ECIES) 35 | const decryptedData = await decrypt(encryptedData, EncryptMethod.ECIES) 36 | expect(Uint8Array.from(decryptedData)).to.deep.equal(data) 37 | }) 38 | after(async () => { 39 | await tearDownEnvironment(envOverrides) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /docs/database.md: -------------------------------------------------------------------------------- 1 | # Ocean Node Database Configuration 2 | 3 | Ocean Node can be run with two types of databases: Elasticsearch or Typesense, or with no database at all (using a NoSQL setup). This flexibility allows you to configure the node based on your infrastructure needs. 4 | 5 | ## Database Configuration 6 | 7 | Depending on the database type you choose, you will need to set specific environment variables. Ocean Node supports either Elasticsearch or Typesense as the database for storing the various node components. 8 | 9 | ### 1. Set the Environment Variables 10 | 11 | • For Typesense, you need to set the following environment variables: 12 | 13 | ```bash 14 | DB_TYPE=typesense 15 | DB_URL="http://localhost:8108/?apiKey=xyz" # Example URL when using Barge for Typesense 16 | ``` 17 | 18 | • For Elasticsearch, you need to set: 19 | 20 | ```bash 21 | DB_TYPE=elasticsearch 22 | DB_URL="http://localhost:9200" # Example URL when using Barge for Elasticsearch 23 | ``` 24 | 25 | Ensure that the correct DB_TYPE is specified as either typesense or elasticsearch depending on your chosen setup. 26 | 27 | ### 2. Starting Ocean Barge 28 | 29 | To run Ocean Node with the appropriate database, you need to start Barge with specific flags. 30 | 31 | • To run Ocean Node with Typesense, use the following command: 32 | 33 | ```bash 34 | ./start_ocean.sh --no-aquarius --no-provider --no-dashboard --with-c2d --with-typesense --no-elasticsearch 35 | ``` 36 | 37 | • To run Ocean Node with Elasticsearch, use the following command: 38 | 39 | ```bash 40 | ./start_ocean.sh --no-aquarius --no-provider --no-dashboard --with-c2d 41 | ``` 42 | 43 | By specifying these flags, you can configure Ocean Node to work with either Typesense or Elasticsearch databases, depending on your requirements. 44 | -------------------------------------------------------------------------------- /controlpanel/README.md: -------------------------------------------------------------------------------- 1 | # Control Panel 2 | 3 | The static controlpanel files are included in ocean nodes so the control panel doesn't have to be rebuilt every time the node is built. If there are changes to the control panel it should be rebuilt with `npm run build:controlpanel`. 4 | 5 | When you start your node the control panel will be made available at: `http://localhost:8000/controlpanel/` 6 | 7 | ## Local development 8 | 9 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 10 | 11 | ## Getting Started 12 | 13 | First, run the development server: 14 | 15 | ```bash 16 | npm i 17 | 18 | npm run dev 19 | ``` 20 | 21 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 22 | 23 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 24 | 25 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 26 | 27 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 28 | 29 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 30 | 31 | ## Build & run 32 | 33 | The control panel is built by default with the Ocean Node. Set the environmental variables and then run the following commands from the root of the project: 34 | 35 | ``` 36 | npm run build 37 | npm run start 38 | ``` 39 | 40 | The control panel will be made available at: `http://localhost:8000/controlpanel/` 41 | -------------------------------------------------------------------------------- /controlpanel/src/components/Table/index.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import DataTable, { TableColumn } from 'react-data-table-component' 3 | 4 | import styles from './index.module.css' 5 | import { customStyles } from './_styles' 6 | 7 | import NodeDetails from '../NodeDetails' 8 | import { Data } from './data' 9 | import ErrorCheck from '../ErrorCheck' 10 | import { DataRowType } from '../../shared/types/RowDataType' 11 | import DownloadSVG from '../../assets/download.svg' 12 | 13 | export interface TableOceanColumn extends TableColumn { 14 | selector?: (row: T) => any 15 | } 16 | 17 | const DownloadButton = () => { 18 | return ( 19 | 22 | ) 23 | } 24 | 25 | export default function Table() { 26 | const Columns: TableOceanColumn[] = [ 27 | { name: 'Node Id', selector: (row) => row.nodeId }, 28 | { name: 'Network', selector: (row) => row.network }, 29 | { name: 'Chain Id', selector: (row) => row.chainId }, 30 | { name: 'Components', selector: (row) => row.components }, 31 | { name: 'Block Number', selector: (row) => row.blockNumber }, 32 | { 33 | name: 'Errors', 34 | selector: (row) => 35 | }, 36 | { name: 'Logs', selector: () => } 37 | ] 38 | 39 | return ( 40 |
41 |

Ocean Node Control Panel

42 | 52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /controlpanel/src/shared/utils/jobs.ts: -------------------------------------------------------------------------------- 1 | import { CommandStatus, JobStatus } from '../types/JobTypes' 2 | 3 | /** 4 | * 5 | * - Light Grey, associated with something that is neutral and not doing any actions 6 | - Blue, associated with non-critical, basic information 7 | - Green, associated with success and completion 8 | - Red, associated with an error, or a critical message 9 | */ 10 | export function getStatusColors(status: CommandStatus): string { 11 | switch (status) { 12 | case CommandStatus.DELIVERED: 13 | return 'DodgerBlue' 14 | case CommandStatus.PENDING: 15 | return 'LightSlateGrey' 16 | case CommandStatus.SUCCESS: 17 | return 'ForestGreen' 18 | case CommandStatus.FAILURE: 19 | return 'OrangeRed' 20 | default: 21 | return 'black' 22 | } 23 | } 24 | 25 | export const checkJobPool = async function (jobId?: string): Promise { 26 | const id = jobId || '' 27 | 28 | try { 29 | const jobsPool = '/api/services/jobs/' + id 30 | const res = await fetch(jobsPool, { 31 | headers: { 32 | Accept: 'application/json', 33 | 'Content-Type': 'application/json' 34 | }, 35 | method: 'GET' 36 | }) 37 | const data = await res.json() 38 | return data.jobs 39 | } catch (err) { 40 | console.error(err) 41 | } 42 | 43 | return [] 44 | } 45 | 46 | export function getSeverityFromStatus(status: CommandStatus): string { 47 | switch (status) { 48 | case CommandStatus.DELIVERED: 49 | return 'info' 50 | case CommandStatus.SUCCESS: 51 | return 'success' 52 | case CommandStatus.PENDING: 53 | return 'warning' 54 | default: 55 | return 'error' 56 | } 57 | } 58 | 59 | export function isJobDone(jobStatus: CommandStatus): boolean { 60 | return [CommandStatus.SUCCESS, CommandStatus.FAILURE].includes(jobStatus) 61 | } 62 | -------------------------------------------------------------------------------- /src/components/core/handler/getJobs.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream' 2 | import { GetJobsCommand } from '../../../@types/commands.js' 3 | import { CORE_LOGGER } from '../../../utils/logging/common.js' 4 | import { buildInvalidRequestMessage } from '../../httpRoutes/validateCommands.js' 5 | import { CommandHandler } from './handler.js' 6 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 7 | 8 | export class GetJobsHandler extends CommandHandler { 9 | validate(command: GetJobsCommand) { 10 | if (command.fromTimestamp && typeof command.fromTimestamp !== 'string') { 11 | return buildInvalidRequestMessage( 12 | 'Parameter : "fromTimestamp" is not a valid string' 13 | ) 14 | } 15 | return { valid: true } 16 | } 17 | 18 | async handle(task: GetJobsCommand): Promise { 19 | const validationResponse = await this.verifyParamsAndRateLimits(task) 20 | if (this.shouldDenyTaskHandling(validationResponse)) { 21 | return validationResponse 22 | } 23 | 24 | try { 25 | const { c2d } = this.getOceanNode().getDatabase() 26 | if (!c2d) { 27 | throw new Error('C2D database not initialized') 28 | } 29 | 30 | const jobs = await c2d.getJobs( 31 | task.environments, 32 | task.fromTimestamp, 33 | task.consumerAddrs 34 | ) 35 | return { 36 | stream: Readable.from(JSON.stringify(jobs)), 37 | status: { 38 | httpStatus: 200, 39 | error: null 40 | } 41 | } 42 | } catch (error) { 43 | const message = error instanceof Error ? error.message : String(error) 44 | CORE_LOGGER.error('Error retrieving node jobs: ' + message) 45 | return { 46 | status: { 47 | httpStatus: 500, 48 | error: message 49 | }, 50 | stream: null 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/components/httpRoutes/dids.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express' 2 | import { sendMissingP2PResponse } from './index.js' 3 | import { hasP2PInterface } from '../../utils/config.js' 4 | 5 | export const getProvidersForStringRoute = express.Router() 6 | getProvidersForStringRoute.get( 7 | '/getProvidersForString', 8 | express.urlencoded({ extended: true, type: '*/*' }), 9 | async (req: Request, res: Response): Promise => { 10 | if (!req.query.input) { 11 | res.sendStatus(400) 12 | return 13 | } 14 | if (hasP2PInterface) { 15 | const providers = await req.oceanNode 16 | .getP2PNode() 17 | .getProvidersForString(req.query.input as string) 18 | res.json(providers) 19 | } else { 20 | sendMissingP2PResponse(res) 21 | } 22 | } 23 | ) 24 | 25 | export const getProvidersForStringsRoute = express.Router() 26 | getProvidersForStringsRoute.post( 27 | '/getProvidersForStrings', 28 | express.json(), 29 | async (req, res) => { 30 | try { 31 | if (!req.body) { 32 | res.status(400).send('Missing array of strings in request body.') 33 | return 34 | } 35 | // const body = JSON.parse(req.body) 36 | if ( 37 | Array.isArray(req.body) && 38 | req.body.every((item: unknown) => typeof item === 'string') 39 | ) { 40 | const timeout = 41 | typeof req.query?.timeout === 'string' 42 | ? parseInt(req.query.timeout, 10) 43 | : undefined 44 | const providers = await req.oceanNode 45 | .getP2PNode() 46 | .getProvidersForStrings(req.body, timeout) 47 | 48 | res.json(providers) 49 | } else { 50 | res.status(400).send('Expected an array of strings.') 51 | } 52 | } catch (error) { 53 | console.error('Error processing request:', error) 54 | res.status(400).send(error) 55 | } 56 | } 57 | ) 58 | -------------------------------------------------------------------------------- /src/@types/Fees.ts: -------------------------------------------------------------------------------- 1 | import { BigNumberish } from 'ethers' 2 | 3 | export type FeeTokens = { 4 | chain: string // chain id => 137 5 | token: string // token => token address 0x967da4048cD07aB37855c090aAF366e4ce1b9F48 6 | } 7 | 8 | export type FeeAmount = { 9 | amount: number 10 | unit: string // ex: MB, KB, GB, etc... 11 | } 12 | // ocean node fees 13 | export type FeeStrategy = { 14 | feeTokens: FeeTokens[] 15 | feeAmount: FeeAmount 16 | } 17 | 18 | export interface ProviderFeeData { 19 | providerFeeAddress: string 20 | providerFeeToken: string 21 | providerFeeAmount: number | BigNumberish 22 | providerData: any 23 | v: any 24 | r: any 25 | s: any 26 | validUntil: number // this is always an absolute timestamp, till order is valid. Not a relative one (until further notice) 27 | } 28 | 29 | export interface ProviderFeeValidation { 30 | isValid: boolean // true if valid provider fee for download 31 | message: any 32 | validUntil: number 33 | } 34 | 35 | export interface ProviderFees { 36 | providerFeeAddress: string 37 | providerFeeToken: string 38 | providerFeeAmount: string 39 | v: string 40 | r: string 41 | s: string 42 | providerData: string 43 | validUntil: string 44 | } 45 | 46 | export interface ProviderInitialize { 47 | datatoken: string 48 | nonce: string 49 | computeAddress: string 50 | providerFee: ProviderFees 51 | } 52 | 53 | export interface ProviderComputeInitialize { 54 | datatoken?: string 55 | validOrder?: string 56 | providerFee?: ProviderFees 57 | } 58 | 59 | export interface ProviderComputeInitializePayment { 60 | escrowAddress: string 61 | payee: string 62 | chainId: number 63 | minLockSeconds: number 64 | token: string 65 | amount: string 66 | } 67 | export interface ProviderComputeInitializeResults { 68 | algorithm?: ProviderComputeInitialize 69 | datasets?: ProviderComputeInitialize[] 70 | payment: ProviderComputeInitializePayment 71 | } 72 | -------------------------------------------------------------------------------- /src/components/core/compute/environments.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream' 2 | import { P2PCommandResponse } from '../../../@types/index.js' 3 | import { CORE_LOGGER } from '../../../utils/logging/common.js' 4 | import { CommandHandler } from '../handler/handler.js' 5 | import { ComputeGetEnvironmentsCommand } from '../../../@types/commands.js' 6 | import { 7 | ValidateParams, 8 | buildInvalidRequestMessage, 9 | validateCommandParameters 10 | } from '../../httpRoutes/validateCommands.js' 11 | export class ComputeGetEnvironmentsHandler extends CommandHandler { 12 | validate(command: ComputeGetEnvironmentsCommand): ValidateParams { 13 | const validateCommand = validateCommandParameters(command, []) 14 | if (!validateCommand.valid) { 15 | return buildInvalidRequestMessage( 16 | 'Invalid getComputeEnv command ' + validateCommand.reason 17 | ) 18 | } 19 | return validateCommand 20 | } 21 | 22 | async handle(task: ComputeGetEnvironmentsCommand): Promise { 23 | const validationResponse = await this.verifyParamsAndRateLimits(task) 24 | if (this.shouldDenyTaskHandling(validationResponse)) { 25 | return validationResponse 26 | } 27 | try { 28 | const computeEngines = this.getOceanNode().getC2DEngines() 29 | const result = await computeEngines.fetchEnvironments(task.chainId) 30 | 31 | CORE_LOGGER.logMessage( 32 | 'ComputeGetEnvironmentsCommand Response: ' + JSON.stringify(result, null, 2), 33 | true 34 | ) 35 | 36 | return { 37 | stream: Readable.from(JSON.stringify(result)), 38 | status: { 39 | httpStatus: 200 40 | } 41 | } 42 | } catch (error) { 43 | CORE_LOGGER.error(error.message) 44 | return { 45 | stream: null, 46 | status: { 47 | httpStatus: 500, 48 | error: error.message 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/database/AuthTokenDatabase.ts: -------------------------------------------------------------------------------- 1 | import { DATABASE_LOGGER } from '../../utils/logging/common.js' 2 | import { AbstractDatabase } from './BaseDatabase.js' 3 | import { OceanNodeDBConfig } from '../../@types/OceanNode.js' 4 | import path from 'path' 5 | import * as fs from 'fs' 6 | import { SQLiteAuthToken } from './sqliteAuthToken.js' 7 | 8 | export interface AuthToken { 9 | token: string 10 | address: string 11 | created: Date 12 | validUntil: Date | null 13 | isValid: boolean 14 | } 15 | 16 | export class AuthTokenDatabase extends AbstractDatabase { 17 | private provider: SQLiteAuthToken 18 | 19 | private constructor(config: OceanNodeDBConfig, provider?: SQLiteAuthToken) { 20 | super(config) 21 | this.provider = provider 22 | } 23 | 24 | static async create(config: OceanNodeDBConfig): Promise { 25 | DATABASE_LOGGER.info('Creating AuthTokenDatabase with SQLite') 26 | const dbDir = path.dirname('databases/authTokenDatabase.sqlite') 27 | if (!fs.existsSync(dbDir)) { 28 | fs.mkdirSync(dbDir, { recursive: true }) 29 | } 30 | const provider = new SQLiteAuthToken('databases/authTokenDatabase.sqlite') 31 | await provider.createTable() 32 | return new AuthTokenDatabase(config, provider) 33 | } 34 | 35 | async createToken( 36 | token: string, 37 | address: string, 38 | validUntil: number | null = null, 39 | createdAt: number 40 | ): Promise { 41 | await this.provider.createToken(token, address, createdAt, validUntil) 42 | return token 43 | } 44 | 45 | async validateToken(token: string): Promise { 46 | const tokenEntry = await this.provider.validateTokenEntry(token) 47 | if (!tokenEntry) { 48 | return null 49 | } 50 | 51 | return tokenEntry 52 | } 53 | 54 | async invalidateToken(token: string): Promise { 55 | await this.provider.invalidateTokenEntry(token) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/database/ElasticSearchMetadataQuery.ts: -------------------------------------------------------------------------------- 1 | import { SearchQuery } from './../../@types/DDO/SearchQuery.js' 2 | import { IMetadataQuery } from '../../@types/DDO/IMetadataQuery.js' 3 | 4 | export class ElasticSearchMetadataQuery implements IMetadataQuery { 5 | buildQuery(searchQuery: SearchQuery): Record { 6 | if (this.isElasticSearchQuery(searchQuery)) { 7 | return searchQuery 8 | } 9 | const elasticsearchQuery: Record = { 10 | from: searchQuery.start || 0, 11 | size: searchQuery.num_hits || 10, 12 | query: { 13 | bool: { 14 | filter: [], 15 | must_not: [] 16 | } 17 | } 18 | } 19 | if (searchQuery.filter_by) { 20 | const filters = searchQuery.filter_by.split(' && ') 21 | filters.forEach((filter: string) => { 22 | let field, value 23 | if (filter.includes('!=')) { 24 | ;[field, value] = filter.split(':!=') 25 | elasticsearchQuery.query.bool.must_not.push({ 26 | term: { [field]: value } 27 | }) 28 | } else if (filter.includes(':=[')) { 29 | ;[field, value] = filter.split(':=[') 30 | const values = value.replace(']', '').split(',') 31 | elasticsearchQuery.query.bool.filter.push({ 32 | terms: { [field]: values } 33 | }) 34 | } else { 35 | ;[field, value] = filter.split(':=') 36 | elasticsearchQuery.query.bool.filter.push({ 37 | term: { [field]: value } 38 | }) 39 | } 40 | }) 41 | } 42 | if (searchQuery.sort_by) { 43 | const [sortField, sortOrder] = searchQuery.sort_by.split(':') 44 | elasticsearchQuery.sort = [{ [sortField]: { order: sortOrder } }] 45 | } 46 | 47 | return elasticsearchQuery 48 | } 49 | 50 | private isElasticSearchQuery(query: any): boolean { 51 | return query && query.query && query.query.bool !== undefined 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/database/SQLLiteConfigDatabase.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import { DATABASE_LOGGER } from '../../utils/logging/common.js' 4 | import { GENERIC_EMOJIS, LOG_LEVELS_STR } from '../../utils/logging/Logger.js' 5 | import { SQLiteProvider } from './sqlite.js' 6 | 7 | export class SQLLiteConfigDatabase { 8 | private provider: SQLiteProvider 9 | 10 | constructor() { 11 | return (async (): Promise => { 12 | DATABASE_LOGGER.info('Config Database initiated with SQLite provider') 13 | 14 | // Ensure the directory exists before instantiating SQLiteProvider 15 | const dbDir = path.dirname('databases/config.sqlite') 16 | if (!fs.existsSync(dbDir)) { 17 | fs.mkdirSync(dbDir, { recursive: true }) 18 | } 19 | this.provider = new SQLiteProvider('databases/config.sqlite') 20 | await this.provider.createTableForConfig() 21 | 22 | return this 23 | })() as unknown as SQLLiteConfigDatabase 24 | } 25 | 26 | async createOrUpdateConfig(key: string = 'version', value: string) { 27 | try { 28 | return await this.provider.createOrUpdateConfig(key, value) 29 | } catch (error) { 30 | const errorMsg = `Error when creating new version entry ${value}: ` + error.message 31 | DATABASE_LOGGER.logMessageWithEmoji( 32 | errorMsg, 33 | true, 34 | GENERIC_EMOJIS.EMOJI_CROSS_MARK, 35 | LOG_LEVELS_STR.LEVEL_ERROR 36 | ) 37 | return null 38 | } 39 | } 40 | 41 | async retrieveValue(key: string = 'version') { 42 | try { 43 | return await this.provider.retrieveValue(key) 44 | } catch (error) { 45 | const errorMsg = `Error when retrieving latest version entry: ` + error.message 46 | DATABASE_LOGGER.logMessageWithEmoji( 47 | errorMsg, 48 | true, 49 | GENERIC_EMOJIS.EMOJI_CROSS_MARK, 50 | LOG_LEVELS_STR.LEVEL_ERROR 51 | ) 52 | return null 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/core/handler/statusHandler.ts: -------------------------------------------------------------------------------- 1 | import { CommandHandler } from './handler.js' 2 | import { status } from '../utils/statusHandler.js' 3 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 4 | import { DetailedStatusCommand, StatusCommand } from '../../../@types/commands.js' 5 | import { Readable } from 'stream' 6 | import { 7 | ValidateParams, 8 | validateCommandParameters 9 | } from '../../httpRoutes/validateCommands.js' 10 | import { CORE_LOGGER } from '../../../utils/logging/common.js' 11 | 12 | export class StatusHandler extends CommandHandler { 13 | validate(command: StatusCommand): ValidateParams { 14 | return validateCommandParameters(command, []) 15 | } 16 | 17 | async handle(task: StatusCommand): Promise { 18 | const checks = await this.verifyParamsAndRateLimits(task) 19 | if (checks.status.httpStatus !== 200 || checks.status.error !== null) { 20 | return checks 21 | } 22 | try { 23 | const statusResult = await status(this.getOceanNode(), task.node, !!task.detailed) 24 | if (!statusResult) { 25 | return { 26 | stream: null, 27 | status: { httpStatus: 404, error: 'Status Not Found' } 28 | } 29 | } 30 | return { 31 | stream: Readable.from(JSON.stringify(statusResult, null, 4)), 32 | status: { httpStatus: 200 } 33 | } 34 | } catch (error) { 35 | CORE_LOGGER.error(`Error in StatusHandler: ${error.message}`) 36 | return { 37 | stream: null, 38 | status: { httpStatus: 500, error: 'Unknown error: ' + error.message } 39 | } 40 | } 41 | } 42 | } 43 | 44 | export class DetailedStatusHandler extends StatusHandler { 45 | validate(command: DetailedStatusCommand): ValidateParams { 46 | return validateCommandParameters(command, []) 47 | } 48 | 49 | async handle(task: StatusCommand): Promise { 50 | task.detailed = true 51 | return await super.handle(task) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/core/admin/adminHandler.ts: -------------------------------------------------------------------------------- 1 | import { AdminCommand, IValidateAdminCommandHandler } from '../../../@types/commands.js' 2 | import { 3 | ValidateParams, 4 | validateCommandParameters, 5 | buildInvalidRequestMessage, 6 | buildRateLimitReachedResponse, 7 | buildInvalidParametersResponse 8 | } from '../../httpRoutes/validateCommands.js' 9 | import { validateAdminSignature } from '../../../utils/auth.js' 10 | import { BaseHandler } from '../handler/handler.js' 11 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 12 | import { ReadableString } from '../../P2P/handleProtocolCommands.js' 13 | import { CommonValidation } from '../../../utils/validators.js' 14 | 15 | export abstract class AdminCommandHandler 16 | extends BaseHandler 17 | implements IValidateAdminCommandHandler 18 | { 19 | async verifyParamsAndRateLimits(task: AdminCommand): Promise { 20 | if (!(await this.checkRateLimit())) { 21 | return buildRateLimitReachedResponse() 22 | } 23 | // then validate the command arguments 24 | const validation = await this.validate(task) 25 | if (!validation.valid) { 26 | return buildInvalidParametersResponse(validation) 27 | } 28 | 29 | // all good! 30 | return { 31 | stream: new ReadableString('OK'), 32 | status: { httpStatus: 200, error: null } 33 | } 34 | } 35 | 36 | async validate(command: AdminCommand): Promise { 37 | const commandValidation = validateCommandParameters(command, [ 38 | 'expiryTimestamp', 39 | 'signature' 40 | ]) 41 | if (!commandValidation.valid) { 42 | return buildInvalidRequestMessage(commandValidation.reason) 43 | } 44 | const signatureValidation: CommonValidation = await validateAdminSignature( 45 | command.expiryTimestamp, 46 | command.signature, 47 | command.address 48 | ) 49 | if (!signatureValidation.valid) { 50 | return buildInvalidRequestMessage( 51 | `Signature check failed: ${signatureValidation.error}` 52 | ) 53 | } 54 | return { valid: true } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/3525.53072abba3ca74b8.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[3525],{33525:function(M,N,D){D.r(N),D.d(N,{default:function(){return T}});var T="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI4IiBoZWlnaHQ9IjI4IiBmaWxsPSIjM0I5OUZDIi8+CjxwYXRoIGQ9Ik04LjM4OTY5IDEwLjM3MzlDMTEuNDg4MiA3LjI3NTM4IDE2LjUxMTggNy4yNzUzOCAxOS42MTAzIDEwLjM3MzlMMTkuOTgzMiAxMC43NDY4QzIwLjEzODIgMTAuOTAxNyAyMC4xMzgyIDExLjE1MjkgMTkuOTgzMiAxMS4zMDc4TDE4LjcwNzYgMTIuNTgzNUMxOC42MzAxIDEyLjY2MDkgMTguNTA0NSAxMi42NjA5IDE4LjQyNzEgMTIuNTgzNUwxNy45MTM5IDEyLjA3MDNDMTUuNzUyMyA5LjkwODcgMTIuMjQ3NyA5LjkwODcgMTAuMDg2MSAxMi4wNzAzTDkuNTM2NTUgMTIuNjE5OEM5LjQ1OTA5IDEyLjY5NzMgOS4zMzM1IDEyLjY5NzMgOS4yNTYwNCAxMi42MTk4TDcuOTgwMzkgMTEuMzQ0MkM3LjgyNTQ3IDExLjE4OTMgNy44MjU0NyAxMC45MzgxIDcuOTgwMzkgMTAuNzgzMkw4LjM4OTY5IDEwLjM3MzlaTTIyLjI0ODUgMTMuMDEyTDIzLjM4MzggMTQuMTQ3NEMyMy41Mzg3IDE0LjMwMjMgMjMuNTM4NyAxNC41NTM1IDIzLjM4MzggMTQuNzA4NEwxOC4yNjQ1IDE5LjgyNzdDMTguMTA5NiAxOS45ODI3IDE3Ljg1ODQgMTkuOTgyNyAxNy43MDM1IDE5LjgyNzdDMTcuNzAzNSAxOS44Mjc3IDE3LjcwMzUgMTkuODI3NyAxNy43MDM1IDE5LjgyNzdMMTQuMDcwMiAxNi4xOTQ0QzE0LjAzMTQgMTYuMTU1NyAxMy45Njg2IDE2LjE1NTcgMTMuOTI5OSAxNi4xOTQ0QzEzLjkyOTkgMTYuMTk0NCAxMy45Mjk5IDE2LjE5NDQgMTMuOTI5OSAxNi4xOTQ0TDEwLjI5NjYgMTkuODI3N0MxMC4xNDE3IDE5Ljk4MjcgOS44OTA1MyAxOS45ODI3IDkuNzM1NjEgMTkuODI3OEM5LjczNTYgMTkuODI3OCA5LjczNTYgMTkuODI3NyA5LjczNTYgMTkuODI3N0w0LjYxNjE5IDE0LjcwODNDNC40NjEyNyAxNC41NTM0IDQuNDYxMjcgMTQuMzAyMiA0LjYxNjE5IDE0LjE0NzNMNS43NTE1MiAxMy4wMTJDNS45MDY0NSAxMi44NTcgNi4xNTc2MyAxMi44NTcgNi4zMTI1NSAxMy4wMTJMOS45NDU5NSAxNi42NDU0QzkuOTg0NjggMTYuNjg0MSAxMC4wNDc1IDE2LjY4NDEgMTAuMDg2MiAxNi42NDU0QzEwLjA4NjIgMTYuNjQ1NCAxMC4wODYyIDE2LjY0NTQgMTAuMDg2MiAxNi42NDU0TDEzLjcxOTQgMTMuMDEyQzEzLjg3NDMgMTIuODU3IDE0LjEyNTUgMTIuODU3IDE0LjI4MDUgMTMuMDEyQzE0LjI4MDUgMTMuMDEyIDE0LjI4MDUgMTMuMDEyIDE0LjI4MDUgMTMuMDEyTDE3LjkxMzkgMTYuNjQ1NEMxNy45NTI2IDE2LjY4NDEgMTguMDE1NCAxNi42ODQxIDE4LjA1NDEgMTYuNjQ1NEwyMS42ODc0IDEzLjAxMkMyMS44NDI0IDEyLjg1NzEgMjIuMDkzNiAxMi44NTcxIDIyLjI0ODUgMTMuMDEyWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg=="}}]); -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/9941.44044767831d9eb0.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[9941],{89941:function(M,I,j){j.r(I),j.d(I,{default:function(){return N}});var N="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3OCA3OCI+PGxpbmVhckdyYWRpZW50IGlkPSJhIiB4Mj0iMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgwIC01NC45NDQgLTU0Ljk0NCAwIDIzLjYyIDc5LjQ3NCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZjFiMmQiLz48c3RvcCBvZmZzZXQ9Ii4zIiBzdG9wLWNvbG9yPSIjZmYxYjJkIi8+PHN0b3Agb2Zmc2V0PSIuNjE0IiBzdG9wLWNvbG9yPSIjZmYxYjJkIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjYTcwMDE0Ii8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIHgyPSIxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDAgLTQ4LjU5NSAtNDguNTk1IDAgMzcuODU0IDc2LjIzNSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM5YzAwMDAiLz48c3RvcCBvZmZzZXQ9Ii43IiBzdG9wLWNvbG9yPSIjZmY0YjRiIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmY0YjRiIi8+PC9saW5lYXJHcmFkaWVudD48cGF0aCBmaWxsPSJ1cmwoI2EpIiBkPSJNMjguMzQ2IDgwLjM5OEMxMi42OTEgODAuMzk4IDAgNjcuNzA3IDAgNTIuMDUyIDAgMzYuODUgMTEuOTY4IDI0LjQ0MyAyNi45OTYgMjMuNzM5YTI4LjI0NCAyOC4yNDQgMCAwIDEgMjAuMjQxIDcuMThjLTMuMzIyLTIuMjAzLTcuMjA3LTMuNDctMTEuMzU5LTMuNDctNi43NSAwLTEyLjc5NiAzLjM0OC0xNi44NjIgOC42MjktMy4xMzQgMy43LTUuMTY0IDkuMTY5LTUuMzAyIDE1LjMwN3YxLjMzNWMuMTM4IDYuMTM3IDIuMTY4IDExLjYwOCA1LjMwMiAxNS4zMDcgNC4wNjYgNS4yOCAxMC4xMTIgOC42MyAxNi44NjIgOC42MyA0LjE1MiAwIDguMDM4LTEuMjY5IDExLjM2LTMuNDc0YTI4LjIzOSAyOC4yMzkgMCAwIDEtMTguNzg1IDcuMjE1bC0uMTA4LjAwMXoiIHRyYW5zZm9ybT0ibWF0cml4KDEuMzMzMyAwIDAgLTEuMzMzMyAwIDEwNy4yKSIvPjxwYXRoIGZpbGw9InVybCgjYikiIGQ9Ik0xOS4wMTYgNjguMDI1YzIuNjAxIDMuMDcgNS45NiA0LjkyMyA5LjYzMSA0LjkyMyA4LjI1MiAwIDE0Ljk0MS05LjM1NiAxNC45NDEtMjAuODk3cy02LjY5LTIwLjg5Ny0xNC45NDEtMjAuODk3Yy0zLjY3IDAtNy4wMyAxLjg1LTkuNjMgNC45MjIgNC4wNjYtNS4yODEgMTAuMTEtOC42MyAxNi44NjItOC42MyA0LjE1MiAwIDguMDM2IDEuMjY4IDExLjM1OSAzLjQ3MiA1LjgwMiA1LjE5IDkuNDU1IDEyLjczNSA5LjQ1NSAyMS4xMzMgMCA4LjM5Ny0zLjY1MyAxNS45NC05LjQ1MyAyMS4xMy0zLjMyNCAyLjIwNi03LjIwOSAzLjQ3My0xMS4zNjEgMy40NzMtNi43NSAwLTEyLjc5Ni0zLjM0OC0xNi44NjItOC42MyIgdHJhbnNmb3JtPSJtYXRyaXgoMS4zMzMzIDAgMCAtMS4zMzMzIDAgMTA3LjIpIi8+PC9zdmc+"}}]); -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Environmental Variables 2 | 3 | #check env.md file for examples and descriptions on each variable 4 | 5 | #----------------- REQUIRED -------------------------- 6 | #This is the only required/mandatory variable 7 | #Node will simply not run without this variable 8 | #All the other variables can remain blank (because they have defaults) or simply commented 9 | export PRIVATE_KEY=REPLACE_ME 10 | #----------------------------------------------------- 11 | 12 | ## core 13 | export RPCS= 14 | export DB_URL= 15 | export IPFS_GATEWAY=https://ipfs.io/ 16 | export ARWEAVE_GATEWAY=https://arweave.net/ 17 | export LOAD_INITIAL_DDOS= 18 | export FEE_TOKENS= 19 | export FEE_AMOUNT= 20 | export ADDRESS_FILE= 21 | export NODE_ENV= 22 | export AUTHORIZED_DECRYPTERS= 23 | export AUTHORIZED_DECRYPTERS_LIST= 24 | export OPERATOR_SERVICE_URL= 25 | export POLICY_SERVER_URL 26 | export INTERFACES= 27 | export ALLOWED_VALIDATORS= 28 | export ALLOWED_VALIDATORS_LIST= 29 | export AUTHORIZED_PUBLISHERS= 30 | export AUTHORIZED_PUBLISHERS_LIST= 31 | export INDEXER_INTERVAL= 32 | export ALLOWED_ADMINS= 33 | export ALLOWED_ADMINS_LIST= 34 | export CONTROL_PANEL=true 35 | export RATE_DENY_LIST= 36 | export MAX_REQ_PER_MINUTE= 37 | export MAX_CHECKSUM_LENGTH= 38 | export LOG_LEVEL= 39 | export HTTP_API_PORT= 40 | export VALIDATE_UNSIGNED_DDO= 41 | export JWT_SECRET= 42 | 43 | ## p2p 44 | 45 | export P2P_ENABLE_IPV4= 46 | export P2P_ENABLE_IPV6= 47 | export P2P_ipV4BindAddress= 48 | export P2P_ipV4BindTcpPort= 49 | export P2P_ipV4BindWsPort= 50 | export P2P_ipV6BindAddress= 51 | export P2P_ipV6BindTcpPort= 52 | export P2P_ipV6BindWsPort= 53 | export P2P_ANNOUNCE_ADDRESSES= 54 | export P2P_ANNOUNCE_PRIVATE= 55 | export P2P_pubsubPeerDiscoveryInterval= 56 | export P2P_dhtMaxInboundStreams= 57 | export P2P_dhtMaxOutboundStreams= 58 | export P2P_mDNSInterval= 59 | export P2P_connectionsMaxParallelDials= 60 | export P2P_connectionsDialTimeout= 61 | export P2P_ENABLE_UPNP= 62 | export P2P_ENABLE_AUTONAT= 63 | export P2P_ENABLE_CIRCUIT_RELAY_SERVER= 64 | export P2P_ENABLE_CIRCUIT_RELAY_CLIENT= 65 | export P2P_BOOTSTRAP_NODES= 66 | export P2P_FILTER_ANNOUNCED_ADDRESSES= 67 | 68 | ## compute 69 | export DOCKER_COMPUTE_ENVIRONMENTS= 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/utils/file.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArweaveFileObject, 3 | EncryptMethod, 4 | IpfsFileObject, 5 | UrlFileObject 6 | } from '../@types/fileObject.js' 7 | import { OceanNode } from '../OceanNode.js' 8 | import { FindDdoHandler } from '../components/core/handler/ddoHandler.js' 9 | import { AssetUtils } from './asset.js' 10 | import { decrypt } from './crypt.js' 11 | import { CORE_LOGGER } from './logging/common.js' 12 | import { sanitizeServiceFiles } from './util.js' 13 | import { isOrderingAllowedForAsset } from '../components/core/handler/downloadHandler.js' 14 | import { DDO, Service } from '@oceanprotocol/ddo-js' 15 | 16 | export async function getFile( 17 | didOrDdo: string | DDO, 18 | serviceId: string, 19 | node: OceanNode 20 | ): Promise { 21 | try { 22 | // 1. Get the DDO 23 | const ddo = 24 | typeof didOrDdo === 'string' 25 | ? ((await new FindDdoHandler(node).findAndFormatDdo(didOrDdo)) as DDO) 26 | : didOrDdo 27 | 28 | const isOrdable = isOrderingAllowedForAsset(ddo) 29 | if (!isOrdable.isOrdable) { 30 | CORE_LOGGER.error(isOrdable.reason) 31 | throw new Error(isOrdable.reason) 32 | } 33 | 34 | // 2. Get the service 35 | const service: Service = AssetUtils.getServiceById(ddo, serviceId) 36 | if (!service) { 37 | const msg = `Service with id ${serviceId} not found` 38 | CORE_LOGGER.error(msg) 39 | throw new Error(msg) 40 | } 41 | // 3. Decrypt the url 42 | const decryptedUrlBytes = await decrypt( 43 | Uint8Array.from(Buffer.from(sanitizeServiceFiles(service.files), 'hex')), 44 | EncryptMethod.ECIES 45 | ) 46 | CORE_LOGGER.logMessage(`URL decrypted for Service ID: ${serviceId}`) 47 | 48 | // Convert the decrypted bytes back to a string 49 | const decryptedFilesString = Buffer.from(decryptedUrlBytes).toString() 50 | const decryptedFileArray = JSON.parse(decryptedFilesString) 51 | return decryptedFileArray.files 52 | } catch (error) { 53 | const msg = 'Error occured while requesting the files: ' + error.message 54 | CORE_LOGGER.error(msg) 55 | throw new Error('Unable to decrypt files, files not served by the current node!') 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/database/TypesenseMetadataQuery.ts: -------------------------------------------------------------------------------- 1 | import { IMetadataQuery } from '../../@types/DDO/IMetadataQuery.js' 2 | import { SearchQuery } from '../../@types/DDO/SearchQuery.js' 3 | 4 | export class TypesenseMetadataQuery implements IMetadataQuery { 5 | buildQuery(searchQuery: SearchQuery): Record { 6 | const { query } = searchQuery 7 | 8 | if (this.isTypesenseQuery(searchQuery)) { 9 | return searchQuery 10 | } 11 | 12 | const typesenseQuery: Record = { 13 | q: '*', 14 | filter_by: '', 15 | num_hits: searchQuery.size || 10, 16 | start: searchQuery.from || 0 17 | } 18 | 19 | const filters: string[] = [] 20 | if (query.bool.filter) { 21 | query.bool.filter.forEach((filter: any) => { 22 | if (filter.term) { 23 | const field = Object.keys(filter.term)[0] 24 | const value = filter.term[field] 25 | filters.push(`${field}:=${value}`) 26 | } else if (filter.terms) { 27 | const field = Object.keys(filter.terms)[0] 28 | const values = filter.terms[field].join(',') 29 | filters.push(`${field}:=[${values}]`) 30 | } 31 | }) 32 | } 33 | 34 | if (query.bool && query.bool.must_not) { 35 | query.bool.must_not.forEach((mustNot: any) => { 36 | if (mustNot.term) { 37 | const field = Object.keys(mustNot.term)[0] 38 | const value = mustNot.term[field] 39 | filters.push(`${field}:!=${value}`) 40 | } else if (mustNot.terms) { 41 | const field = Object.keys(mustNot.terms)[0] 42 | const values = mustNot.terms[field].join(',') 43 | filters.push(`${field}:!=[${values}]`) 44 | } 45 | }) 46 | } 47 | 48 | if (filters.length > 0) { 49 | typesenseQuery.filter_by = filters.join(' && ') 50 | } 51 | 52 | if (searchQuery.sort) { 53 | typesenseQuery.sort_by = Object.entries(searchQuery.sort) 54 | .map(([field, direction]: [string, string]) => { 55 | return `${field}:${direction}` 56 | }) 57 | .join(',') 58 | } 59 | 60 | return typesenseQuery 61 | } 62 | 63 | private isTypesenseQuery(query: any): boolean { 64 | return query && query.filter_by !== undefined 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/httpRoutes/requestValidator.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { getConfiguration } from '../../utils/config.js' 3 | import { HTTP_LOGGER } from '../../utils/logging/common.js' 4 | import { OceanNodeConfig } from '../../@types/OceanNode.js' 5 | import { 6 | checkGlobalConnectionsRateLimit, 7 | checkRequestsRateLimit, 8 | CommonValidation 9 | } from '../../utils/validators.js' 10 | 11 | // Middleware to validate IP and apply rate limiting 12 | export const requestValidator = async function (req: Request, res: Response, next: any) { 13 | const now = Date.now() 14 | const requestIP = (req.headers['x-forwarded-for'] || 15 | req.socket.remoteAddress || 16 | '') as string 17 | 18 | const configuration = await getConfiguration() 19 | 20 | const ipValidation = await checkIP(requestIP, configuration) 21 | if (!ipValidation.valid) { 22 | HTTP_LOGGER.logMessage(`IP denied: ${ipValidation.error}`) 23 | return res.status(403).send(ipValidation.error) 24 | } 25 | 26 | const rateLimitCheck = checkRequestsRateLimit(requestIP, configuration, now) 27 | if (!rateLimitCheck.valid) { 28 | HTTP_LOGGER.logMessage( 29 | `Exceeded limit of requests per minute ${configuration.rateLimit}: ${rateLimitCheck.error}` 30 | ) 31 | return res.status(429).send(rateLimitCheck.error) 32 | } 33 | 34 | const connectionsRateValidation = checkGlobalConnectionsRateLimit(configuration, now) 35 | if (!connectionsRateValidation.valid) { 36 | res.status(403).send(connectionsRateValidation.error) 37 | return 38 | } 39 | 40 | next() 41 | } 42 | 43 | function checkIP( 44 | requestIP: string | string[], 45 | configuration: OceanNodeConfig 46 | ): CommonValidation { 47 | let onDenyList = false 48 | if (!Array.isArray(requestIP)) { 49 | onDenyList = configuration.denyList?.ips?.includes(requestIP) 50 | } else { 51 | for (const ip of requestIP) { 52 | if (configuration.denyList?.ips?.includes(ip)) { 53 | onDenyList = true 54 | break 55 | } 56 | } 57 | } 58 | 59 | if (onDenyList) { 60 | HTTP_LOGGER.error(`Incoming request denied to ip address: ${requestIP}`) 61 | } 62 | 63 | return { 64 | valid: !onDenyList, 65 | error: onDenyList ? 'Unauthorized request' : '' 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/httpRoutes/adminConfig.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { HTTP_LOGGER } from '../../utils/logging/common.js' 3 | import { FetchConfigHandler } from '../core/admin/fetchConfigHandler.js' 4 | import { PushConfigHandler } from '../core/admin/pushConfigHandler.js' 5 | import { PROTOCOL_COMMANDS } from '../../utils/constants.js' 6 | import { Readable } from 'stream' 7 | import { streamToObject } from '../../utils/util.js' 8 | 9 | export const adminConfigRoutes = express.Router() 10 | 11 | adminConfigRoutes.get('/api/admin/config', express.json(), async (req, res) => { 12 | try { 13 | const { expiryTimestamp, signature } = req.body 14 | 15 | const response = await new FetchConfigHandler(req.oceanNode).handle({ 16 | command: PROTOCOL_COMMANDS.FETCH_CONFIG, 17 | expiryTimestamp, 18 | signature 19 | }) 20 | 21 | if (response.status.httpStatus === 200) { 22 | const result = await streamToObject(response.stream as Readable) 23 | res.status(200).json(result) 24 | } else { 25 | HTTP_LOGGER.log('LEVEL_ERROR', `Error fetching config: ${response.status.error}`) 26 | res.status(response.status.httpStatus).json({ error: response.status.error }) 27 | } 28 | } catch (error) { 29 | HTTP_LOGGER.error(`Error fetching config: ${error.message}`) 30 | res.status(500).send(`Internal Server Error: ${error.message}`) 31 | } 32 | }) 33 | 34 | adminConfigRoutes.post('/api/admin/config/update', express.json(), async (req, res) => { 35 | try { 36 | const { expiryTimestamp, signature, config } = req.body 37 | 38 | const response = await new PushConfigHandler(req.oceanNode).handle({ 39 | command: PROTOCOL_COMMANDS.PUSH_CONFIG, 40 | expiryTimestamp, 41 | signature, 42 | config 43 | }) 44 | 45 | if (response.status.httpStatus === 200) { 46 | const result = await streamToObject(response.stream as Readable) 47 | res.status(200).json(result) 48 | } else { 49 | HTTP_LOGGER.log('LEVEL_ERROR', `Error pushing config: ${response.status.error}`) 50 | res.status(response.status.httpStatus).json({ error: response.status.error }) 51 | } 52 | } catch (error) { 53 | HTTP_LOGGER.error(`Error pushing config: ${error.message}`) 54 | res.status(500).send(`Internal Server Error: ${error.message}`) 55 | } 56 | }) 57 | -------------------------------------------------------------------------------- /src/components/core/admin/reindexChainHandler.ts: -------------------------------------------------------------------------------- 1 | import { AdminCommandHandler } from './adminHandler.js' 2 | import { AdminReindexChainCommand } from '../../../@types/commands.js' 3 | import { 4 | validateCommandParameters, 5 | ValidateParams, 6 | buildInvalidRequestMessage, 7 | buildInvalidParametersResponse, 8 | buildErrorResponse 9 | } from '../../httpRoutes/validateCommands.js' 10 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 11 | import { CORE_LOGGER } from '../../../utils/logging/common.js' 12 | import { checkSupportedChainId } from '../../../utils/blockchain.js' 13 | import { ReadableString } from '../../P2P/handleProtocolCommands.js' 14 | 15 | export class ReindexChainHandler extends AdminCommandHandler { 16 | async validateAdminCommand(command: AdminReindexChainCommand): Promise { 17 | if (!validateCommandParameters(command, ['chainId'])) { 18 | return buildInvalidRequestMessage( 19 | `Missing chainId field for command: "${command}".` 20 | ) 21 | } 22 | return await super.validate(command) 23 | } 24 | 25 | async handle(task: AdminReindexChainCommand): Promise { 26 | const validation = await this.validateAdminCommand(task) 27 | if (!validation.valid) { 28 | return buildInvalidParametersResponse(validation) 29 | } 30 | CORE_LOGGER.logMessage(`Reindexing chain command called`) 31 | const checkChainId = await checkSupportedChainId(task.chainId) 32 | if (!checkChainId.validation) { 33 | return buildErrorResponse( 34 | `Chain ID ${task.chainId} is not supported in the node's config` 35 | ) 36 | } 37 | try { 38 | const indexer = this.getOceanNode().getIndexer() 39 | if (!indexer) { 40 | return buildErrorResponse('Node is not running an indexer instance!') 41 | } 42 | 43 | const job = await indexer.resetCrawling(Number(task.chainId), task.block) 44 | if (job) { 45 | return { 46 | status: { httpStatus: 200 }, 47 | stream: new ReadableString(JSON.stringify(job)) 48 | } 49 | } 50 | return buildErrorResponse( 51 | `Unable to reset crawling, worker thread is not valid/running?` 52 | ) 53 | } catch (error) { 54 | CORE_LOGGER.error(`REINDEX chain: ${error.message}`) 55 | return buildErrorResponse(`REINDEX chain: ${error.message}`) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/@types/C2D/C2D_OPFK8.ts: -------------------------------------------------------------------------------- 1 | /* The following are specific to OPF_k8 compute engine */ 2 | export interface OPFK8ComputeStageInput { 3 | index: number 4 | id?: string 5 | remote?: any 6 | url?: string[] 7 | } 8 | export interface OPFK8ComputeStageAlgorithm { 9 | id?: string 10 | url?: string 11 | remote?: any 12 | rawcode?: string 13 | container?: { 14 | /** 15 | * The command to execute, or script to run inside the Docker image. 16 | * @type {string} 17 | */ 18 | entrypoint: string 19 | 20 | /** 21 | * Name of the Docker image. 22 | * @type {string} 23 | */ 24 | image: string 25 | 26 | /** 27 | * Tag of the Docker image. 28 | * @type {string} 29 | */ 30 | tag: string 31 | } 32 | } 33 | 34 | export interface OPFK8ComputeOutput { 35 | // this is a copy of ComputeOutput, but they could diverge in the future 36 | publishAlgorithmLog?: boolean 37 | publishOutput?: boolean 38 | providerAddress?: string 39 | providerUri?: string 40 | metadataUri?: string 41 | nodeUri?: string 42 | owner?: string 43 | secretStoreUri?: string 44 | whitelist?: string[] 45 | } 46 | export interface OPFK8ComputeStage { 47 | index: number 48 | input: OPFK8ComputeStageInput[] 49 | algorithm: OPFK8ComputeStageAlgorithm 50 | compute?: {} 51 | output: OPFK8ComputeOutput 52 | } 53 | 54 | export interface OPFK8ComputeWorkflow { 55 | stages: OPFK8ComputeStage[] 56 | } 57 | export interface OPFK8ComputeStart { 58 | workflow: OPFK8ComputeWorkflow 59 | owner: string 60 | agreementId: string 61 | providerSignature: string 62 | providerAddress: string 63 | environment: string 64 | validUntil: number 65 | nonce: number 66 | chainId: number 67 | } 68 | 69 | export interface OPFK8ComputeStop { 70 | jobId: string 71 | owner: string 72 | agreementId?: string 73 | providerSignature: string // message=owner+jobId 74 | providerAddress: string 75 | nonce: number 76 | } 77 | 78 | export interface OPFK8ComputeGetStatus { 79 | agreementId?: string 80 | jobId?: string 81 | owner?: string 82 | providerSignature: string // message=owner+jobId(if any) 83 | providerAddress: string 84 | nonce: number 85 | } 86 | 87 | export interface OPFK8ComputeGetResult { 88 | jobId: string 89 | owner: string 90 | index: number 91 | providerSignature: string // message=owner+jobId 92 | providerAddress: string 93 | nonce: number 94 | } 95 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/5119.33e08a0525159056.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[5119],{65119:function(I,N,M){M.r(N),M.d(N,{default:function(){return i}});var i="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOCIgaGVpZ2h0PSIyOCIgZmlsbD0ibm9uZSI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBmaWxsPSJ1cmwoI2IpIiBkPSJNLjk0MyAxMy43NTRjMCA3LjU4NiA1Ljk0NCAxMy43NTUgMTMuMjUyIDEzLjc1NSA3LjMwOCAwIDEzLjI1Mi02LjE3IDEzLjI1Mi0xMy43NTVDMjcuNDQgNi4xNyAyMS40OTcgMCAxNC4xOTUgMCA2Ljg4NyAwIC45NDMgNi4xNy45NDMgMTMuNzU0WiIvPjxwYXRoIGZpbGw9InVybCgjYykiIGQ9Ik0uOTQzIDEzLjc1NGMwIDcuNTg2IDUuOTQ0IDEzLjc1NSAxMy4yNTIgMTMuNzU1IDcuMzA4IDAgMTMuMjUyLTYuMTcgMTMuMjUyLTEzLjc1NUMyNy40NCA2LjE3IDIxLjQ5NyAwIDE0LjE5NSAwIDYuODg3IDAgLjk0MyA2LjE3Ljk0MyAxMy43NTRaIi8+PHBhdGggZmlsbD0idXJsKCNkKSIgZD0iTS45NDMgMTMuNzU0YzAgNy41ODYgNS45NDQgMTMuNzU1IDEzLjI1MiAxMy43NTUgNy4zMDggMCAxMy4yNTItNi4xNyAxMy4yNTItMTMuNzU1QzI3LjQ0IDYuMTcgMjEuNDk3IDAgMTQuMTk1IDAgNi44ODcgMCAuOTQzIDYuMTcuOTQzIDEzLjc1NFoiLz48L2c+PGRlZnM+PHJhZGlhbEdyYWRpZW50IGlkPSJiIiBjeD0iMCIgY3k9IjAiIHI9IjEiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMTkuOTU0NyAwIDAgMjAuNzExMyAxOC4xNiA2LjcpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIuMDA1IiBzdG9wLWNvbG9yPSIjZmZmIi8+PHN0b3Agb2Zmc2V0PSIuNDU4IiBzdG9wLWNvbG9yPSIjQjdEOEM4Ii8+PHN0b3Agb2Zmc2V0PSIuNjU2IiBzdG9wLWNvbG9yPSIjNkQ5NDg3Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNEI0QzNDIi8+PC9yYWRpYWxHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9ImMiIGN4PSIwIiBjeT0iMCIgcj0iMSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxOS45NTQ3IDAgMCAyMC43MTEzIDE4LjE2IDYuNykiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9Ii4wMDUiIHN0b3AtY29sb3I9IiNmZmYiLz48c3RvcCBvZmZzZXQ9Ii40NTgiIHN0b3AtY29sb3I9IiNCNUI0QzYiLz48c3RvcCBvZmZzZXQ9Ii42NTYiIHN0b3AtY29sb3I9IiM5QjhGOEYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM0QjRDM0MiLz48L3JhZGlhbEdyYWRpZW50PjxyYWRpYWxHcmFkaWVudCBpZD0iZCIgY3g9IjAiIGN5PSIwIiByPSIxIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDE5Ljk1NDcgMCAwIDIwLjcxMTMgMTguMTYgNi43KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iLjE1NiIgc3RvcC1jb2xvcj0iI0RDQzhEMCIvPjxzdG9wIG9mZnNldD0iLjMwMiIgc3RvcC1jb2xvcj0iIzc4QzhDRiIvPjxzdG9wIG9mZnNldD0iLjQyNyIgc3RvcC1jb2xvcj0iIzREOTU5RSIvPjxzdG9wIG9mZnNldD0iLjU1NyIgc3RvcC1jb2xvcj0iIzMwNUVCOSIvPjxzdG9wIG9mZnNldD0iLjc5NyIgc3RvcC1jb2xvcj0iIzMxMUYxMiIvPjxzdG9wIG9mZnNldD0iLjkwNiIgc3RvcC1jb2xvcj0iIzY4NDIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzJEMUMxMyIvPjwvcmFkaWFsR3JhZGllbnQ+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDI4djI4SDB6Ii8+PC9jbGlwUGF0aD48L2RlZnM+PC9zdmc+"}}]); -------------------------------------------------------------------------------- /src/test/unit/indexer/version.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai' 2 | import { describe, it } from 'mocha' 3 | import { 4 | compareVersions, 5 | isReindexingNeeded 6 | } from '../../../components/Indexer/version.js' 7 | 8 | describe('Version utilities', () => { 9 | describe('compareVersions', () => { 10 | it('should return 0 for equal versions', () => { 11 | expect(compareVersions('1.0.0', '1.0.0')).to.equal(0) 12 | expect(compareVersions('2.3.1', '2.3.1')).to.equal(0) 13 | }) 14 | 15 | it('should return -1 when first version is less than second', () => { 16 | expect(compareVersions('1.0.0', '1.0.1')).to.equal(-1) 17 | expect(compareVersions('1.9.9', '2.0.0')).to.equal(-1) 18 | expect(compareVersions('2.3.0', '2.3.1')).to.equal(-1) 19 | expect(compareVersions('0.2.1', '0.2.2')).to.equal(-1) 20 | }) 21 | 22 | it('should return 1 when first version is greater than second', () => { 23 | expect(compareVersions('1.0.1', '1.0.0')).to.equal(1) 24 | expect(compareVersions('2.0.0', '1.9.9')).to.equal(1) 25 | expect(compareVersions('2.3.1', '2.3.0')).to.equal(1) 26 | }) 27 | 28 | it('should handle versions with different number of segments', () => { 29 | expect(compareVersions('1.0', '1.0.0')).to.equal(0) 30 | expect(compareVersions('1.0.0.0', '1.0.0')).to.equal(0) 31 | expect(compareVersions('1.0', '1.0.1')).to.equal(-1) 32 | expect(compareVersions('1.1', '1.0.9')).to.equal(1) 33 | }) 34 | }) 35 | 36 | describe('isReindexingNeeded', () => { 37 | it('should return true if dbVersion is null', () => { 38 | assert(isReindexingNeeded('1.0.0', null, '0.9.0') === true) 39 | }) 40 | 41 | it('should return true if dbVersion is less than minVersion', () => { 42 | assert(isReindexingNeeded('1.0.0', '0.1.0', '0.2.0') === true) 43 | assert(isReindexingNeeded('0.3.0', '0.2.1', '0.2.2') === true) 44 | }) 45 | 46 | it('should return false if dbVersion is equal to minVersion', () => { 47 | assert(isReindexingNeeded('1.0.0', '0.2.0', '0.2.0') === false) 48 | }) 49 | 50 | it('should return false if dbVersion is greater than minVersion', () => { 51 | assert(isReindexingNeeded('1.0.0', '0.3.0', '0.2.0') === false) 52 | }) 53 | 54 | it('should throw error if currentVersion is less than minVersion', () => { 55 | expect(() => isReindexingNeeded('0.1.0', '0.2.0', '0.2.0')).to.throw( 56 | 'Current version 0.1.0 is less than minimum required version 0.2.0' 57 | ) 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /src/components/core/admin/reindexTxHandler.ts: -------------------------------------------------------------------------------- 1 | import { AdminCommandHandler } from './adminHandler.js' 2 | import { 3 | validateCommandParameters, 4 | buildInvalidRequestMessage, 5 | buildInvalidParametersResponse, 6 | buildErrorResponse, 7 | ValidateParams 8 | } from '../../httpRoutes/validateCommands.js' 9 | import { AdminReindexTxCommand } from '../../../@types/commands.js' 10 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 11 | import { CORE_LOGGER } from '../../../utils/logging/common.js' 12 | import { ReadableString } from '../../P2P/handleProtocolCommands.js' 13 | import { checkSupportedChainId } from '../../../utils/blockchain.js' 14 | 15 | export class ReindexTxHandler extends AdminCommandHandler { 16 | async validate(command: AdminReindexTxCommand): Promise { 17 | if (!validateCommandParameters(command, ['chainId', 'txId'])) { 18 | return buildInvalidRequestMessage( 19 | `Missing chainId or txId fields for command: "${command}".` 20 | ) 21 | } 22 | if (!/^0x([A-Fa-f0-9]{64})$/.test(command.txId)) { 23 | return buildInvalidRequestMessage(`Invalid format for transaction ID.`) 24 | } 25 | return await super.validate(command) 26 | } 27 | 28 | async handle(task: AdminReindexTxCommand): Promise { 29 | const validation = await this.validate(task) 30 | if (!validation.valid) { 31 | return buildInvalidParametersResponse(validation) 32 | } 33 | CORE_LOGGER.logMessage(`Reindexing tx...`) 34 | const checkChainId = await checkSupportedChainId(task.chainId) 35 | if (!checkChainId.validation) { 36 | return buildErrorResponse( 37 | `Chain ID ${task.chainId} is not supported in the node's config` 38 | ) 39 | } 40 | try { 41 | const indexer = this.getOceanNode().getIndexer() 42 | if (!indexer) { 43 | return buildErrorResponse('Node is not running an indexer instance!') 44 | } 45 | const job = indexer.addReindexTask({ 46 | txId: task.txId, 47 | chainId: task.chainId 48 | }) 49 | 50 | if (job) { 51 | return { 52 | status: { httpStatus: 200 }, 53 | stream: new ReadableString(JSON.stringify(job)) 54 | } 55 | } 56 | return buildErrorResponse( 57 | `Unable to reindex tx ${task.txId}, worker thread is not valid/running?` 58 | ) 59 | } catch (error) { 60 | CORE_LOGGER.error(`REINDEX tx: ${error.message}`) 61 | return buildErrorResponse(`REINDEX tx: ${error.message} `) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/core/handler/queryHandler.ts: -------------------------------------------------------------------------------- 1 | import { CommandHandler } from './handler.js' 2 | import { QueryCommand } from '../../../@types/commands.js' 3 | import { P2PCommandResponse } from '../../../@types/OceanNode.js' 4 | import { Readable } from 'stream' 5 | import { 6 | ValidateParams, 7 | buildInvalidParametersResponse, 8 | validateCommandParameters 9 | } from '../../httpRoutes/validateCommands.js' 10 | import { CORE_LOGGER } from '../../../utils/logging/common.js' 11 | 12 | export class QueryHandler extends CommandHandler { 13 | validate(command: QueryCommand): ValidateParams { 14 | return validateCommandParameters(command, ['query']) 15 | } 16 | 17 | async handle(task: QueryCommand): Promise { 18 | const validationResponse = await this.verifyParamsAndRateLimits(task) 19 | if (this.shouldDenyTaskHandling(validationResponse)) { 20 | return validationResponse 21 | } 22 | try { 23 | let result = await this.getOceanNode().getDatabase().ddo.search(task.query) 24 | if (!result) { 25 | result = [] 26 | } 27 | return { 28 | stream: Readable.from(JSON.stringify(result)), 29 | status: { httpStatus: 200 } 30 | } 31 | } catch (error) { 32 | CORE_LOGGER.error(`Error in QueryHandler: ${error.message}`) 33 | return { 34 | stream: null, 35 | status: { httpStatus: 500, error: 'Unknown error: ' + error.message } 36 | } 37 | } 38 | } 39 | } 40 | 41 | export class QueryDdoStateHandler extends QueryHandler { 42 | async handle(task: QueryCommand): Promise { 43 | const validation = this.validate(task) 44 | if (!validation.valid) { 45 | return buildInvalidParametersResponse(validation) 46 | } 47 | try { 48 | const result = await this.getOceanNode().getDatabase().ddoState.search(task.query) 49 | 50 | CORE_LOGGER.debug(`DDO State search result: ${JSON.stringify(result)}`) 51 | 52 | if (result === null) { 53 | CORE_LOGGER.error('Database search returned null') 54 | return { 55 | stream: null, 56 | status: { httpStatus: 500, error: 'Database search failed' } 57 | } 58 | } 59 | 60 | return { 61 | stream: Readable.from(JSON.stringify(result)), 62 | status: { httpStatus: 200 } 63 | } 64 | } catch (error) { 65 | CORE_LOGGER.error(`Error in QueryDdoStateHandler: ${error.message}`) 66 | return { 67 | stream: null, 68 | status: { httpStatus: 500, error: 'Unknown error: ' + error.message } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /controlpanel/src/components/NodePeers/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import styles from './style.module.css' 3 | import Spinner from '../Spinner' 4 | import Copy from '../Copy' 5 | import { Button, Typography } from '@mui/material' 6 | 7 | export default function NodePeers() { 8 | const [nodePeers, setNodePeers] = useState([]) 9 | const [isLoadingNodePeers, setLoadingNodePeers] = useState(true) 10 | const [showAll, setShowAll] = useState(false) 11 | 12 | const fetchNodePeers = async () => { 13 | setLoadingNodePeers(true) 14 | try { 15 | const apiNodePeers = '/getOceanPeers' 16 | const res = await fetch(apiNodePeers, { 17 | headers: { 18 | Accept: 'application/json', 19 | 'Content-Type': 'application/json' 20 | }, 21 | method: 'GET' 22 | }) 23 | const data = await res.json() 24 | setNodePeers(data) 25 | } catch (error) { 26 | console.error('error', error) 27 | } finally { 28 | setLoadingNodePeers(false) 29 | } 30 | } 31 | 32 | useEffect(() => { 33 | fetchNodePeers() 34 | 35 | const intervalId = setInterval(() => { 36 | fetchNodePeers() 37 | }, 120000) // 2 minutes 38 | 39 | return () => clearInterval(intervalId) 40 | }, []) 41 | 42 | // Determine the nodes to display 43 | const displayedNodePeers = showAll ? nodePeers : nodePeers.slice(0, 10) 44 | 45 | return ( 46 |
47 |
Connected Nodes (Total {nodePeers.length})
48 | 49 | {isLoadingNodePeers ? ( 50 |
51 | 52 |
53 | ) : ( 54 | <> 55 | {nodePeers.length > 0 ? ( 56 | displayedNodePeers.map((address) => ( 57 |
58 | {address} 59 |
60 | )) 61 | ) : ( 62 | There are no nodes connected 63 | )} 64 | 65 | {!showAll && nodePeers.length > 10 && ( 66 | 69 | )} 70 | {showAll && nodePeers.length > 10 && ( 71 | 74 | )} 75 | 76 | )} 77 |
78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /src/utils/address.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import addresses from '@oceanprotocol/contracts/addresses/address.json' assert { type: 'json' } 3 | import { CORE_LOGGER } from './logging/common.js' 4 | import { isDefined } from './index.js' 5 | 6 | /** 7 | * Get the artifacts address from the address.json file 8 | * either from the env or from the ocean-contracts dir 9 | * @returns data or null 10 | */ 11 | export function getOceanArtifactsAdresses(): any { 12 | try { 13 | if (isDefined(process.env.ADDRESS_FILE)) { 14 | // eslint-disable-next-line security/detect-non-literal-fs-filename 15 | const data = fs.readFileSync(process.env.ADDRESS_FILE, 'utf8') 16 | return JSON.parse(data) 17 | } 18 | return addresses 19 | } catch (error) { 20 | CORE_LOGGER.error(error) 21 | return addresses 22 | } 23 | } 24 | 25 | /** 26 | * Get the artifacts address from the address.json file, for the given chain 27 | * either from the env or from the ocean-contracts dir, safer than above, because sometimes the network name 28 | * is mispeled, best example "optimism_sepolia" vs "optimism-sepolia" 29 | * @returns data or null 30 | */ 31 | export function getOceanArtifactsAdressesByChainId(chain: number): any { 32 | try { 33 | // eslint-disable-next-line security/detect-non-literal-fs-filename 34 | const data = getOceanArtifactsAdresses() 35 | if (data) { 36 | const networks = Object.keys(data) 37 | for (const network of networks) { 38 | if (data[network].chainId === chain) { 39 | return data[network] 40 | } 41 | } 42 | } 43 | // just warn about this missing configuration if running locally 44 | if (chain === DEVELOPMENT_CHAIN_ID && !isDefined(process.env.ADDRESS_FILE)) { 45 | CORE_LOGGER.warn( 46 | 'Cannot find contract artifacts addresses for "development" chain. Please set the "ADDRESS_FILE" environmental variable!' 47 | ) 48 | } 49 | } catch (error) { 50 | CORE_LOGGER.error(error) 51 | } 52 | return null 53 | } 54 | 55 | // eslint-disable-next-line require-await 56 | export function getOceanTokenAddressForChain(chainId: number): Promise { 57 | const addresses = getOceanArtifactsAdressesByChainId(chainId) 58 | if (addresses && addresses.Ocean) return addresses.Ocean 59 | return null 60 | } 61 | 62 | // default token addresses per chain 63 | export const OCEAN_ARTIFACTS_ADDRESSES_PER_CHAIN = addresses 64 | export const DEVELOPMENT_CHAIN_ID = 8996 65 | 66 | export const KNOWN_CONFIDENTIAL_EVMS = [ 67 | BigInt(23294), // mainnet oasis_sapphire, 68 | BigInt(23295) // oasis_sapphire_testnet 69 | ] 70 | -------------------------------------------------------------------------------- /src/components/core/admin/IndexingThreadHandler.ts: -------------------------------------------------------------------------------- 1 | import { P2PCommandResponse } from '../../../@types/index.js' 2 | import { IndexingCommand, StartStopIndexingCommand } from '../../../@types/commands.js' 3 | import { ReadableString } from '../../P2P/handleProtocolCommands.js' 4 | import { 5 | buildErrorResponse, 6 | buildInvalidParametersResponse, 7 | buildInvalidRequestMessage, 8 | validateCommandParameters, 9 | ValidateParams 10 | } from '../../httpRoutes/validateCommands.js' 11 | import { AdminCommandHandler } from './adminHandler.js' 12 | import { checkSupportedChainId } from '../../../utils/blockchain.js' 13 | 14 | export class IndexingThreadHandler extends AdminCommandHandler { 15 | async validateAdminCommand(command: StartStopIndexingCommand): Promise { 16 | if ( 17 | !validateCommandParameters(command, ['action']) || 18 | ![IndexingCommand.START_THREAD, IndexingCommand.STOP_THREAD].includes( 19 | command.action 20 | ) || 21 | (command.chainId && !checkSupportedChainId(command.chainId)) 22 | ) { 23 | return buildInvalidRequestMessage( 24 | `Missing or invalid "action" and/or "chainId" fields for command: "${command}".` 25 | ) 26 | } 27 | return await super.validate(command) 28 | } 29 | 30 | // eslint-disable-next-line require-await 31 | async handle(task: StartStopIndexingCommand): Promise { 32 | const validation = await this.validateAdminCommand(task) 33 | if (!validation.valid) { 34 | return buildInvalidParametersResponse(validation) 35 | } 36 | const indexer = this.getOceanNode().getIndexer() 37 | if (!indexer) { 38 | return buildErrorResponse('Node is not running an indexer instance!') 39 | } 40 | if (task.action === IndexingCommand.START_THREAD) { 41 | const output = task.chainId 42 | ? indexer.startThread(task.chainId) 43 | : indexer.startThreads() 44 | return { 45 | status: { 46 | httpStatus: output ? 200 : 400, 47 | error: output ? null : 'Unable to start indexing thread(s)!' 48 | }, 49 | stream: output ? new ReadableString('OK') : null 50 | } 51 | } else if (task.action === IndexingCommand.STOP_THREAD) { 52 | const output = task.chainId 53 | ? indexer.stopThread(task.chainId) 54 | : indexer.stopAllThreads() 55 | return { 56 | status: { 57 | httpStatus: output ? 200 : 400, 58 | error: output ? null : 'Unable to stop indexing thread(s)!' 59 | }, 60 | stream: output ? new ReadableString('OK') : null 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/httpRoutes/auth.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { SERVICES_API_BASE_PATH, PROTOCOL_COMMANDS } from '../../utils/constants.js' 3 | import { HTTP_LOGGER } from '../../utils/logging/common.js' 4 | import { 5 | CreateAuthTokenHandler, 6 | InvalidateAuthTokenHandler 7 | } from '../core/handler/authHandler.js' 8 | import { streamToString } from '../../utils/util.js' 9 | import { Readable } from 'stream' 10 | 11 | export const authRoutes = express.Router() 12 | 13 | authRoutes.post( 14 | `${SERVICES_API_BASE_PATH}/auth/token`, 15 | express.json(), 16 | async (req, res) => { 17 | try { 18 | const { signature, address, nonce, validUntil } = req.body 19 | 20 | if (!signature || !address) { 21 | return res.status(400).json({ error: 'Missing required parameters' }) 22 | } 23 | 24 | const response = await new CreateAuthTokenHandler(req.oceanNode).handle({ 25 | command: PROTOCOL_COMMANDS.CREATE_AUTH_TOKEN, 26 | signature, 27 | address, 28 | nonce, 29 | validUntil 30 | }) 31 | 32 | if (response.status.error) { 33 | return res 34 | .status(response.status.httpStatus) 35 | .json({ error: response.status.error }) 36 | } 37 | 38 | const result = JSON.parse(await streamToString(response.stream as Readable)) 39 | res.json(result) 40 | } catch (error) { 41 | HTTP_LOGGER.error(`Error creating auth token: ${error}`) 42 | res.status(500).json({ error: 'Internal server error' }) 43 | } 44 | } 45 | ) 46 | 47 | authRoutes.post( 48 | `${SERVICES_API_BASE_PATH}/auth/token/invalidate`, 49 | express.json(), 50 | async (req, res) => { 51 | try { 52 | const { signature, address, nonce, token } = req.body 53 | 54 | if (!signature || !address || !token) { 55 | return res.status(400).json({ error: 'Missing required parameters' }) 56 | } 57 | 58 | const response = await new InvalidateAuthTokenHandler(req.oceanNode).handle({ 59 | command: PROTOCOL_COMMANDS.INVALIDATE_AUTH_TOKEN, 60 | signature, 61 | address, 62 | nonce, 63 | token 64 | }) 65 | 66 | if (response.status.error) { 67 | return res 68 | .status(response.status.httpStatus) 69 | .json({ error: response.status.error }) 70 | } 71 | 72 | const result = JSON.parse(await streamToString(response.stream as Readable)) 73 | res.json(result) 74 | } catch (error) { 75 | HTTP_LOGGER.error(`Error invalidating auth token: ${error}`) 76 | res.status(500).json({ error: 'Internal server error' }) 77 | } 78 | } 79 | ) 80 | -------------------------------------------------------------------------------- /src/test/integration/elasticsearch.test.ts: -------------------------------------------------------------------------------- 1 | import { ddo } from '../data/ddo.js' 2 | import { expect } from 'chai' 3 | import { Database } from '../../components/database/index.js' 4 | import { 5 | ElasticsearchDdoDatabase, 6 | ElasticsearchDdoStateDatabase, 7 | ElasticsearchIndexerDatabase, 8 | ElasticsearchLogDatabase, 9 | ElasticsearchOrderDatabase 10 | } from '../../components/database/ElasticSearchDatabase.js' 11 | import { DB_TYPES } from '../../utils/index.js' 12 | import { SQLLiteNonceDatabase } from '../../components/database/SQLLiteNonceDatabase.js' 13 | 14 | const dbConfig = { 15 | url: 'http://localhost:9200', 16 | dbType: DB_TYPES.ELASTIC_SEARCH 17 | } 18 | const elasticsearch: Database = await Database.init(dbConfig) 19 | 20 | describe('Elastic Search', () => { 21 | it('Get instances of Elastic Search', () => { 22 | expect(elasticsearch.ddo).to.be.instanceOf(ElasticsearchDdoDatabase) 23 | expect(elasticsearch.indexer).to.be.instanceOf(ElasticsearchIndexerDatabase) 24 | expect(elasticsearch.ddoState).to.be.instanceOf(ElasticsearchDdoStateDatabase) 25 | expect(elasticsearch.order).to.be.instanceOf(ElasticsearchOrderDatabase) 26 | expect(elasticsearch.nonce).to.be.instanceOf(SQLLiteNonceDatabase) 27 | expect(elasticsearch.logs).to.be.instanceOf(ElasticsearchLogDatabase) 28 | }) 29 | }) 30 | 31 | describe('Elastic Search DDO collections', () => { 32 | it('create document in ddo collection', async () => { 33 | const result = await elasticsearch.ddo.create(ddo) 34 | expect(result.result).to.equal('created') 35 | expect(result._id).to.equal(ddo.id) 36 | // expect(result.metadata).to.not.be.an('undefined') 37 | // expect(result.metadata.name).to.be.equal(ddo.metadata.name) 38 | }) 39 | 40 | it('retrieve document in ddo collection', async () => { 41 | const result = await elasticsearch.ddo.retrieve(ddo.id) 42 | expect(result.id).to.equal(ddo.id) 43 | expect(result.metadata).to.not.be.an('undefined') 44 | expect(result.metadata.name).to.be.equal(ddo.metadata.name) 45 | }) 46 | 47 | it('update document in ddo collection', async () => { 48 | const newMetadataName = 'new metadata name' 49 | const updatedData = ddo 50 | updatedData.metadata.name = newMetadataName 51 | const result = await elasticsearch.ddo.update(updatedData) 52 | expect(result.result).to.equal('updated') 53 | expect(result._id).to.equal(updatedData.id) 54 | }) 55 | 56 | it('delete document in ddo collection', async () => { 57 | const result = await elasticsearch.ddo.delete(ddo.id) 58 | expect(result.result).to.equal('deleted') 59 | expect(result._id).to.equal(ddo.id) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /src/test/unit/auth/token.test.ts: -------------------------------------------------------------------------------- 1 | import { OceanNodeConfig } from '../../../@types/OceanNode.js' 2 | import { getConfiguration } from '../../../utils/index.js' 3 | import { expect } from 'chai' 4 | import { Wallet } from 'ethers' 5 | import { Auth } from '../../../components/Auth/index.js' 6 | import { AuthTokenDatabase } from '../../../components/database/AuthTokenDatabase.js' 7 | 8 | describe('Auth Token Tests', () => { 9 | let wallet: Wallet 10 | let authTokenDatabase: AuthTokenDatabase 11 | let config: OceanNodeConfig 12 | let auth: Auth 13 | 14 | before(async () => { 15 | config = await getConfiguration(true) 16 | authTokenDatabase = await AuthTokenDatabase.create(config.dbConfig) 17 | wallet = new Wallet(process.env.PRIVATE_KEY) 18 | auth = new Auth(authTokenDatabase) 19 | }) 20 | 21 | const getRandomNonce = () => { 22 | return Math.floor(Math.random() * 1000000).toString() 23 | } 24 | 25 | it('should create and validate a token', async () => { 26 | const jwtToken = await auth.getJWTToken(wallet.address, getRandomNonce(), Date.now()) 27 | await auth.insertToken(wallet.address, jwtToken, Date.now() + 1000, Date.now()) 28 | 29 | const result = await auth.validateAuthenticationOrToken({ token: jwtToken }) 30 | expect(result.valid).to.be.equal(true) 31 | }) 32 | 33 | it('should fail validation with invalid token', async () => { 34 | const result = await auth.validateAuthenticationOrToken({ token: 'invalid-token' }) 35 | expect(result.valid).to.be.equal(false) 36 | }) 37 | 38 | it('should fail validation with invalid signature', async () => { 39 | const invalidSignature = '0x' + '0'.repeat(130) 40 | 41 | const result = await auth.validateAuthenticationOrToken({ 42 | signature: invalidSignature, 43 | nonce: getRandomNonce(), 44 | address: wallet.address 45 | }) 46 | expect(result.valid).to.be.equal(false) 47 | }) 48 | 49 | it('should respect token expiry', async () => { 50 | const jwtToken = await auth.getJWTToken(wallet.address, getRandomNonce(), Date.now()) 51 | await auth.insertToken(wallet.address, jwtToken, Date.now() + 1000, Date.now()) 52 | 53 | await new Promise((resolve) => setTimeout(resolve, 1500)) 54 | 55 | const validationResult = await auth.validateToken(jwtToken) 56 | expect(validationResult).to.be.equal(null) 57 | }) 58 | 59 | it('should invalidate a token', async () => { 60 | const jwtToken = await auth.getJWTToken(wallet.address, getRandomNonce(), Date.now()) 61 | await auth.insertToken(wallet.address, jwtToken, Date.now() + 1000, Date.now()) 62 | 63 | await auth.invalidateToken(jwtToken) 64 | 65 | const validationResult = await auth.validateToken(jwtToken) 66 | expect(validationResult).to.be.equal(null) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /src/test/integration/nonce.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, assert } from 'chai' 2 | import { ethers, ZeroAddress } from 'ethers' 3 | import { nonceSchema } from '../data/nonceSchema.js' 4 | import { Typesense, convertTypesenseConfig } from '../../components/database/typesense.js' 5 | 6 | describe('handle nonce', () => { 7 | let typesense: Typesense 8 | let error: Error 9 | 10 | before(() => { 11 | const url = 'http://localhost:8108/?apiKey=xyz' 12 | typesense = new Typesense(convertTypesenseConfig(url)) 13 | }) 14 | 15 | it('instance Typesense', () => { 16 | expect(typesense).to.be.instanceOf(Typesense) 17 | }) 18 | 19 | it('create nonce collection', async () => { 20 | let result 21 | try { 22 | result = await typesense.collections(nonceSchema.name).retrieve() 23 | } catch (error) { 24 | result = await typesense.collections().create(nonceSchema) 25 | } 26 | expect(result.enable_nested_fields).to.equal(true) 27 | expect(result.fields).to.not.be.an('undefined') 28 | expect(result.name).to.be.equal(nonceSchema.name) 29 | assert(result.num_documents >= 0, 'num_documents is not a valid number') 30 | }) 31 | 32 | it('should validate signature', async () => { 33 | try { 34 | await typesense 35 | .collections(nonceSchema.name) 36 | .documents() 37 | .retrieve('0x4cc9DBfc4bEeA8c986c61DAABB350C2eC55e29d1') 38 | // if not, create it now 39 | } catch (ex) { 40 | await typesense.collections(nonceSchema.name).documents().create({ 41 | id: '0x4cc9DBfc4bEeA8c986c61DAABB350C2eC55e29d1', 42 | nonce: 1 43 | }) 44 | } 45 | const wallet = new ethers.Wallet( 46 | '0xbee525d70c715bee6ca15ea5113e544d13cc1bb2817e07113d0af7755ddb6391' 47 | ) 48 | // message to sign 49 | const nonce = '1' 50 | const expectedAddress = await wallet.getAddress() 51 | // '0x8F292046bb73595A978F4e7A131b4EBd03A15e8a' 52 | // sign message/nonce 53 | const signature = await wallet.signMessage(nonce) 54 | const actualAddress = ethers.verifyMessage(nonce, signature) 55 | expect(actualAddress).to.be.equal(expectedAddress) 56 | }) 57 | 58 | it('should get nonce (1)', async () => { 59 | const document = await typesense 60 | .collections(nonceSchema.name) 61 | .documents() 62 | .retrieve('0x4cc9DBfc4bEeA8c986c61DAABB350C2eC55e29d1') 63 | expect(document.nonce).to.be.equal(1) 64 | }) 65 | 66 | it('should throw error for retrieving unexistent address', async () => { 67 | try { 68 | await typesense.collections(nonceSchema.name).documents().retrieve(ZeroAddress) 69 | } catch (err) { 70 | error = err 71 | } 72 | expect(error.message).to.eql( 73 | 'Could not find a document with id: 0x0000000000000000000000000000000000000000' 74 | ) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /src/components/httpRoutes/policyServer.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from 'express' 2 | import { 3 | PolicyServerPassthroughHandler, 4 | PolicyServerInitializeHandler 5 | } from '../core/handler/policyServer.js' 6 | import { HTTP_LOGGER } from '../../utils/logging/common.js' 7 | import { PROTOCOL_COMMANDS, SERVICES_API_BASE_PATH } from '../../utils/constants.js' 8 | 9 | export const PolicyServerPassthroughRoute = express.Router() 10 | PolicyServerPassthroughRoute.use(express.json()) // Ensure JSON parsing middleware is used 11 | 12 | PolicyServerPassthroughRoute.post( 13 | `${SERVICES_API_BASE_PATH}/PolicyServerPassthrough`, 14 | express.urlencoded({ extended: true, type: '*/*' }), 15 | async (req: Request, res: Response): Promise => { 16 | HTTP_LOGGER.logMessage( 17 | `PolicyServerPassthroughRoute request received: ${JSON.stringify(req.body)}`, 18 | true 19 | ) 20 | try { 21 | const response = await new PolicyServerPassthroughHandler(req.oceanNode).handle({ 22 | command: PROTOCOL_COMMANDS.POLICY_SERVER_PASSTHROUGH, 23 | policyServerPassthrough: req.body.policyServerPassthrough 24 | }) 25 | if (response.stream) { 26 | res.status(response.status.httpStatus) 27 | res.set(response.status.headers) 28 | response.stream.pipe(res) 29 | } else { 30 | HTTP_LOGGER.error(response.status.error) 31 | res.status(response.status.httpStatus).send(response.status.error) 32 | } 33 | } catch (error) { 34 | HTTP_LOGGER.error(error.message) 35 | res.status(500).send(error) 36 | } 37 | // res.sendStatus(200) 38 | } 39 | ) 40 | 41 | PolicyServerPassthroughRoute.post( 42 | `${SERVICES_API_BASE_PATH}/initializePSVerification`, 43 | express.urlencoded({ extended: true, type: '*/*' }), 44 | async (req: Request, res: Response): Promise => { 45 | HTTP_LOGGER.logMessage( 46 | `initializePSVerificationRoute request received: ${JSON.stringify(req.body)}`, 47 | true 48 | ) 49 | try { 50 | const response = await new PolicyServerInitializeHandler(req.oceanNode).handle({ 51 | command: PROTOCOL_COMMANDS.POLICY_SERVER_PASSTHROUGH, 52 | documentId: req.body.documentId, 53 | serviceId: req.body.serviceId, 54 | consumerAddress: req.body.consumerAddress, 55 | policyServer: req.body.policyServer 56 | }) 57 | if (response.stream) { 58 | res.status(response.status.httpStatus) 59 | res.set(response.status.headers) 60 | response.stream.pipe(res) 61 | } else { 62 | HTTP_LOGGER.error(response.status.error) 63 | res.status(response.status.httpStatus).send(response.status.error) 64 | } 65 | } catch (error) { 66 | HTTP_LOGGER.error(error.message) 67 | res.status(500).send(error) 68 | } 69 | // res.sendStatus(200) 70 | } 71 | ) 72 | -------------------------------------------------------------------------------- /src/components/httpRoutes/index.ts: -------------------------------------------------------------------------------- 1 | import express, { Response } from 'express' 2 | import { p2pRoutes } from './getOceanPeers.js' 3 | import { getProvidersForStringRoute, getProvidersForStringsRoute } from './dids.js' 4 | import { directCommandRoute } from './commands.js' 5 | import { logRoutes } from './logs.js' 6 | import { providerRoutes } from './provider.js' 7 | import { aquariusRoutes } from './aquarius.js' 8 | import { rootEndpointRoutes } from './rootEndpoint.js' 9 | import { fileInfoRoute } from './fileInfo.js' 10 | import { computeRoutes } from './compute.js' 11 | import { queueRoutes } from './queue.js' 12 | // import { getConfiguration } from '../../utils/config.js' 13 | import { jobsRoutes } from './jobs.js' 14 | import { addMapping, allRoutesMapping, findPathName } from './routeUtils.js' 15 | import { PolicyServerPassthroughRoute } from './policyServer.js' 16 | import { authRoutes } from './auth.js' 17 | import { adminConfigRoutes } from './adminConfig.js' 18 | 19 | export * from './getOceanPeers.js' 20 | export * from './auth.js' 21 | 22 | export const httpRoutes = express.Router() 23 | 24 | export function sendMissingP2PResponse(res: Response) { 25 | res.status(400).send('Invalid or Non Existing P2P configuration') 26 | } 27 | 28 | // /p2pRoutes 29 | httpRoutes.use(p2pRoutes) 30 | // /getProvidersForDid 31 | httpRoutes.use(getProvidersForStringRoute) 32 | httpRoutes.use(getProvidersForStringsRoute) 33 | // /directCommand 34 | httpRoutes.use(directCommandRoute) 35 | // /logs 36 | // /log/:id 37 | httpRoutes.use(logRoutes) 38 | // /api/services/fileInfo 39 | httpRoutes.use(fileInfoRoute) 40 | // /api/services/decrypt 41 | // /api/services/encrypt 42 | // /api/services/download 43 | // /api/services/initialize 44 | // /api/services/nonce 45 | httpRoutes.use(providerRoutes) 46 | // /api/aquarius/assets/ddo/:did 47 | // /api/aquarius/assets/metadata/:did 48 | // /api/aquarius/assets/metadata/query 49 | // /api/aquarius/state/ddo 50 | // /api/aquarius/assets/ddo/validate 51 | httpRoutes.use(aquariusRoutes) 52 | httpRoutes.use(rootEndpointRoutes) 53 | // /api/services/computeEnvironments 54 | httpRoutes.use(computeRoutes) 55 | // queue routes 56 | httpRoutes.use(queueRoutes) 57 | // running jobs 58 | httpRoutes.use(jobsRoutes) 59 | // policy server passthrough 60 | httpRoutes.use(PolicyServerPassthroughRoute) 61 | // auth routes 62 | httpRoutes.use(authRoutes) 63 | // admin config routes 64 | httpRoutes.use(adminConfigRoutes) 65 | 66 | export function getAllServiceEndpoints() { 67 | httpRoutes.stack.forEach(addMapping.bind(null, [])) 68 | const data: any = {} 69 | const keys = allRoutesMapping.keys() 70 | for (const key of keys) { 71 | const pathData = allRoutesMapping.get(key) 72 | const name = findPathName(pathData[0], pathData[1]) 73 | if (name) { 74 | data[name] = pathData 75 | } else { 76 | // use the key 77 | data[key] = pathData 78 | } 79 | } 80 | return data 81 | } 82 | -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/1424.c15d7e6321ca35d6.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1424],{81424:function(M,j,L){L.r(j),L.d(j,{default:function(){return N}});var N="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyOCAyOCI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48bWFzayBpZD0iYiIgd2lkdGg9IjI4IiBoZWlnaHQ9IjI4IiB4PSIwIiB5PSIwIiBtYXNrVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBzdHlsZT0ibWFzay10eXBlOmFscGhhIj48cGF0aCBmaWxsPSIjRkRGREZGIiBkPSJNMTQgMjhjNy43MzIgMCAxNC02LjI2OCAxNC0xNFMyMS43MzIgMCAxNCAwIDAgNi4yNjggMCAxNHM2LjI2OCAxNCAxNCAxNFoiLz48L21hc2s+PGcgbWFzaz0idXJsKCNiKSI+PHBhdGggZmlsbD0iI0ZERkRGRiIgZD0iTTYgM2gxNnYyMUg2eiIvPjxwYXRoIGZpbGw9IiMxMjczRUEiIGQ9Ik0wIDE0djE0aDI4VjBIMHYxNFptMTkuNDY4LTguNTQzYy4zMDcuMDcyLjUzNy4yMDEuNzQ3LjQyLjEwMy4xMDguMTg2LjIzLjI1My4zNy4wNi4xMjYuMDg5LjIxMi4xMTUuMzQzbC4wMi4xdjExLjgwNWwtLjAyLjA5NGMtLjA2Mi4yOC0uMTkzLjUyOS0uMzgzLjcyMi0uMDUuMDUtLjMwMi4yNi0uNjcyLjU1NmwtMS4wMzguODMzLS43NTQuNjA1LTIuMDM4IDEuNjM2Yy0uNTY5LjQ1OC0xLjA2Ljg1LTEuMDkzLjg3Mi0uMzQuMjMtLjc4Mi4yNDYtMS4xNC4wNGE1LjgwNCA1LjgwNCAwIDAgMS0uMzg4LS4yOTRMOS45NCAyMS4wNGwtMS4xOTktLjk2MmMtLjY1MS0uNTIzLS44OTUtLjcyNC0uOTU0LS43ODZhMS40NDUgMS40NDUgMCAwIDEtLjM2OS0uNzM5Yy0uMDE3LS4xMDEtLjAxNy0uMTktLjAxNS02LjAwNWwuMDAyLTUuOTAyLjAyNi0uMTAzYTEuNDQ1IDEuNDQ1IDAgMCAxIDEuMjI0LTEuMTA1Yy4wMTctLjAwMiAyLjQ0LS4wMDMgNS4zOC0uMDAzbDUuMzUuMDAyLjA4My4wMloiLz48cGF0aCBmaWxsPSIjMTI3M0VBIiBkPSJNMTAuODczIDcuNThBMS40MyAxLjQzIDAgMCAwIDkuNTg2IDguNjVjLS4wNDUuMTgxLS4wNDQtLjA1LS4wNDQgNC40ODkgMCAyLjkwNS4wMDMgNC4yNS4wMSA0LjMwMi4wNDMuMzQ4LjE4OC42NDIuNDMuODc1LjA1My4wNSAyLjIwNSAxLjc4NCAyLjMxOCAxLjg2Ny4wMjUuMDIuMDQuMDIzLjA5NS4wMjMuMDU2IDAgLjA3LS4wMDQuMTAxLS4wMjRhLjE4LjE4IDAgMCAwIC4wNTYtLjA2M2wuMDE5LS4wNHYtMi45NTdjMC0xLjg5LjAwNC0yLjk2OC4wMDktMi45ODhhLjE4Ni4xODYgMCAwIDEgLjA5LS4xMTNsLjA0Mi0uMDIyaC44NzRjLjk1OCAwIC45NDggMCAxLjExMi4wNTUuMzIyLjEwNy41NzIuMzYuNjguNjg0LjA1NC4xNi4wNTEtLjAwNy4wNTEgMi43NjUgMCAxLjgxLjAwMyAyLjU1Mi4wMSAyLjU3Ny4wMjEuMDguMDg5LjEzLjE3Mi4xM2EuMTc1LjE3NSAwIDAgMCAuMDcyLS4wMTVjLjAyNy0uMDE0IDIuMTk0LTEuNzUgMi4zMDgtMS44NDguMjI0LS4xOTUuMzktLjQ5NC40NS0uODA5LjAyLS4xLjAyLS4xMDguMDIzLTEuNDQyLjAwNC0xLjM5MiAwLTEuNTItLjAzMy0xLjY4MmExLjM3NSAxLjM3NSAwIDAgMC0uMzktLjcwNyAxLjM3NCAxLjM3NCAwIDAgMC0uODQ3LS40MWwtLjA5NC0uMDEyLjA2My0uMDA3Yy4xOC0uMDIuMzYyLS4wNzEuNTAzLS4xNDIuMzk3LS4yLjY2OS0uNTUyLjc2Ny0uOTk2bC4wMi0uMDl2LTEuNjFjMC0xLjUzMyAwLTEuNjEzLS4wMTctMS42OThhMS40MDQgMS40MDQgMCAwIDAtLjQyNi0uNzc4IDEuNCAxLjQgMCAwIDAtLjg1My0uMzgyYy0uMDkzLS4wMDgtNi4xODYtLjAxMS02LjI4NC0uMDAzWm00LjQ2NCAxLjQ0NWMuMDIxLjAxMi4wNS4wMzcuMDYzLjA1N2wuMDI1LjAzNi4wMDIgMS4yMzNjLjAwMiAxLjEwMy4wMDEgMS4yNDItLjAxMSAxLjMyMi0uMDcyLjQzMi0uMzkuNzctLjgzLjg3OC0uMDc0LjAxOC0uMDk3LjAxOC0uOTY3LjAyMS0uOTk2LjAwNC0uOTM3LjAwNy0uOTk4LS4wNmEuMjEzLjIxMyAwIDAgMS0uMDQtLjA2OWMtLjAxNC0uMDQ4LS4wMTQtMy4yNjUgMC0zLjMxM2EuMjA3LjIwNyAwIDAgMSAuMTEtLjExOGMuMDMxLS4wMS4yMy0uMDEyIDEuMzIyLS4wMWwxLjI4Ni4wMDEuMDM4LjAyMloiLz48L2c+PC9nPjxkZWZzPjxjbGlwUGF0aCBpZD0iYSI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTAgMGgyOHYyOEgweiIvPjwvY2xpcFBhdGg+PC9kZWZzPjwvc3ZnPg=="}}]); -------------------------------------------------------------------------------- /dist/controlpanel/_next/static/chunks/1748.f63b451fd93f590b.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1748],{31748:function(M,I,A){A.r(I),A.d(I,{default:function(){return g}});var g="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0OCA0OCI+PGcgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBmaWxsPSJ1cmwoI2IpIiBkPSJNMCAxNmMwLTUuNiAwLTguNCAxLjA5LTEwLjU0YTEwIDEwIDAgMCAxIDQuMzctNC4zN0M3LjYgMCAxMC40IDAgMTYgMGgxNmM1LjYgMCA4LjQgMCAxMC41NCAxLjA5YTEwIDEwIDAgMCAxIDQuMzcgNC4zN0M0OCA3LjYgNDggMTAuNCA0OCAxNnYxNmMwIDUuNiAwIDguNC0xLjA5IDEwLjU0YTEwLjAwMSAxMC4wMDEgMCAwIDEtNC4zNyA0LjM3QzQwLjQgNDggMzcuNiA0OCAzMiA0OEgxNmMtNS42IDAtOC40IDAtMTAuNTQtMS4wOWExMCAxMCAwIDAgMS00LjM3LTQuMzdDMCA0MC40IDAgMzcuNiAwIDMyVjE2WiIvPjxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjA4IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xLjEzMyA5LjUxM0MxIDExLjEzMSAxIDEzLjE4MyAxIDE2djE2YzAgMi44MTcgMCA0Ljg3LjEzMyA2LjQ4Ni4xMzEgMS42MDYuMzg3IDIuNjk1Ljg0OCAzLjZhOSA5IDAgMCAwIDMuOTMzIDMuOTMzYy45MDUuNDYxIDEuOTk0LjcxNyAzLjYuODQ4QzExLjEzIDQ3IDEzLjE4MyA0NyAxNiA0N2gxNmMyLjgxNyAwIDQuODcgMCA2LjQ4Ni0uMTMzIDEuNjA2LS4xMzEgMi42OTUtLjM4NyAzLjYtLjg0OGE5IDkgMCAwIDAgMy45MzMtMy45MzNjLjQ2MS0uOTA1LjcxNy0xLjk5NC44NDgtMy42QzQ3IDM2Ljg3IDQ3IDM0LjgxNiA0NyAzMlYxNmMwLTIuODE3IDAtNC44Ny0uMTMzLTYuNDg3LS4xMzEtMS42MDUtLjM4Ny0yLjY5NC0uODQ4LTMuNTk5YTkgOSAwIDAgMC0zLjkzMy0zLjkzM2MtLjkwNS0uNDYxLTEuOTk0LS43MTctMy42LS44NDhDMzYuODcgMSAzNC44MTYgMSAzMiAxSDE2Yy0yLjgxNyAwLTQuODcgMC02LjQ4Ny4xMzMtMS42MDUuMTMxLTIuNjk0LjM4Ny0zLjU5OS44NDhhOSA5IDAgMCAwLTMuOTMzIDMuOTMzYy0uNDYxLjkwNS0uNzE3IDEuOTk0LS44NDggMy42Wk0xLjA5IDUuNDZDMCA3LjYgMCAxMC40IDAgMTZ2MTZjMCA1LjYgMCA4LjQgMS4wOSAxMC41NGExMCAxMCAwIDAgMCA0LjM3IDQuMzdDNy42IDQ4IDEwLjQgNDggMTYgNDhoMTZjNS42IDAgOC40IDAgMTAuNTQtMS4wOWExMC4wMDEgMTAuMDAxIDAgMCAwIDQuMzctNC4zN0M0OCA0MC40IDQ4IDM3LjYgNDggMzJWMTZjMC01LjYgMC04LjQtMS4wOS0xMC41NGExMCAxMCAwIDAgMC00LjM3LTQuMzdDNDAuNCAwIDM3LjYgMCAzMiAwSDE2QzEwLjQgMCA3LjYgMCA1LjQ2IDEuMDlhMTAgMTAgMCAwIDAtNC4zNyA0LjM3WiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuMTIiIGQ9Ik0zNi4zNDUgMTMuMTU1YTEuNSAxLjUgMCAxIDAtMyAwdjIuMjI0YzAgLjYyNy0uNzc1LjkzNy0xLjIxOC40OTRhMTIuNzUgMTIuNzUgMCAxIDAgMCAxOC4wMzEgMS41IDEuNSAwIDEgMC0yLjEyMS0yLjEyIDkuNzUgOS43NSAwIDEgMSAwLTEzLjc5Yy42MS42MS4xNzIgMS42MTYtLjY5MSAxLjYxNkgyNi44OWExLjUgMS41IDAgMCAwIDAgM2g3Ljk1NWExLjUgMS41IDAgMCAwIDEuNS0xLjV2LTcuOTU1WiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0zNi4zNDUgMTIuMTU1YTEuNSAxLjUgMCAxIDAtMyAwdjIuMjI0YzAgLjYyNy0uNzc1LjkzNy0xLjIxOC40OTRhMTIuNzUgMTIuNzUgMCAxIDAgMCAxOC4wMzEgMS41IDEuNSAwIDEgMC0yLjEyMS0yLjEyIDkuNzUgOS43NSAwIDEgMSAwLTEzLjc5Yy42MS42MS4xNzIgMS42MTYtLjY5MSAxLjYxNkgyNi44OWExLjUgMS41IDAgMCAwIDAgM2g3Ljk1NWExLjUgMS41IDAgMCAwIDEuNS0xLjV2LTcuOTU1WiIvPjwvZz48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImIiIHgxPSIyNCIgeDI9IjI0IiB5MT0iMCIgeTI9IjQ4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iIzU5NjI3QSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzRBNTI2NiIvPjwvbGluZWFyR3JhZGllbnQ+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwaDQ4djQ4SDB6Ii8+PC9jbGlwUGF0aD48L2RlZnM+PC9zdmc+"}}]); -------------------------------------------------------------------------------- /controlpanel/src/components/Admin/index.module.css: -------------------------------------------------------------------------------- 1 | .download { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: center; 5 | flex-wrap: nowrap; 6 | align-items: center; 7 | width: 100%; 8 | gap: 4px; 9 | color: #4a5360; 10 | font-family: Helvetica; 11 | font-size: 16px; 12 | font-style: normal; 13 | font-weight: 500; 14 | line-height: 140%; 15 | text-align: left; 16 | background: transparent; 17 | border: transparent; 18 | } 19 | 20 | .unlockButton { 21 | padding: 10px 20px; 22 | background-color: #007bff; 23 | border: none; 24 | color: white; 25 | text-transform: uppercase; 26 | font-weight: bold; 27 | cursor: pointer; 28 | transition: 29 | background-color 0.3s, 30 | transform 0.2s; 31 | border-radius: 4px; 32 | outline: none; 33 | } 34 | 35 | .unlockButton:hover { 36 | background-color: #0056b3; 37 | transform: scale(1.05); 38 | } 39 | 40 | .unlockButton:active { 41 | transform: scale(0.95); 42 | } 43 | 44 | .unlockButton:focus { 45 | box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.5); 46 | } 47 | 48 | .buttonIcon { 49 | color: #a0aec0; 50 | } 51 | 52 | .download:hover { 53 | background-color: transparent; 54 | color: black; 55 | } 56 | 57 | .loader { 58 | width: 48px; 59 | height: 48px; 60 | border: 3px dotted #fff; 61 | border-style: solid solid dotted dotted; 62 | border-radius: 50%; 63 | display: inline-block; 64 | position: relative; 65 | box-sizing: border-box; 66 | animation: rotation 2s linear infinite; 67 | } 68 | .loader::after { 69 | content: ''; 70 | box-sizing: border-box; 71 | position: absolute; 72 | left: 0; 73 | right: 0; 74 | top: 0; 75 | bottom: 0; 76 | margin: auto; 77 | border: 3px dotted #ff3d00; 78 | border-style: solid solid dotted; 79 | width: 24px; 80 | height: 24px; 81 | border-radius: 50%; 82 | animation: rotationBack 1s linear infinite; 83 | transform-origin: center center; 84 | } 85 | 86 | @keyframes rotation { 87 | 0% { 88 | transform: rotate(0deg); 89 | } 90 | 100% { 91 | transform: rotate(360deg); 92 | } 93 | } 94 | @keyframes rotationBack { 95 | 0% { 96 | transform: rotate(0deg); 97 | } 98 | 100% { 99 | transform: rotate(-360deg); 100 | } 101 | } 102 | 103 | .root { 104 | border-radius: 12px; 105 | background: #fff; 106 | max-width: 320px; 107 | min-width: 245px; 108 | display: flex; 109 | flex-direction: column; 110 | padding: 20px; 111 | } 112 | 113 | .title { 114 | color: #3d4551; 115 | font-family: Helvetica; 116 | font-size: 20px; 117 | font-style: normal; 118 | font-weight: 700; 119 | line-height: 140%; 120 | margin-bottom: 47px; 121 | } 122 | 123 | .unauthorised { 124 | color: #ff3d00; 125 | } 126 | 127 | @media screen and (max-width: 700px) { 128 | .root { 129 | max-width: none; 130 | width: 90vw; 131 | margin: 0 auto; 132 | padding: 20px; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/integration/purgatory.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, assert } from 'chai' 2 | import { Purgatory } from '../../components/Indexer/purgatory.js' 3 | import { 4 | OverrideEnvConfig, 5 | buildEnvOverrideConfig, 6 | setupEnvironment, 7 | tearDownEnvironment 8 | } from '../utils/utils.js' 9 | import { ENVIRONMENT_VARIABLES } from '../../utils/index.js' 10 | 11 | describe('Purgatory test', () => { 12 | let purgatory: Purgatory 13 | let previousConfiguration: OverrideEnvConfig[] 14 | 15 | before(async () => { 16 | // override and save configuration (always before calling getConfig()) 17 | previousConfiguration = await setupEnvironment( 18 | null, 19 | buildEnvOverrideConfig( 20 | [ 21 | ENVIRONMENT_VARIABLES.ASSET_PURGATORY_URL, 22 | ENVIRONMENT_VARIABLES.ACCOUNT_PURGATORY_URL 23 | ], 24 | [ 25 | 'https://raw.githubusercontent.com/oceanprotocol/list-purgatory/main/list-assets.json', 26 | 'https://raw.githubusercontent.com/oceanprotocol/list-purgatory/main/list-accounts.json' 27 | ] 28 | ) 29 | ) 30 | 31 | purgatory = await Purgatory.getInstance() 32 | }) 33 | 34 | it('instance Purgatory', () => { 35 | expect(purgatory).to.be.instanceOf(Purgatory) 36 | }) 37 | it('should retrieve account list', async () => { 38 | const accountPurgatory = await purgatory.parsePurgatoryAccounts() 39 | assert(accountPurgatory, 'account purgatory list could not be fetched.') 40 | let res: any 41 | for (const acc of accountPurgatory) { 42 | if ( 43 | acc.address?.toLowerCase() === 44 | '0xAD23fC9D943018C34aC55E8DA29AF700A2Fd0FeB'?.toLowerCase() 45 | ) { 46 | res = acc 47 | break 48 | } 49 | } 50 | assert(res, 'could not find this banned account') 51 | assert(res.reason === 'bad actor') 52 | }) 53 | 54 | it('should check if account is banned', async () => { 55 | assert( 56 | (await purgatory.isBannedAccount('0xAD23fC9D943018C34aC55E8DA29AF700A2Fd0FeB')) === 57 | true 58 | ) 59 | }) 60 | 61 | it('should retrieve assets list', async () => { 62 | const assetPurgatory = await purgatory.parsePurgatoryAssets() 63 | assert(assetPurgatory, 'asset purgatory list could not be fetched.') 64 | let res: any 65 | for (const a of assetPurgatory) { 66 | if ( 67 | a.did === 68 | 'did:op:5b33dd722bd9e5e291685545203dfcfd914b55d12de0c1f31541d323e581041c' 69 | ) { 70 | res = a 71 | break 72 | } 73 | } 74 | assert(res, 'could not find this banned asset') 75 | assert(res.reason === 'mumbai test dataset') 76 | }) 77 | 78 | it('should check if asset is banned', async () => { 79 | assert( 80 | (await purgatory.isBannedAsset( 81 | 'did:op:5b33dd722bd9e5e291685545203dfcfd914b55d12de0c1f31541d323e581041c' 82 | )) === true 83 | ) 84 | }) 85 | 86 | after(async () => { 87 | await tearDownEnvironment(previousConfiguration) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /docs/PolicyServer.md: -------------------------------------------------------------------------------- 1 | # Policy Server 2 | 3 | Sometimes, actions performed by Ocean Node have to be double-checked on a higher level of authorization. This might include Oath tokens, SSI verifiable credentials, Enterprise LDAP, etc... 4 | 5 | For this, we will adopt a simple, but flexible architecture: 6 | 7 | For every command, Ocean Node will query PolicyServer (if such env is defined) and wait for it to perform all needed checks. 8 | 9 | For 200 OK responses, Ocean Node will continue to perform the action. For everything else, it will deny. If there is a body in response, we will forward that body to the caller. (so users can see the PolicyServer error messages and act accordingly) 10 | 11 | Every Ocean Node command will also accept a data field, called "policyServer" which will be added to the query (so we can pass data from the user to PolicyServer) 12 | 13 | ## PolicyServer API definition 14 | 15 | All queries will be performed by sending a POST request to PolicyServer Endpoint, with a json payload that looks like this: 16 | 17 | ```json 18 | { 19 | "action":"newDDO", 20 | ...... 21 | } 22 | ``` 23 | 24 | Every command will have its own set of data, in addition to the "action" field. 25 | I will describe them below: 26 | 27 | ### newDDO 28 | 29 | Called whenever a new DDO is detected by indexer 30 | 31 | ```json 32 | { 33 | "action":"newDDO", 34 | "rawDDO": {..}, 35 | "chainId": 1, 36 | "txId": "0x123", 37 | "eventRaw": "raw event data" 38 | } 39 | ``` 40 | 41 | ### updateDDO 42 | 43 | Called whenever a DDO is updated by indexer 44 | 45 | ```json 46 | { 47 | "action":"updateDDO", 48 | "rawDDO": {..}, 49 | "chainId": 1, 50 | "txId": "0x123", 51 | "eventRaw": "raw event data" 52 | } 53 | ``` 54 | 55 | ### initialize 56 | 57 | Called whenever a new initialize command is received by Ocean Node 58 | 59 | ```json 60 | { 61 | "action":"initialize", 62 | "documentId": "did:op:123", 63 | "ddo": {}, 64 | "serviceId": "0x123", 65 | "consumerAddress": "0x123" 66 | "policyServer": {} 67 | } 68 | ``` 69 | 70 | ### download 71 | 72 | Called whenever a new download command is received by Ocean Node 73 | 74 | ```json 75 | { 76 | "action":"download", 77 | "documentId": "did:op:123", 78 | "ddo": {}, 79 | "serviceId": "0x123", 80 | "fileIndex": 1, 81 | "transferTxId": "0x123", 82 | "consumerAddress": "0x123" 83 | "policyServer": {} 84 | } 85 | ``` 86 | 87 | ### encrypt 88 | 89 | Called whenever a new encrypt command is received by Ocean Node 90 | 91 | ```json 92 | { 93 | "action": "encrypt", 94 | "policyServer": {} 95 | } 96 | ``` 97 | 98 | ### decrypt 99 | 100 | Called whenever a new decrypt command is received by Ocean Node 101 | 102 | ```json 103 | { 104 | "action": "decrypt", 105 | "decrypterAddress": "0x123", 106 | "chainId": 1, 107 | "transactionId": "0x123", 108 | "dataNftAddress": "0x123", 109 | "policyServer": {} 110 | } 111 | ``` 112 | -------------------------------------------------------------------------------- /docs/networking.md: -------------------------------------------------------------------------------- 1 | # Ocean Node Networking 2 | 3 | ## Networking in cloud environments or DMZ 4 | 5 | In order for your node to join the network, the others nodes needs to be able to connect to it. 6 | All options can be controlled using [environment 7 | variables](env.md#p2p) 8 | 9 | To quickly start your node, you can keep all of the default values,but most likely it will hurt performance. If you want a customised approach, here are the full steps: 10 | 11 | - decide what IP version to use (IPV4 or/and IPv6). You should use both if available. 12 | - decide if you want to filter private ips (if you run multiple nodes in a LAN or cloud environment, leave them on) 13 | - if you already have an external ip configured on your machine, you are good to go. 14 | - if you have a private ip, but an UPNP gateway, you should be fine as well. 15 | - if you have a private ip and you can forward external ports from your gateway, use P2P_ANNOUNCE_ADDRESSES and let other nodes know your external IP/port. 16 | - if you cannot forward ports on your gateway, the only choice is to use a circuit relay server (then all traffic will go through that node and it will proxy) 17 | 18 | In order to check connectivity, you can do the following: 19 | 20 | ### On your node, check and observe how your node sees itself: 21 | 22 | ```bash 23 | curl http://localhost:8000/getP2pPeer?peerId=16Uiu2HAkwWe6BFQXZWg6zE9X7ExynvXEe9BRTR5Wn3udNs7JpUDx 24 | ``` 25 | 26 | and observe the addresses section: 27 | 28 | ```json 29 | { 30 | "addresses": [ 31 | { "multiaddr": "/ip4/127.0.0.1/tcp/34227", "isCertified": false }, 32 | { "multiaddr": "/ip4/127.0.0.1/tcp/36913/ws", "isCertified": false }, 33 | { "multiaddr": "/ip4/172.15.0.1/tcp/34227", "isCertified": false }, 34 | { "multiaddr": "/ip4/172.15.0.1/tcp/36913/ws", "isCertified": false }, 35 | { "multiaddr": "/ip4/172.26.53.25/tcp/34227", "isCertified": false }, 36 | { "multiaddr": "/ip4/172.26.53.25/tcp/36913/ws", "isCertified": false }, 37 | { "multiaddr": "/ip6/::1/tcp/41157", "isCertified": false } 38 | ], 39 | "protocols": [ 40 | "/floodsub/1.0.0", 41 | "/ipfs/id/1.0.0", 42 | "/ipfs/id/push/1.0.0", 43 | "/ipfs/ping/1.0.0", 44 | "/libp2p/autonat/1.0.0", 45 | "/libp2p/circuit/relay/0.2.0/hop", 46 | "/libp2p/circuit/relay/0.2.0/stop", 47 | "/libp2p/dcutr", 48 | "/meshsub/1.0.0", 49 | "/meshsub/1.1.0", 50 | "/ocean/nodes/1.0.0", 51 | "/ocean/nodes/1.0.0/kad/1.0.0", 52 | "/ocean/nodes/1.0.0/lan/kad/1.0.0" 53 | ], 54 | "metadata": {}, 55 | "tags": {}, 56 | "id": "16Uiu2HAkwWe6BFQXZWg6zE9X7ExynvXEe9BRTR5Wn3udNs7JpUDx", 57 | "publicKey": "08021221021efd24150c233d689ade0f9f467aa6a5a2969a5f52d70c85caac8681925093e3" 58 | } 59 | ``` 60 | 61 | Are any of those IPs reachable from other nodes? 62 | 63 | ### To observe how your node is seen by others, start your node, wait a bit and then ask another node to give you details about you: 64 | 65 | ```bash 66 | curl http://node2.oceanprotocol.com:8000/getP2pPeer?peerId=16Uiu2HAk 67 | wWe6BFQXZWg6zE9X7ExynvXEe9BRTR5Wn3udNs7JpUDx 68 | ``` 69 | -------------------------------------------------------------------------------- /src/test/unit/ocean.test.ts: -------------------------------------------------------------------------------- 1 | import { OceanNode } from '../../OceanNode.js' 2 | import { OceanIndexer } from '../../components/Indexer/index.js' 3 | import { OceanP2P } from '../../components/P2P/index.js' 4 | import { OceanProvider } from '../../components/Provider/index.js' 5 | import { Database } from '../../components/database/index.js' 6 | import { ENVIRONMENT_VARIABLES, getConfiguration } from '../../utils/index.js' 7 | 8 | import { expect } from 'chai' 9 | import { 10 | OverrideEnvConfig, 11 | TEST_ENV_CONFIG_FILE, 12 | buildEnvOverrideConfig, 13 | setupEnvironment, 14 | tearDownEnvironment 15 | } from '../utils/utils.js' 16 | 17 | let envOverrides: OverrideEnvConfig[] 18 | 19 | describe('Status command tests', async () => { 20 | // need to do it first 21 | envOverrides = buildEnvOverrideConfig( 22 | [ 23 | ENVIRONMENT_VARIABLES.PRIVATE_KEY, 24 | ENVIRONMENT_VARIABLES.IPFS_GATEWAY, 25 | ENVIRONMENT_VARIABLES.ARWEAVE_GATEWAY, 26 | ENVIRONMENT_VARIABLES.RPCS, 27 | ENVIRONMENT_VARIABLES.INDEXER_NETWORKS 28 | ], 29 | [ 30 | '0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58', 31 | 'https://ipfs.io/', 32 | 'https://arweave.net/', 33 | '{ "1": "https://rpc.eth.gateway.fm", "137": "https://polygon.meowrpc.com" }', 34 | JSON.stringify([1, 137]) 35 | ] 36 | ) 37 | envOverrides = await setupEnvironment(TEST_ENV_CONFIG_FILE, envOverrides) 38 | // because of this 39 | const config = await getConfiguration(true) 40 | const db = await Database.init(config.dbConfig) 41 | const oceanP2P = new OceanP2P(config, db) 42 | const oceanIndexer = new OceanIndexer(db, config.indexingNetworks) 43 | const oceanProvider = new OceanProvider(db) 44 | const oceanNode = OceanNode.getInstance(config, db, oceanP2P) 45 | 46 | after(async () => { 47 | // Restore original local setup / env variables after test 48 | await tearDownEnvironment(envOverrides) 49 | await oceanIndexer.stopAllThreads() 50 | }) 51 | 52 | it('Ocean Node instance', () => { 53 | expect(oceanNode).to.be.instanceOf(OceanNode) 54 | expect(config.supportedNetworks).to.eql({ 55 | '1': 'https://rpc.eth.gateway.fm', 56 | '137': 'https://polygon.meowrpc.com' 57 | }) 58 | expect(oceanNode.getDatabase()).to.not.eql(null) 59 | expect(config.hasP2P).to.eql(true) 60 | expect(config.hasIndexer).to.eql(true) 61 | }) 62 | it('Ocean P2P should be initialized correctly', () => { 63 | expect(oceanNode.getP2PNode()).to.not.eql(null) 64 | expect(OceanNode.getInstance(config, db).getP2PNode()).to.not.eql(null) 65 | }) 66 | it('Ocean Indexer should be initialized correctly', () => { 67 | oceanNode.addIndexer(oceanIndexer) 68 | expect(oceanNode.getIndexer().getSupportedNetworks()).to.eql(config.indexingNetworks) 69 | expect(oceanNode.getIndexer().getDatabase()).to.eql(db) 70 | }) 71 | it('Ocean Provider should be initialized correctly', () => { 72 | oceanNode.addProvider(oceanProvider) 73 | expect(oceanNode.getProvider().getDatabase()).to.eql(db) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /src/test/unit/indexer/indexer.test.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from 'chai' 2 | import { describe, it } from 'mocha' 3 | import { OceanIndexer } from '../../../components/Indexer/index.js' 4 | import { ENVIRONMENT_VARIABLES, getConfiguration } from '../../../utils/index.js' 5 | import { Database } from '../../../components/database/index.js' 6 | import { OceanNodeConfig } from '../../../@types/OceanNode.js' 7 | import { 8 | hasValidDBConfiguration, 9 | isReachableConnection 10 | } from '../../../utils/database.js' 11 | import { 12 | buildEnvOverrideConfig, 13 | OverrideEnvConfig, 14 | setupEnvironment, 15 | tearDownEnvironment, 16 | TEST_ENV_CONFIG_FILE 17 | } from '../../utils/utils.js' 18 | import sinon, { SinonSandbox } from 'sinon' 19 | 20 | describe('OceanIndexer', () => { 21 | let envOverrides: OverrideEnvConfig[] 22 | let oceanIndexer: OceanIndexer 23 | let mockDatabase: Database 24 | let config: OceanNodeConfig 25 | let sandbox: SinonSandbox 26 | before(async () => { 27 | envOverrides = buildEnvOverrideConfig( 28 | [ENVIRONMENT_VARIABLES.RPCS], 29 | [ 30 | '{ "8996":{ "rpc":"http://127.0.0.1:8545", "fallbackRPCs": ["http://127.0.0.3:8545","http://127.0.0.1:8545"], "chainId": 8996, "network": "development", "chunkSize": 100 }}' 31 | ] 32 | ) 33 | envOverrides = await setupEnvironment(TEST_ENV_CONFIG_FILE, envOverrides) 34 | config = await getConfiguration(true) 35 | sandbox = sinon.createSandbox() 36 | sandbox.stub(Database, 'init').resolves({ 37 | nonce: {}, 38 | c2d: {}, 39 | authToken: {}, 40 | sqliteConfig: {}, 41 | ddo: {}, 42 | indexer: {}, 43 | logs: {}, 44 | order: {}, 45 | ddoState: {}, 46 | getConfig: () => config.dbConfig 47 | } as any) 48 | }) 49 | 50 | it('should start threads and handle worker events', async () => { 51 | mockDatabase = await Database.init(config.dbConfig) 52 | console.log('mockDatabase: ', mockDatabase) 53 | console.log('config.dbConfig: ', JSON.stringify(config.dbConfig)) 54 | oceanIndexer = new OceanIndexer(mockDatabase, config.supportedNetworks) 55 | assert(oceanIndexer, 'indexer should not be null') 56 | expect(oceanIndexer.getDatabase().getConfig()).to.be.equal(mockDatabase.getConfig()) 57 | expect(oceanIndexer.getIndexingQueue().length).to.be.equal(0) 58 | expect(oceanIndexer.getJobsPool().length).to.be.equal(0) 59 | 60 | if ( 61 | hasValidDBConfiguration(mockDatabase.getConfig()) && 62 | (await isReachableConnection(mockDatabase.getConfig().url)) 63 | ) { 64 | // should be fine, if we have a valid RPC as well 65 | expect(await oceanIndexer.startThreads()).to.be.equal(true) 66 | } else { 67 | // cannot start threads without DB connection 68 | expect(await oceanIndexer.startThreads()).to.be.equal(false) 69 | } 70 | 71 | // there are no worker threads available 72 | expect(oceanIndexer.stopAllThreads()).to.be.equal(false) 73 | }) 74 | after(async () => { 75 | await tearDownEnvironment(envOverrides) 76 | sandbox.restore() 77 | }) 78 | }) 79 | --------------------------------------------------------------------------------