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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
873 changes: 421 additions & 452 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ocean-node",
"version": "3.0.1",
"version": "3.0.3",
"description": "Ocean Node is used to run all core services in the Ocean stack",
"author": "Ocean Protocol Foundation",
"license": "Apache-2.0",
Expand Down Expand Up @@ -132,7 +132,7 @@
"mocha": "^11.1.0",
"nyc": "^17.1.0",
"prettier": "^3.7.4",
"release-it": "^19.0.6",
"release-it": "^20.0.0",
"sinon": "^19.0.2",
"tsx": "^4.19.3",
"typescript": "^5.9.3"
Expand Down
42 changes: 27 additions & 15 deletions src/components/c2d/compute_engine_docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ import type {
ComputeResourcesPricingInfo
} from '../../@types/C2D/C2D.js'
import {
BENCHMARK_MONITORING_ADDRESS,
BASE_CHAIN_ID,
getConfiguration,
SEPOLIA_CHAIN_ID,
USDC_TOKEN
USDC_TOKEN_ADDRESS_BASE
} from '../../utils/config.js'
import { C2DEngine } from './compute_engine_base.js'
import { C2DDatabase } from '../database/C2DDatabase.js'
Expand Down Expand Up @@ -64,7 +63,8 @@ import { Service } from '@oceanprotocol/ddo-js'
import { getOceanTokenAddressForChain } from '../../utils/address.js'
import { dockerRegistrysAuth, dockerRegistryAuth } from '../../@types/OceanNode.js'
import { EncryptMethod } from '../../@types/fileObject.js'
import { ZeroAddress } from 'ethers'
import { getAddress, ZeroAddress } from 'ethers'
import { AccessList } from '../../@types/AccessList.js'

const C2D_CONTAINER_UID = 1000
const C2D_CONTAINER_GID = 1000
Expand Down Expand Up @@ -215,11 +215,8 @@ export class C2DEngineDocker extends C2DEngine {
price: 1
}))

const sepoliaChainId = SEPOLIA_CHAIN_ID
const usdcToken = USDC_TOKEN

const benchmarkFees: ComputeEnvFeesStructure = {
[sepoliaChainId]: [{ feeToken: usdcToken, prices: benchmarkPrices }]
[BASE_CHAIN_ID]: [{ feeToken: USDC_TOKEN_ADDRESS_BASE, prices: benchmarkPrices }]
}

const benchmarkEnv: C2DEnvironmentConfig = {
Expand All @@ -234,8 +231,10 @@ export class C2DEngineDocker extends C2DEngine {
...gpuResources
],
access: {
addresses: [BENCHMARK_MONITORING_ADDRESS],
accessLists: null
addresses: [],
accessLists: [
{ BASE_CHAIN_ID: [getAddress('0xcb7Db55Ca9Aa9C3b25F5Bc266da63317fa02086a')] }
]
},
fees: benchmarkFees
}
Expand All @@ -248,7 +247,7 @@ export class C2DEngineDocker extends C2DEngine {
const envConfig = await this.getC2DConfig().connection
if (!envConfig?.environments?.length) {
CORE_LOGGER.warn(
`Skipping C2D engine ${this.getC2DConfig().hash}: no environments configured`
`Skipping C2D Engine ${this.getC2DConfig().hash}: no environments configured`
)
return
}
Expand Down Expand Up @@ -286,7 +285,13 @@ export class C2DEngineDocker extends C2DEngine {
const consumerAddress = this.getKeyManager().getEthAddress()

if (config.enableBenchmark) {
this.createBenchmarkEnvironment(sysinfo, envConfig)
if (supportedChains.includes(parseInt(BASE_CHAIN_ID))) {
this.createBenchmarkEnvironment(sysinfo, envConfig)
} else {
CORE_LOGGER.warn(
`Skipping benchmark environment: Base chain (${BASE_CHAIN_ID}) is not in supportedNetworks`
)
}
}

for (let envIdx = 0; envIdx < envConfig.environments.length; envIdx++) {
Expand Down Expand Up @@ -403,9 +408,16 @@ export class C2DEngineDocker extends C2DEngine {
for (const env of this.envs) {
const cpuRes = this.getResource(env.resources ?? [], 'cpu')
if (cpuRes && cpuRes.total > 0) {
const isBenchmarkEnv = env.access?.addresses?.includes(
BENCHMARK_MONITORING_ADDRESS
)
let isBenchmarkEnv = false
if (env.access?.accessLists) {
const baseAccessList = env.access?.accessLists?.[0] as AccessList
if (baseAccessList && baseAccessList[BASE_CHAIN_ID]) {
isBenchmarkEnv = baseAccessList[BASE_CHAIN_ID].includes(
getAddress('0xcb7Db55Ca9Aa9C3b25F5Bc266da63317fa02086a')
)
}
}

if (isBenchmarkEnv) {
const total = physicalCpuCount > 0 ? physicalCpuCount : cpuRes.total
const cores = Array.from({ length: total }, (_, i) => i)
Expand Down
45 changes: 6 additions & 39 deletions src/components/core/handler/persistentStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,7 @@ function requirePersistentStorage(handler: CommandHandler): PersistentStorageFac

export class PersistentStorageCreateBucketHandler extends CommandHandler {
validate(command: PersistentStorageCreateBucketCommand): ValidateParams {
const base = validateCommandParameters(command, [
'consumerAddress',
'signature',
'nonce',
'accessLists'
])
const base = validateCommandParameters(command, ['accessLists'])
if (!base.valid) return base
if (!Array.isArray(command.accessLists)) {
return buildInvalidRequestMessage(
Expand Down Expand Up @@ -115,12 +110,7 @@ export class PersistentStorageCreateBucketHandler extends CommandHandler {

export class PersistentStorageGetBucketsHandler extends CommandHandler {
validate(command: PersistentStorageGetBucketsCommand): ValidateParams {
const base = validateCommandParameters(command, [
'consumerAddress',
'signature',
'nonce',
'owner'
])
const base = validateCommandParameters(command, ['owner'])
if (!base.valid) return base
if (!command.owner || typeof command.owner !== 'string') {
return buildInvalidRequestMessage(
Expand Down Expand Up @@ -177,12 +167,7 @@ export class PersistentStorageGetBucketsHandler extends CommandHandler {

export class PersistentStorageListFilesHandler extends CommandHandler {
validate(command: PersistentStorageListFilesCommand): ValidateParams {
const base = validateCommandParameters(command, [
'consumerAddress',
'signature',
'nonce',
'bucketId'
])
const base = validateCommandParameters(command, ['bucketId'])
if (!base.valid) return base
if (!command.bucketId || typeof command.bucketId !== 'string') {
return buildInvalidRequestMessage('Invalid parameter: "bucketId" must be a string')
Expand Down Expand Up @@ -226,13 +211,7 @@ export class PersistentStorageListFilesHandler extends CommandHandler {

export class PersistentStorageGetFileObjectHandler extends CommandHandler {
validate(command: PersistentStorageGetFileObjectCommand): ValidateParams {
const base = validateCommandParameters(command, [
'consumerAddress',
'signature',
'nonce',
'bucketId',
'fileName'
])
const base = validateCommandParameters(command, ['bucketId', 'fileName'])
if (!base.valid) return base
return { valid: true }
}
Expand Down Expand Up @@ -280,13 +259,7 @@ export class PersistentStorageGetFileObjectHandler extends CommandHandler {

export class PersistentStorageUploadFileHandler extends CommandHandler {
validate(command: PersistentStorageUploadFileCommand): ValidateParams {
const base = validateCommandParameters(command, [
'consumerAddress',
'signature',
'nonce',
'bucketId',
'fileName'
])
const base = validateCommandParameters(command, ['bucketId', 'fileName'])
if (!base.valid) return base
return { valid: true }
}
Expand Down Expand Up @@ -338,13 +311,7 @@ export class PersistentStorageUploadFileHandler extends CommandHandler {

export class PersistentStorageDeleteFileHandler extends CommandHandler {
validate(command: PersistentStorageDeleteFileCommand): ValidateParams {
const base = validateCommandParameters(command, [
'consumerAddress',
'signature',
'nonce',
'bucketId',
'fileName'
])
const base = validateCommandParameters(command, ['bucketId', 'fileName'])
if (!base.valid) return base
return { valid: true }
}
Expand Down
16 changes: 14 additions & 2 deletions src/components/httpRoutes/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,22 @@ directCommandRoute.post(
res.end()
} else if (hasP2PInterface) {
// Remote command - use P2P sendTo
let { multiAddrs } = req.body
if (typeof multiAddrs === 'string') {
if (multiAddrs.startsWith('[')) {
try {
const parsed = JSON.parse(multiAddrs)
multiAddrs = Array.isArray(parsed) ? parsed : [multiAddrs]
} catch {
multiAddrs = [multiAddrs]
}
} else {
multiAddrs = [multiAddrs]
}
}
const response = await req.oceanNode
.getP2PNode()
.sendTo(req.body.node as string, JSON.stringify(req.body), req.body.multiAddrs)

.sendTo(req.body.node as string, JSON.stringify(req.body), multiAddrs)
res.status(response.status.httpStatus)
if (response.status.headers) {
res.header(response.status.headers)
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import { scheduleCronJobs } from './utils/cronjobs/scheduleCronJobs.js'
import { requestValidator } from './components/httpRoutes/requestValidator.js'
import { hasValidDBConfiguration } from './utils/database.js'
import { assertFeeTokensSupportedByOec } from './utils/feeTokenValidation.js'

const app: Express = express()

Expand Down Expand Up @@ -102,6 +103,12 @@
// KeyManager will determine provider type from config.keys.type and initialize in constructor
const keyManager = new KeyManager(config)
const blockchainRegistry = new BlockchainRegistry(keyManager, config)
try {
await assertFeeTokensSupportedByOec(config, blockchainRegistry)
} catch (error) {
OCEAN_NODE_LOGGER.error(error instanceof Error ? error.message : String(error))
process.exit(1)
}

if (config.hasP2P) {
if (dbconn) {
Expand Down Expand Up @@ -153,7 +160,7 @@
if (config.httpCertPath && config.httpKeyPath) {
try {
const options = {
cert: fs.readFileSync(config.httpCertPath),

Check warning on line 163 in src/index.ts

View workflow job for this annotation

GitHub Actions / lint

Found readFileSync from package "fs" with non literal argument at index 0
key: fs.readFileSync(config.httpKeyPath)
}
https.createServer(options, app).listen(config.httpPort, () => {
Expand Down
3 changes: 2 additions & 1 deletion src/utils/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ export const ENV_TO_CONFIG_MAPPING = {
// Configuration defaults
export const DEFAULT_RATE_LIMIT_PER_MINUTE = 30
export const DEFAULT_MAX_CONNECTIONS_PER_MINUTE = 60 * 2 // 120 requests per minute
export const BENCHMARK_MONITORING_ADDRESS = '0xC5ea7916f95D5a087A644f1Dc0f7d19955eC446F'
export const SEPOLIA_CHAIN_ID = '11155111'
export const BASE_CHAIN_ID = '8453'
export const USDC_TOKEN = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'
export const USDC_TOKEN_ADDRESS_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'

export const DEFAULT_BOOTSTRAP_ADDRESSES = [
// OPF nodes
Expand Down
81 changes: 81 additions & 0 deletions src/utils/feeTokenValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import EnterpriseFeeCollectorJson from '@oceanprotocol/contracts/artifacts/contracts/communityFee/EnterpriseFeeCollector.sol/EnterpriseFeeCollector.json' with { type: 'json' }
import { Contract } from 'ethers'
import type { OceanNodeConfig } from '../@types/OceanNode.js'
import type { FeeTokens } from '../@types/Fees.js'
import type { Blockchain } from './blockchain.js'
import type { BlockchainRegistry } from '../components/BlockchainRegistry/index.js'
import { getOceanArtifactsAdressesByChainId } from './address.js'
import { CORE_LOGGER } from './logging/common.js'

export type UnsupportedFeeToken = {
chain: string
token: string
}

type BlockchainRegistryLike = Pick<BlockchainRegistry, 'getBlockchain'>

function formatFeeToken(token: UnsupportedFeeToken): string {
return `chain=${token.chain}, token=${token.token}`
}

export async function validateFeeTokensSupportedByOec(
config: OceanNodeConfig,
blockchainRegistry: BlockchainRegistryLike
): Promise<UnsupportedFeeToken[]> {
const feeTokens = config?.feeStrategy?.feeTokens || []
const unsupportedFeeTokens: UnsupportedFeeToken[] = []

for (const feeToken of feeTokens) {
const { chain, token } = feeToken as FeeTokens
const chainId = Number(chain)

try {
const addresses = getOceanArtifactsAdressesByChainId(chainId)
const enterpriseFeeCollectorAddress = addresses?.EnterpriseFeeCollector
CORE_LOGGER.info(
`Validating fee token ${token} on chain ${chainId} with EnterpriseFeeCollector ${enterpriseFeeCollectorAddress}`
)

const blockchain = blockchainRegistry.getBlockchain(chainId) as Blockchain | null
if (!enterpriseFeeCollectorAddress || !blockchain) {
throw new Error('Unable to initialize EnterpriseFeeCollector validation')
}
const signer = await blockchain.getSigner()
const enterpriseFeeCollector = new Contract(
enterpriseFeeCollectorAddress,
EnterpriseFeeCollectorJson.abi,
signer
)
const isAllowed = await enterpriseFeeCollector.isTokenAllowed(token)
if (isAllowed !== true) {
unsupportedFeeTokens.push({ chain, token })
} else {
CORE_LOGGER.info(
`Fee token ${token} on chain ${chainId} with EnterpriseFeeCollector ${enterpriseFeeCollectorAddress} validated`
)
}
} catch {
unsupportedFeeTokens.push({ chain, token })
}
}

return unsupportedFeeTokens
}

export async function assertFeeTokensSupportedByOec(
config: OceanNodeConfig,
blockchainRegistry: BlockchainRegistryLike
): Promise<void> {
const unsupportedFeeTokens = await validateFeeTokensSupportedByOec(
config,
blockchainRegistry
)

if (unsupportedFeeTokens.length > 0) {
throw new Error(
`Unsupported fee token(s) configured in FEE_TOKENS: ${unsupportedFeeTokens
.map(formatFeeToken)
.join('; ')}`
)
}
}
Loading