diff --git a/config.json b/config.json index c8691ff6e..7917ba87c 100644 --- a/config.json +++ b/config.json @@ -1,150 +1,150 @@ { - "authorizedDecrypters": [], - "authorizedDecryptersList": [], - "allowedValidators": [], - "allowedValidatorsList": [], - "authorizedPublishers": [], - "authorizedPublishersList": [], - "keys": {}, - "hasIndexer": true, - "hasHttp": true, - "hasP2P": true, - "p2pConfig": { - "bootstrapTimeout": 20000, - "bootstrapTagName": "bootstrap", - "bootstrapTagValue": 50, - "bootstrapTTL": 0, - "enableIPV4": true, - "enableIPV6": true, - "ipV4BindAddress": "0.0.0.0", - "ipV4BindTcpPort": 9000, - "ipV4BindWsPort": 9001, - "ipV4BindWssPort": 9005, - "ipV6BindAddress": "::", - "ipV6BindTcpPort": 9002, - "ipV6BindWsPort": 9003, - "announceAddresses": [], - "pubsubPeerDiscoveryInterval": 10000, - "dhtMaxInboundStreams": 500, - "dhtMaxOutboundStreams": 500, - "dhtFilter": null, - "mDNSInterval": 20000, - "connectionsMaxParallelDials": 15, - "connectionsDialTimeout": 30000, - "upnp": true, - "autoNat": true, - "enableCircuitRelayServer": false, - "enableCircuitRelayClient": false, - "circuitRelays": 0, - "announcePrivateIp": false, - "filterAnnouncedAddresses": [ - "127.0.0.0/8", - "10.0.0.0/8", - "172.16.0.0/12", - "192.168.0.0/16", - "100.64.0.0/10", - "169.254.0.0/16", - "192.0.0.0/24", - "192.0.2.0/24", - "198.51.100.0/24", - "203.0.113.0/24", - "224.0.0.0/4", - "240.0.0.0/4" - ], - "minConnections": 1, - "maxConnections": 300, - "autoDialPeerRetryThreshold": 7200000, - "autoDialConcurrency": 5, - "maxPeerAddrsToDial": 5, - "autoDialInterval": 5000, - "enableNetworkStats": false - }, - "hasControlPanel": true, - "httpPort": 8001, - "dbConfig": { - "url": "http://localhost:8108/?apiKey=xyz", - "username": "", - "password": "", - "dbType": "typesense" - }, - "supportedNetworks": { - "8996": { - "rpc": "http://127.0.0.1:8545", - "chainId": 8996, - "network": "development", - "chunkSize": 100 - } - }, - "feeStrategy": {}, - "c2dClusters": [], - "ipfsGateway": "https://ipfs.io/", - "arweaveGateway": "https://arweave.net/", - "accountPurgatoryUrl": null, - "assetPurgatoryUrl": null, - "allowedAdmins": [], - "allowedAdminsList": [], - "rateLimit": 30, - "maxConnections": 30, - "denyList": { - "peers": [], - "ips": [] - }, - "unsafeURLs": [], - "isBootstrap": false, - "claimDurationTimeout": 3600, - "validateUnsignedDDO": true, - "jwtSecret": "ocean-node-secret", - "dockerComputeEnvironments": [ - { - "socketPath": "/var/run/docker.sock", - "resources": [ + "authorizedDecrypters": [], + "authorizedDecryptersList": null, + "allowedValidators": [], + "allowedValidatorsList": null, + "authorizedPublishers": [], + "authorizedPublishersList": null, + "keys": {}, + "hasIndexer": true, + "hasHttp": true, + "hasP2P": true, + "p2pConfig": { + "bootstrapTimeout": 20000, + "bootstrapTagName": "bootstrap", + "bootstrapTagValue": 50, + "bootstrapTTL": 0, + "enableIPV4": true, + "enableIPV6": true, + "ipV4BindAddress": "0.0.0.0", + "ipV4BindTcpPort": 9000, + "ipV4BindWsPort": 9001, + "ipV4BindWssPort": 9005, + "ipV6BindAddress": "::", + "ipV6BindTcpPort": 9002, + "ipV6BindWsPort": 9003, + "announceAddresses": [], + "pubsubPeerDiscoveryInterval": 10000, + "dhtMaxInboundStreams": 500, + "dhtMaxOutboundStreams": 500, + "dhtFilter": null, + "mDNSInterval": 20000, + "connectionsMaxParallelDials": 15, + "connectionsDialTimeout": 30000, + "upnp": true, + "autoNat": true, + "enableCircuitRelayServer": false, + "enableCircuitRelayClient": false, + "circuitRelays": 0, + "announcePrivateIp": false, + "filterAnnouncedAddresses": [ + "127.0.0.0/8", + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "100.64.0.0/10", + "169.254.0.0/16", + "192.0.0.0/24", + "192.0.2.0/24", + "198.51.100.0/24", + "203.0.113.0/24", + "224.0.0.0/4", + "240.0.0.0/4" + ], + "minConnections": 1, + "maxConnections": 300, + "autoDialPeerRetryThreshold": 7200000, + "autoDialConcurrency": 5, + "maxPeerAddrsToDial": 5, + "autoDialInterval": 5000, + "enableNetworkStats": false + }, + "hasControlPanel": true, + "httpPort": 8001, + "dbConfig": { + "url": "http://localhost:8108/?apiKey=xyz", + "username": "", + "password": "", + "dbType": "typesense" + }, + "supportedNetworks": { + "8996": { + "rpc": "http://127.0.0.1:8545", + "chainId": 8996, + "network": "development", + "chunkSize": 100 + } + }, + "feeStrategy": {}, + "c2dClusters": [], + "ipfsGateway": "https://ipfs.io/", + "arweaveGateway": "https://arweave.net/", + "accountPurgatoryUrl": null, + "assetPurgatoryUrl": null, + "allowedAdmins": [], + "allowedAdminsList": null, + "rateLimit": 30, + "maxConnections": 30, + "denyList": { + "peers": [], + "ips": [] + }, + "unsafeURLs": [], + "isBootstrap": false, + "claimDurationTimeout": 3600, + "validateUnsignedDDO": true, + "jwtSecret": "ocean-node-secret", + "dockerComputeEnvironments": [ { - "id": "disk", - "total": 1 + "socketPath": "/var/run/docker.sock", + "resources": [ + { + "id": "disk", + "total": 1 + } + ], + "storageExpiry": 604800, + "maxJobDuration": 3600, + "minJobDuration": 60, + "access": { + "addresses": [], + "accessLists": [] + }, + "fees": { + "8996": [ + { + "prices": [ + { + "id": "cpu", + "price": 1 + } + ] + } + ] + }, + "free": { + "maxJobDuration": 3600, + "minJobDuration": 60, + "maxJobs": 3, + "access": { + "addresses": [], + "accessLists": [] + }, + "resources": [ + { + "id": "cpu", + "max": 1 + }, + { + "id": "ram", + "max": 1 + }, + { + "id": "disk", + "max": 1 + } + ] + } } - ], - "storageExpiry": 604800, - "maxJobDuration": 3600, - "minJobDuration": 60, - "access": { - "addresses": [], - "accessLists": [] - }, - "fees": { - "8996": [ - { - "prices": [ - { - "id": "cpu", - "price": 1 - } - ] - } - ] - }, - "free": { - "maxJobDuration": 3600, - "minJobDuration": 60, - "maxJobs": 3, - "access": { - "addresses": [], - "accessLists": [] - }, - "resources": [ - { - "id": "cpu", - "max": 1 - }, - { - "id": "ram", - "max": 1 - }, - { - "id": "disk", - "max": 1 - } - ] - } - } - ] -} + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3659dcd24..78012a107 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@libp2p/websockets": "^8.1.1", "@multiformats/multiaddr": "^10.2.0", "@oceanprotocol/contracts": "^2.5.0", - "@oceanprotocol/ddo-js": "^0.1.4", + "@oceanprotocol/ddo-js": "^0.2.0", "axios": "^1.12.0", "base58-js": "^2.0.0", "cors": "^2.8.5", @@ -4280,9 +4280,9 @@ "license": "Apache-2.0" }, "node_modules/@oceanprotocol/ddo-js": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@oceanprotocol/ddo-js/-/ddo-js-0.1.4.tgz", - "integrity": "sha512-+9nOslJCYMiIldIQ8dixdSjBjrrlqzb9q+nRQXqn60ZIi45AoR4ajv1LdqPwScCmoeLZgMxiZsnUXq/suDSLNg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@oceanprotocol/ddo-js/-/ddo-js-0.2.0.tgz", + "integrity": "sha512-Qy6mRY72KBf7k8u/sbbvm7TT+0d686Pn4ICg6VskzMfPnfFg9ObBCf0ejon6yyx1gMwQ+h5/8NYX51NdYPcEVw==", "license": "Apache-2.0", "dependencies": { "@rdfjs/formats-common": "^3.1.0", diff --git a/package.json b/package.json index 8e5ce703c..3df685662 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@libp2p/websockets": "^8.1.1", "@multiformats/multiaddr": "^10.2.0", "@oceanprotocol/contracts": "^2.5.0", - "@oceanprotocol/ddo-js": "^0.1.4", + "@oceanprotocol/ddo-js": "^0.2.0", "axios": "^1.12.0", "base58-js": "^2.0.0", "cors": "^2.8.5", @@ -159,4 +159,4 @@ "publish": false } } -} \ No newline at end of file +} diff --git a/src/@types/C2D/C2D.ts b/src/@types/C2D/C2D.ts index be10b82c3..4182c2fc2 100644 --- a/src/@types/C2D/C2D.ts +++ b/src/@types/C2D/C2D.ts @@ -82,7 +82,7 @@ export interface RunningPlatform { export interface ComputeAccessList { addresses: string[] - accessLists: string[] + accessLists: { [chainId: string]: string[] } } export interface ComputeEnvironmentFreeOptions { diff --git a/src/@types/DDO/Credentials.ts b/src/@types/DDO/Credentials.ts index dc7c0dcc6..5044541ec 100644 --- a/src/@types/DDO/Credentials.ts +++ b/src/@types/DDO/Credentials.ts @@ -1 +1,5 @@ -export const KNOWN_CREDENTIALS_TYPES = ['address', 'accessList'] +// Supported credential types enum-like object for easier use in code +export const CREDENTIALS_TYPES = { + ADDRESS: 'address' as const, + ACCESS_LIST: 'accessList' as const +} diff --git a/src/@types/OceanNode.ts b/src/@types/OceanNode.ts index f949a9b72..1f43ece68 100644 --- a/src/@types/OceanNode.ts +++ b/src/@types/OceanNode.ts @@ -160,7 +160,7 @@ export interface OceanNodeStatus { platform: any uptime?: number // seconds since start codeHash?: string - allowedAdmins?: string[] + allowedAdmins?: { addresses: string[]; accessLists: AccessListContract } // detailed information c2dClusters?: any[] supportedSchemas?: Schema[] diff --git a/src/components/c2d/compute_engine_docker.ts b/src/components/c2d/compute_engine_docker.ts index 68c65a30b..9d7966800 100644 --- a/src/components/c2d/compute_engine_docker.ts +++ b/src/components/c2d/compute_engine_docker.ts @@ -170,7 +170,7 @@ export class C2DEngineDocker extends C2DEngine { }, access: { addresses: [], - accessLists: [] + accessLists: null }, fees, queuedJobs: 0, @@ -249,7 +249,7 @@ export class C2DEngineDocker extends C2DEngine { this.envs[0].free = { access: { addresses: [], - accessLists: [] + accessLists: null } } if (`access` in envConfig.free) this.envs[0].free.access = envConfig.free.access diff --git a/src/components/core/compute/initialize.ts b/src/components/core/compute/initialize.ts index 11d92f595..586570669 100644 --- a/src/components/core/compute/initialize.ts +++ b/src/components/core/compute/initialize.ts @@ -31,7 +31,7 @@ import { isOrderingAllowedForAsset } from '../handler/downloadHandler.js' import { getNonceAsNumber } from '../utils/nonceHandler.js' import { C2DEngineDocker, getAlgorithmImage } from '../../c2d/compute_engine_docker.js' import { Credentials, DDOManager } from '@oceanprotocol/ddo-js' -import { areKnownCredentialTypes, checkCredentials } from '../../../utils/credentials.js' +import { checkCredentials } from '../../../utils/credentials.js' import { PolicyServer } from '../../policyServer/index.js' import { generateUniqueID, getAlgoChecksums, validateAlgoForDataset } from './utils.js' @@ -269,6 +269,20 @@ export class ComputeInitializeHandler extends CommandHandler { } } } + const config = await getConfiguration() + const { rpc, network, chainId, fallbackRPCs } = + config.supportedNetworks[ddoChainId] + const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + const { ready, error } = await blockchain.isNetworkReady() + if (!ready) { + return { + stream: null, + status: { + httpStatus: 400, + error: `Initialize Compute: ${error}` + } + } + } // check credentials (DDO level) let accessGrantedDDOLevel: boolean if (credentials) { @@ -284,9 +298,11 @@ export class ComputeInitializeHandler extends CommandHandler { ) accessGrantedDDOLevel = response.success } else { - accessGrantedDDOLevel = areKnownCredentialTypes(credentials as Credentials) - ? checkCredentials(credentials as Credentials, task.consumerAddress) - : true + accessGrantedDDOLevel = await checkCredentials( + task.consumerAddress, + credentials as Credentials, + blockchain.getSigner() + ) } if (!accessGrantedDDOLevel) { CORE_LOGGER.logMessage( @@ -329,9 +345,11 @@ export class ComputeInitializeHandler extends CommandHandler { ) accessGrantedServiceLevel = accessGrantedDDOLevel || response.success } else { - accessGrantedServiceLevel = areKnownCredentialTypes(service.credentials) - ? checkCredentials(service.credentials, task.consumerAddress) - : true + accessGrantedServiceLevel = await checkCredentials( + task.consumerAddress, + service.credentials, + blockchain.getSigner() + ) } if (!accessGrantedServiceLevel) { @@ -348,20 +366,6 @@ export class ComputeInitializeHandler extends CommandHandler { } } } - const config = await getConfiguration() - const { rpc, network, chainId, fallbackRPCs } = - config.supportedNetworks[ddoChainId] - const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) - const { ready, error } = await blockchain.isNetworkReady() - if (!ready) { - return { - stream: null, - status: { - httpStatus: 400, - error: `Initialize Compute: ${error}` - } - } - } // docker images? const clusters = config.c2dClusters diff --git a/src/components/core/compute/startCompute.ts b/src/components/core/compute/startCompute.ts index f818ac545..4a4697890 100644 --- a/src/components/core/compute/startCompute.ts +++ b/src/components/core/compute/startCompute.ts @@ -37,11 +37,9 @@ import { isOrderingAllowedForAsset } from '../handler/downloadHandler.js' import { Credentials, DDOManager } from '@oceanprotocol/ddo-js' import { getNonceAsNumber } from '../utils/nonceHandler.js' import { PolicyServer } from '../../policyServer/index.js' -import { - areKnownCredentialTypes, - checkCredentials, - findAccessListCredentials -} from '../../../utils/credentials.js' +import { checkCredentials } from '../../../utils/credentials.js' +import { checkAddressOnAccessList } from '../../../utils/accessList.js' + export class PaidComputeStartHandler extends CommandHandler { validate(command: PaidComputeStartCommand): ValidateParams { const commandValidation = validateCommandParameters(command, [ @@ -221,6 +219,22 @@ export class PaidComputeStartHandler extends CommandHandler { } } } + const config = await getConfiguration() + const { rpc, network, chainId, fallbackRPCs } = + config.supportedNetworks[ddoChainId] + const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + const { ready, error } = await blockchain.isNetworkReady() + if (!ready) { + return { + stream: null, + status: { + httpStatus: 400, + error: `Start Compute : ${error}` + } + } + } + + const signer = blockchain.getSigner() // check credentials (DDO level) let accessGrantedDDOLevel: boolean if (credentials) { @@ -236,9 +250,11 @@ export class PaidComputeStartHandler extends CommandHandler { ) accessGrantedDDOLevel = response.success } else { - accessGrantedDDOLevel = areKnownCredentialTypes(credentials as Credentials) - ? checkCredentials(credentials as Credentials, task.consumerAddress) - : true + accessGrantedDDOLevel = await checkCredentials( + task.consumerAddress, + credentials as Credentials, + blockchain.getSigner() + ) } if (!accessGrantedDDOLevel) { CORE_LOGGER.logMessage( @@ -281,9 +297,11 @@ export class PaidComputeStartHandler extends CommandHandler { ) accessGrantedServiceLevel = accessGrantedDDOLevel || response.success } else { - accessGrantedServiceLevel = areKnownCredentialTypes(service.credentials) - ? checkCredentials(service.credentials, task.consumerAddress) - : true + accessGrantedServiceLevel = await checkCredentials( + task.consumerAddress, + service.credentials, + blockchain.getSigner() + ) } if (!accessGrantedServiceLevel) { @@ -301,22 +319,6 @@ export class PaidComputeStartHandler extends CommandHandler { } } - const config = await getConfiguration() - const { rpc, network, chainId, fallbackRPCs } = - config.supportedNetworks[ddoChainId] - const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) - const { ready, error } = await blockchain.isNetworkReady() - if (!ready) { - return { - stream: null, - status: { - httpStatus: 400, - error: `Start Compute : ${error}` - } - } - } - - const signer = blockchain.getSigner() // let's see if we can access this asset // check if oasis evm or similar const confidentialEVM = isConfidentialChainDDO(BigInt(ddo.chainId), service) @@ -668,7 +670,33 @@ export class FreeComputeStartHandler extends CommandHandler { } } const ddoInstance = DDOManager.getDDOClass(ddo) - const { credentials } = ddoInstance.getDDOFields() + const { chainId: ddoChainId, credentials } = ddoInstance.getDDOFields() + const isOrdable = isOrderingAllowedForAsset(ddo) + if (!isOrdable.isOrdable) { + CORE_LOGGER.error(isOrdable.reason) + return { + stream: null, + status: { + httpStatus: 500, + error: isOrdable.reason + } + } + } + const config = await getConfiguration() + const { rpc, network, chainId, fallbackRPCs } = + config.supportedNetworks[ddoChainId] + const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + const { ready, error } = await blockchain.isNetworkReady() + if (!ready) { + return { + stream: null, + status: { + httpStatus: 400, + error: `Start Compute : ${error}` + } + } + } + // check credentials (DDO level) let accessGrantedDDOLevel: boolean if (credentials) { @@ -684,9 +712,11 @@ export class FreeComputeStartHandler extends CommandHandler { ) accessGrantedDDOLevel = response.success } else { - accessGrantedDDOLevel = areKnownCredentialTypes(credentials as Credentials) - ? checkCredentials(credentials as Credentials, task.consumerAddress) - : true + accessGrantedDDOLevel = await checkCredentials( + task.consumerAddress, + credentials as Credentials, + blockchain.getSigner() + ) } if (!accessGrantedDDOLevel) { CORE_LOGGER.logMessage( @@ -729,9 +759,11 @@ export class FreeComputeStartHandler extends CommandHandler { ) accessGrantedServiceLevel = accessGrantedDDOLevel || response.success } else { - accessGrantedServiceLevel = areKnownCredentialTypes(service.credentials) - ? checkCredentials(service.credentials, task.consumerAddress) - : true + accessGrantedServiceLevel = await checkCredentials( + task.consumerAddress, + service.credentials, + blockchain.getSigner() + ) } if (!accessGrantedServiceLevel) { @@ -874,7 +906,10 @@ async function validateAccess( return true } - if (access.accessLists.length === 0 && access.addresses.length === 0) { + if ( + !access.accessLists || + (Object.keys(access.accessLists).length === 0 && access.addresses.length === 0) + ) { return true } @@ -882,32 +917,28 @@ async function validateAccess( return true } - if (access.accessLists.length > 0) { - const config = await getConfiguration() - const { supportedNetworks } = config - - for (const accessListAddress of access.accessLists) { - for (const chainIdStr of Object.keys(supportedNetworks)) { - const { rpc, network, chainId, fallbackRPCs } = supportedNetworks[chainIdStr] - try { - const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) - const signer = blockchain.getSigner() - - const hasAccess = await findAccessListCredentials( - signer, - accessListAddress, - consumerAddress - ) - if (hasAccess) { - return true - } - } catch (error) { - CORE_LOGGER.logMessage( - `Failed to check access list ${accessListAddress} on chain ${chainIdStr}: ${error.message}`, - true - ) + const config = await getConfiguration() + const { supportedNetworks } = config + for (const chain of Object.keys(access.accessLists)) { + const { rpc, network, chainId, fallbackRPCs } = supportedNetworks[chain] + try { + const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) + const signer = blockchain.getSigner() + for (const accessListAddress of access.accessLists[chain]) { + const hasAccess = await checkAddressOnAccessList( + accessListAddress, + consumerAddress, + signer + ) + if (hasAccess) { + return true } } + } catch (error) { + CORE_LOGGER.logMessage( + `Failed to check access lists on chain ${chain}: ${error.message}`, + true + ) } } diff --git a/src/components/core/handler/downloadHandler.ts b/src/components/core/handler/downloadHandler.ts index 92aa2d7ea..b651c6845 100644 --- a/src/components/core/handler/downloadHandler.ts +++ b/src/components/core/handler/downloadHandler.ts @@ -21,7 +21,7 @@ import { getConfiguration, isPolicyServerConfigured } from '../../../utils/index.js' -import { areKnownCredentialTypes, checkCredentials } from '../../../utils/credentials.js' +import { checkCredentials } from '../../../utils/credentials.js' import { CORE_LOGGER } from '../../../utils/logging/common.js' import { OceanNode } from '../../../OceanNode.js' import { DownloadCommand, DownloadURLCommand } from '../../../@types/commands.js' @@ -264,41 +264,7 @@ export class DownloadHandler extends CommandHandler { } } - // check credentials (DDO level) - let accessGrantedDDOLevel: boolean - if (credentials) { - // if POLICY_SERVER_URL exists, then ocean-node will NOT perform any checks. - // It will just use the existing code and let PolicyServer decide. - if (isPolicyServerConfigured()) { - const response = await policyServer.checkDownload( - ddoInstance.getDid(), - ddo, - task.serviceId, - task.consumerAddress, - task.policyServer - ) - accessGrantedDDOLevel = response.success - } else { - accessGrantedDDOLevel = areKnownCredentialTypes(credentials as Credentials) - ? checkCredentials(credentials as Credentials, task.consumerAddress) - : true - } - if (!accessGrantedDDOLevel) { - CORE_LOGGER.logMessage( - `Error: Access to asset ${ddoInstance.getDid()} was denied`, - true - ) - return { - stream: null, - status: { - httpStatus: 403, - error: `Error: Access to asset ${ddoInstance.getDid()} was denied` - } - } - } - } - - // from now on, we need blockchain checks + // Initialize blockchain early (needed for credential checks with accessList) const config = await getConfiguration() const { rpc, network, chainId, fallbackRPCs } = config.supportedNetworks[ddoChainId] let provider @@ -339,6 +305,43 @@ export class DownloadHandler extends CommandHandler { } } } + + // check credentials (DDO level) + let accessGrantedDDOLevel: boolean + if (credentials) { + // if POLICY_SERVER_URL exists, then ocean-node will NOT perform any checks. + // It will just use the existing code and let PolicyServer decide. + if (isPolicyServerConfigured()) { + const response = await policyServer.checkDownload( + ddoInstance.getDid(), + ddo, + task.serviceId, + task.consumerAddress, + task.policyServer + ) + accessGrantedDDOLevel = response.success + } else { + accessGrantedDDOLevel = await checkCredentials( + task.consumerAddress, + credentials as Credentials, + blockchain.getSigner() + ) + } + if (!accessGrantedDDOLevel) { + CORE_LOGGER.logMessage( + `Error: Access to asset ${ddoInstance.getDid()} was denied`, + true + ) + return { + stream: null, + status: { + httpStatus: 403, + error: `Error: Access to asset ${ddoInstance.getDid()} was denied` + } + } + } + } + let service = AssetUtils.getServiceById(ddo, task.serviceId) if (!service) service = AssetUtils.getServiceByIndex(ddo, Number(task.serviceId)) if (!service) throw new Error('Cannot find service') @@ -359,9 +362,11 @@ export class DownloadHandler extends CommandHandler { ) accessGrantedServiceLevel = accessGrantedDDOLevel || response.success } else { - accessGrantedServiceLevel = areKnownCredentialTypes(service.credentials) - ? checkCredentials(service.credentials, task.consumerAddress) - : true + accessGrantedServiceLevel = await checkCredentials( + task.consumerAddress, + service.credentials, + blockchain.getSigner() + ) } if (!accessGrantedServiceLevel) { diff --git a/src/components/core/utils/statusHandler.ts b/src/components/core/utils/statusHandler.ts index c07c4e046..6db76df32 100644 --- a/src/components/core/utils/statusHandler.ts +++ b/src/components/core/utils/statusHandler.ts @@ -139,14 +139,18 @@ export async function status( // depends on request if (detailed) { nodeStatus.c2dClusters = [] - const engines = await oceanNode.getC2DEngines().getAllEngines() - for (const engine of engines) { - const type = await engine.getC2DType() - nodeStatus.c2dClusters.push({ - type, - hash: await engine.getC2DConfig().hash, - environments: await engine.getComputeEnvironments() - }) + try { + const engines = await oceanNode.getC2DEngines().getAllEngines() + for (const engine of engines) { + const type = await engine.getC2DType() + nodeStatus.c2dClusters.push({ + type, + hash: await engine.getC2DConfig().hash, + environments: await engine.getComputeEnvironments() + }) + } + } catch (error) { + CORE_LOGGER.log(LOG_LEVELS_STR.LEVEL_ERROR, `Error getting c2d clusters: ${error}`) } nodeStatus.supportedSchemas = typesenseSchemas.ddoSchemas } diff --git a/src/test/data/assets.ts b/src/test/data/assets.ts index 3758f079f..532415d48 100644 --- a/src/test/data/assets.ts +++ b/src/test/data/assets.ts @@ -1,4 +1,5 @@ -import { Credentials, CREDENTIALS_TYPES } from '@oceanprotocol/ddo-js' +import { Credentials } from '@oceanprotocol/ddo-js' +import { CREDENTIALS_TYPES } from '../../@types/DDO/Credentials.js' export const downloadAsset = { '@context': ['https://w3id.org/did/v1'], @@ -10,14 +11,12 @@ export const downloadAsset = { created: '2021-12-20T14:35:20Z', updated: '2021-12-20T14:35:20Z', type: 'dataset', - name: 'cli fixed asset', - description: 'asset published using ocean.js cli tool', + name: 'downloadAsset', + description: 'download asset with no credentials', tags: ['test'], author: 'oceanprotocol', license: 'https://market.oceanprotocol.com/terms', - additionalInformation: { - termsAndConditions: true - } + additionalInformation: { termsAndConditions: true } }, services: [ { @@ -47,19 +46,31 @@ export const downloadAsset = { owner: '', created: '' }, - stats: { - orders: 0, - price: { - value: '0' - } - }, - purgatory: { - state: false - }, + stats: { orders: 0, price: { value: '0' } }, + purgatory: { state: false }, datatokens: [] as any } const nftLevelCredentials: Credentials = { + match_allow: 'any', + allow: [ + { + type: CREDENTIALS_TYPES.ADDRESS, + values: ['0xBE5449a6A97aD46c8558A3356267Ee5D2731ab5e'] + }, + { + type: CREDENTIALS_TYPES.ADDRESS, + values: ['0xA78deb2Fa79463945C247991075E2a0e98Ba7A09'] + } + ], + deny: [ + { + type: CREDENTIALS_TYPES.ADDRESS, + values: ['0x02354A1F160A3fd7ac8b02ee91F04104440B28E7'] + } + ] +} +const nftLevelCredentialsWithMatchAll: Credentials = { allow: [ { type: CREDENTIALS_TYPES.ADDRESS, @@ -98,14 +109,12 @@ export const downloadAssetWithCredentials = { created: '2021-12-20T14:35:20Z', updated: '2021-12-20T14:35:20Z', type: 'dataset', - name: 'cli fixed asset', - description: 'asset published using ocean.js cli tool', + name: 'downloadAssetWithCredentials', + description: 'asset with credentials match any', tags: ['test'], author: 'oceanprotocol', license: 'https://market.oceanprotocol.com/terms', - additionalInformation: { - termsAndConditions: true - } + additionalInformation: { termsAndConditions: true } }, credentials: nftLevelCredentials, services: [ @@ -137,17 +146,61 @@ export const downloadAssetWithCredentials = { owner: '', created: '' }, - purgatory: { - state: false - }, + purgatory: { state: false }, datatokens: [] as any, - stats: { - allocated: 0, - orders: 0, - price: { - value: '0' + stats: { allocated: 0, orders: 0, price: { value: '0' } } +} + +export const downloadAssetWithCredentialsWithMatchAll = { + '@context': ['https://w3id.org/did/v1'], + id: '', + nftAddress: '', + version: '4.1.0', + chainId: 8996, + metadata: { + created: '2021-12-20T14:35:20Z', + updated: '2021-12-20T14:35:20Z', + type: 'dataset', + name: 'downloadAssetWithCredentialsWithMatchAll', + description: 'asset with credentials match all', + tags: ['test'], + author: 'oceanprotocol', + license: 'https://market.oceanprotocol.com/terms', + additionalInformation: { termsAndConditions: true } + }, + credentials: nftLevelCredentialsWithMatchAll, + services: [ + { + id: 'ccb398c50d6abd5b456e8d7242bd856a1767a890b537c2f8c10ba8b8a10e6025', + type: 'access', + files: { + files: [ + { + type: 'url', + url: 'https://raw.githubusercontent.com/oceanprotocol/testdatasets/main/shs_dataset_test.txt', + method: 'GET' + } + ] + }, + credentials: serviceLevelCredentials, + datatokenAddress: '', + serviceEndpoint: 'https://v4.provider.oceanprotocol.com', + timeout: 86400 } - } + ], + event: {}, + nft: { + address: '', + name: 'Ocean Data NFT', + symbol: 'OCEAN-NFT', + state: 5, + tokenURI: '', + owner: '', + created: '' + }, + purgatory: { state: false }, + datatokens: [] as any, + stats: { allocated: 0, orders: 0, price: { value: '0' } } } export const computeAssetWithCredentials = { @@ -160,14 +213,12 @@ export const computeAssetWithCredentials = { created: '2021-12-20T14:35:20Z', updated: '2021-12-20T14:35:20Z', type: 'dataset', - name: 'cli fixed asset', - description: 'asset published using ocean.js cli tool', + name: 'computeAssetWithCredentials', + description: 'compute asset with credentials match any', tags: ['test'], author: 'oceanprotocol', license: 'https://market.oceanprotocol.com/terms', - additionalInformation: { - termsAndConditions: true - } + additionalInformation: { termsAndConditions: true } }, credentials: nftLevelCredentials, services: [ @@ -192,11 +243,7 @@ export const computeAssetWithCredentials = { allowNetworkAccess: true, publisherTrustedAlgorithmPublishers: ['*'] as any, publisherTrustedAlgorithms: [ - { - did: '*', - filesChecksum: '*', - containerSectionChecksum: '*' - } + { did: '*', filesChecksum: '*', containerSectionChecksum: '*' } ] as any } } @@ -211,17 +258,9 @@ export const computeAssetWithCredentials = { owner: '', created: '' }, - purgatory: { - state: false - }, + purgatory: { state: false }, datatokens: [] as any, - stats: { - allocated: 0, - orders: 0, - price: { - value: '0' - } - } + stats: { allocated: 0, orders: 0, price: { value: '0' } } } export const algoAssetWithCredentials = { @@ -234,13 +273,11 @@ export const algoAssetWithCredentials = { created: '2023-08-01T17:09:39Z', updated: '2023-08-01T17:09:39Z', type: 'algorithm', - name: 'CLi Algo', - description: 'Cli algo', + name: 'algoAssetWithCredentials', + description: 'algo asset with credentials', author: 'OPF', license: 'https://market.oceanprotocol.com/terms', - additionalInformation: { - termsAndConditions: true - }, + additionalInformation: { termsAndConditions: true }, algorithm: { language: '', version: '0.1', @@ -274,13 +311,7 @@ export const algoAssetWithCredentials = { serviceEndpoint: 'https://v4.provider.oceanprotocol.com' } ], - stats: { - allocated: 0, - orders: 0, - price: { - value: '0' - } - }, + stats: { allocated: 0, orders: 0, price: { value: '0' } }, nft: { address: '', name: 'Ocean Data NFT', @@ -302,14 +333,12 @@ export const computeAsset = { created: '2021-12-20T14:35:20Z', updated: '2021-12-20T14:35:20Z', type: 'dataset', - name: 'cli fixed asset', - description: 'asset published using ocean.js cli tool', + name: 'computeAsset', + description: 'compute asset with no credentials', tags: ['test'], author: 'oceanprotocol', license: 'https://market.oceanprotocol.com/terms', - additionalInformation: { - termsAndConditions: true - } + additionalInformation: { termsAndConditions: true } }, services: [ { @@ -332,11 +361,7 @@ export const computeAsset = { allowNetworkAccess: true, publisherTrustedAlgorithmPublishers: ['*'] as any, publisherTrustedAlgorithms: [ - { - did: '*', - filesChecksum: '*', - containerSectionChecksum: '*' - } + { did: '*', filesChecksum: '*', containerSectionChecksum: '*' } ] as any } } @@ -351,17 +376,9 @@ export const computeAsset = { owner: '', created: '' }, - purgatory: { - state: false - }, + purgatory: { state: false }, datatokens: [] as any, - stats: { - allocated: 0, - orders: 0, - price: { - value: '0' - } - } + stats: { allocated: 0, orders: 0, price: { value: '0' } } } export const computeAssetWithNoAccess = { @@ -374,14 +391,12 @@ export const computeAssetWithNoAccess = { created: '2021-12-20T14:35:20Z', updated: '2021-12-20T14:35:20Z', type: 'dataset', - name: 'cli fixed asset', - description: 'asset published using ocean.js cli tool', + name: 'computeAssetWithNoAccess', + description: 'compute asset with no access', tags: ['test'], author: 'oceanprotocol', license: 'https://market.oceanprotocol.com/terms', - additionalInformation: { - termsAndConditions: true - } + additionalInformation: { termsAndConditions: true } }, services: [ { @@ -417,17 +432,9 @@ export const computeAssetWithNoAccess = { owner: '', created: '' }, - purgatory: { - state: false - }, + purgatory: { state: false }, datatokens: [] as any, - stats: { - allocated: 0, - orders: 0, - price: { - value: '0' - } - } + stats: { allocated: 0, orders: 0, price: { value: '0' } } } export const algoAsset = { @@ -440,13 +447,11 @@ export const algoAsset = { created: '2023-08-01T17:09:39Z', updated: '2023-08-01T17:09:39Z', type: 'algorithm', - name: 'CLi Algo', - description: 'Cli algo', + name: 'algoAsset', + description: 'algoAsset', author: 'OPF', license: 'https://market.oceanprotocol.com/terms', - additionalInformation: { - termsAndConditions: true - }, + additionalInformation: { termsAndConditions: true }, algorithm: { language: '', version: '0.1', @@ -478,13 +483,7 @@ export const algoAsset = { serviceEndpoint: 'https://v4.provider.oceanprotocol.com' } ], - stats: { - allocated: 0, - orders: 0, - price: { - value: '0' - } - }, + stats: { allocated: 0, orders: 0, price: { value: '0' } }, nft: { address: '', name: 'Ocean Data NFT', @@ -544,10 +543,7 @@ export const dockerImageManifest = { size: 7286, digest: 'sha256:386e0be86bde5eff9f85ea9eda02727dd4641664d746688b4049f79ef0cdb1c9' }, - platform: { - architecture: 'amd64', - os: 'linux' - }, + platform: { architecture: 'amd64', os: 'linux' }, layers: [ { mediaType: 'application/vnd.docker.image.rootfs.diff.tar.gzip', diff --git a/src/test/integration/accessLists.test.ts b/src/test/integration/accessLists.test.ts index 3445f7bdb..4c371b498 100644 --- a/src/test/integration/accessLists.test.ts +++ b/src/test/integration/accessLists.test.ts @@ -16,7 +16,7 @@ import { AccessListContract, OceanNodeConfig } from '../../@types/OceanNode.js' import { homedir } from 'os' import { getConfiguration } from '../../utils/config.js' import { assert, expect } from 'chai' -import { findAccessListCredentials } from '../../utils/credentials.js' +import { checkAddressOnAccessList } from '../../utils/accessList.js' describe('Should deploy some accessLists before all other tests.', () => { let config: OceanNodeConfig @@ -175,7 +175,7 @@ describe('Should deploy some accessLists before all other tests.', () => { for (let i = 0; i < wallets.length; i++) { const account = await wallets[i].getAddress() expect( - (await findAccessListCredentials(owner, account, accessListAddress)) === true, + (await checkAddressOnAccessList(accessListAddress, account, owner)) === true, `Address ${account} has no balance on Access List ${accessListAddress}, so its not Authorized` ) } @@ -188,7 +188,7 @@ describe('Should deploy some accessLists before all other tests.', () => { for (let i = wallets.length; i < 4; i++) { const account = await (await provider.getSigner(i)).getAddress() expect( - (await findAccessListCredentials(owner, account, accessListAddress)) === false, + (await checkAddressOnAccessList(accessListAddress, account, owner)) === false, `Address ${account} should not be part Access List ${accessListAddress}, therefore its not Authorized` ) } diff --git a/src/test/integration/compute.test.ts b/src/test/integration/compute.test.ts index c76154bca..c60ff4d93 100644 --- a/src/test/integration/compute.test.ts +++ b/src/test/integration/compute.test.ts @@ -1654,15 +1654,45 @@ describe('Compute Access Restrictions', () => { '0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58', JSON.stringify(['0xe2DD09d719Da89e5a3D0F2549c7E24566e947260']), `${homedir}/.ocean/ocean-contracts/artifacts/address.json`, - '[{"socketPath":"/var/run/docker.sock","resources":[{"id":"disk","total":10}],"storageExpiry":604800,"maxJobDuration":3600,"minJobDuration":60,"access":{"addresses":[],"accessLists":["' + - accessListAddress + - '"]},"fees":{"' + - DEVELOPMENT_CHAIN_ID + - '":[{"feeToken":"' + - paymentToken + - '","prices":[{"id":"cpu","price":1}]}]},"free":{"maxJobDuration":60,"minJobDuration":10,"maxJobs":3,"access":{"addresses":[],"accessLists":["' + - accessListAddress + - '"]},"resources":[{"id":"cpu","max":1},{"id":"ram","max":1},{"id":"disk","max":1}]}}]' + JSON.stringify([ + { + socketPath: '/var/run/docker.sock', + resources: [{ id: 'disk', total: 10 }], + storageExpiry: 604800, + maxJobDuration: 3600, + minJobDuration: 60, + access: { + addresses: [], + accessLists: { + [DEVELOPMENT_CHAIN_ID]: [accessListAddress] + } + }, + fees: { + [DEVELOPMENT_CHAIN_ID]: [ + { + feeToken: paymentToken, + prices: [{ id: 'cpu', price: 1 }] + } + ] + }, + free: { + maxJobDuration: 60, + minJobDuration: 10, + maxJobs: 3, + access: { + addresses: [], + accessLists: { + DEVELOPMENT_CHAIN_ID: [accessListAddress] + } + }, + resources: [ + { id: 'cpu', max: 1 }, + { id: 'ram', max: 1 }, + { id: 'disk', max: 1 } + ] + } + } + ]) ] ) ) @@ -1697,7 +1727,7 @@ describe('Compute Access Restrictions', () => { firstEnv = computeEnvironments[0] assert(firstEnv.access, 'Access control should exist') assert( - firstEnv.access.accessLists.includes(accessListAddress), + firstEnv.access.accessLists[DEVELOPMENT_CHAIN_ID].includes(accessListAddress), 'Should have access list address' ) }) @@ -1709,6 +1739,7 @@ describe('Compute Access Restrictions', () => { firstEnv.id ) const response = await new PaidComputeStartHandler(oceanNode).handle(command) + console.log(response) expect(response.status.httpStatus).to.not.equal(403) }) @@ -1719,6 +1750,7 @@ describe('Compute Access Restrictions', () => { firstEnv.id ) const response = await new PaidComputeStartHandler(oceanNode).handle(command) + console.log(response) assert( response.status.httpStatus === 403, `Expected 403 but got ${response.status.httpStatus}: ${response.status.error}` @@ -1732,6 +1764,7 @@ describe('Compute Access Restrictions', () => { firstEnv.id ) const response = await new FreeComputeStartHandler(oceanNode).handle(command) + console.log(response) expect(response.status.httpStatus).to.not.equal(403) }) diff --git a/src/test/integration/credentials.test.ts b/src/test/integration/credentials.test.ts index 4caf05b6f..6ba6bed76 100644 --- a/src/test/integration/credentials.test.ts +++ b/src/test/integration/credentials.test.ts @@ -54,7 +54,8 @@ import { publishAsset, orderAsset } from '../utils/assets.js' import { algoAsset, computeAssetWithCredentials, - downloadAssetWithCredentials + downloadAssetWithCredentials, + downloadAssetWithCredentialsWithMatchAll } from '../data/assets.js' import { ganachePrivateKeys } from '../utils/addresses.js' import { homedir } from 'os' @@ -78,13 +79,15 @@ describe('[Credentials Flow] - Should run a complete node flow.', () => { let consumerAddresses: string[] let ddo: any + let ddoWithMatchAll: any let computeDdo: any let algoDdo: any let did: string + let didWithMatchAll: string let computeDid: string let algoDid: string const orderTxIds: string[] = [] - + const orderTxIdsWithMatchAll: string[] = [] const mockSupportedNetworks: RPCS = getMockSupportedNetworks() let previousConfiguration: OverrideEnvConfig[] @@ -209,6 +212,10 @@ describe('[Credentials Flow] - Should run a complete node flow.', () => { downloadAssetWithCredentials, publisherAccount ) + const publishedDatasetWithMatchAll = await publishAsset( + downloadAssetWithCredentialsWithMatchAll, + publisherAccount + ) const publishedComputeDataset = await publishAsset( computeAssetWithCredentials, @@ -226,6 +233,16 @@ describe('[Credentials Flow] - Should run a complete node flow.', () => { if (!ddo) { assert(wasTimeout === true, 'published failed due to timeout!') } + didWithMatchAll = publishedDatasetWithMatchAll.ddo.id + const resolvedDdoWithMatchAll = await waitToIndex( + didWithMatchAll, + EVENTS.METADATA_CREATED, + DEFAULT_TEST_TIMEOUT * 3 + ) + if (!resolvedDdoWithMatchAll.ddo) { + assert(wasTimeout === true, 'published failed due to timeout!') + } + ddoWithMatchAll = resolvedDdoWithMatchAll.ddo computeDid = publishedComputeDataset.ddo.id const resolvedComputeDdo = await waitToIndex( @@ -295,9 +312,62 @@ describe('[Credentials Flow] - Should run a complete node flow.', () => { assert(txHash, `transaction id not found for consumer ${i}`) orderTxIds.push(txHash) } + for (let i = 0; i < 3; i++) { + const orderTxReceipt = await orderAsset( + ddoWithMatchAll, + 0, + consumerAccounts[i], + consumerAddresses[i], + publisherAccount, + oceanNode + ) + assert(orderTxReceipt, `order transaction for consumer ${i} failed`) + const txHash = orderTxReceipt.hash + assert(txHash, `transaction id not found for consumer ${i}`) + orderTxIdsWithMatchAll.push(txHash) + } }) - it('should download file for first consumer', async function () { + it('should fail to download file for first consumer for credentials with match all', async function () { + this.timeout(DEFAULT_TEST_TIMEOUT * 3) + + const doCheck = async () => { + const consumerAddress = consumerAddresses[0] + const consumerPrivateKey = ganachePrivateKeys[consumerAddress] + const transferTxId = orderTxIdsWithMatchAll[0] + + const wallet = new ethers.Wallet(consumerPrivateKey) + const nonce = Date.now().toString() + const message = String(ddoWithMatchAll.id + nonce) + const consumerMessage = ethers.solidityPackedKeccak256( + ['bytes'], + [ethers.hexlify(ethers.toUtf8Bytes(message))] + ) + const messageHashBytes = ethers.toBeArray(consumerMessage) + const signature = await wallet.signMessage(messageHashBytes) + + const downloadTask = { + fileIndex: 0, + documentId: didWithMatchAll, + serviceId: ddoWithMatchAll.services[0].id, + transferTxId, + nonce, + consumerAddress, + signature, + command: PROTOCOL_COMMANDS.DOWNLOAD + } + const response = await new DownloadHandler(oceanNode).handle(downloadTask) + assert(response) + assert(response.status.httpStatus === 403, 'http status not 403') + } + + setTimeout(() => { + expect(expectedTimeoutFailure(this.test.title)).to.be.equal(true) + }, DEFAULT_TEST_TIMEOUT * 3) + + await doCheck() + }) + it('should download file for first consumer for credentials with match any', async function () { this.timeout(DEFAULT_TEST_TIMEOUT * 3) const doCheck = async () => { diff --git a/src/test/integration/download.test.ts b/src/test/integration/download.test.ts index f1243ed3c..3dc01799d 100644 --- a/src/test/integration/download.test.ts +++ b/src/test/integration/download.test.ts @@ -121,12 +121,10 @@ describe('[Download Flow] - Should run a complete node flow.', () => { const resp = await streamToString(response.stream as Readable) const status = JSON.parse(resp) assert(status.id === oceanNodeConfig.keys.peerId.toString(), 'peer id not matching ') - // test allowedAdmins - assert(status.allowedAdmins.length === 1, 'incorrect length') assert( - status.allowedAdmins[0]?.toLowerCase() === + status.allowedAdmins?.addresses[0]?.toLowerCase() === '0xe2DD09d719Da89e5a3D0F2549c7E24566e947260'?.toLowerCase(), - 'incorrect allowed admin publisherAddress' + 'incorrect admin address' ) assert(status.c2dClusters === undefined, 'clusters info should be undefined') assert(status.supportedSchemas === undefined, 'schemas info should be undefined') diff --git a/src/test/unit/credentials.test.ts b/src/test/unit/credentials.test.ts index d1f1e5aca..babf78e68 100644 --- a/src/test/unit/credentials.test.ts +++ b/src/test/unit/credentials.test.ts @@ -1,9 +1,5 @@ import { expect } from 'chai' -import { - areKnownCredentialTypes, - checkCredentials, - hasAddressMatchAllRule -} from '../../utils/credentials.js' +import { checkCredentials } from '../../utils/credentials.js' import { buildEnvOverrideConfig, OverrideEnvConfig, @@ -13,9 +9,14 @@ import { } from '../utils/utils.js' import { ENVIRONMENT_VARIABLES } from '../../utils/constants.js' import { homedir } from 'os' -import { Credentials, CREDENTIALS_TYPES } from '@oceanprotocol/ddo-js' +import { Credentials } from '@oceanprotocol/ddo-js' +import { CREDENTIALS_TYPES } from '../../@types/DDO/Credentials.js' +import { Blockchain } from '../../utils/blockchain.js' +import { Signer } from 'ethers' let envOverrides: OverrideEnvConfig[] +let blockchain: Blockchain +let signer: Signer describe('credentials', () => { before(async () => { @@ -27,27 +28,38 @@ describe('credentials', () => { ] ) envOverrides = await setupEnvironment(TEST_ENV_CONFIG_FILE, envOverrides) + // Initialize blockchain for tests + blockchain = new Blockchain('http://172.0.0.1:8545', 'development', 8996, []) + signer = blockchain.getSigner() }) - it('should allow access with undefined or empty credentials', () => { + it('should allow access with undefined or empty credentials', async () => { const credentialsUndefined: Credentials = undefined const consumerAddress = '0x123' - const accessGranted1 = checkCredentials(credentialsUndefined, consumerAddress) + const accessGranted1 = await checkCredentials( + consumerAddress, + credentialsUndefined, + signer + ) expect(accessGranted1).to.equal(true) const credentialsEmapty = {} as Credentials - const accessGranted2 = checkCredentials(credentialsEmapty, consumerAddress) + const accessGranted2 = await checkCredentials( + consumerAddress, + credentialsEmapty, + signer + ) expect(accessGranted2).to.equal(true) }) - it('should allow access with empty allow and deny lists', () => { + it('should allow access with empty allow and deny lists', async () => { const credentials: Credentials = { allow: [], deny: [] } const consumerAddress = '0x123' - const accessGranted = checkCredentials(credentials, consumerAddress) + const accessGranted = await checkCredentials(consumerAddress, credentials, signer) expect(accessGranted).to.equal(true) }) - it('should allow access with empty values in deny lists', () => { + it('should allow access with empty values in deny lists', async () => { const credentials: Credentials = { allow: [], deny: [ @@ -58,27 +70,28 @@ describe('credentials', () => { ] } const consumerAddress = '0x123' - const accessGranted = checkCredentials(credentials, consumerAddress) + const accessGranted = await checkCredentials(consumerAddress, credentials, signer) expect(accessGranted).to.equal(true) }) - it('should allow access with "accessList" credentials type', () => { + it('should allow access with "accessList" credentials type', async () => { const consumerAddress = '0x123' const credentials: Credentials = { allow: [], deny: [ { type: CREDENTIALS_TYPES.ACCESS_LIST, - values: [consumerAddress] + chainId: 8996, + accessList: '0x0000000000000000000000000000000000000000' } ] } - const accessGranted = checkCredentials(credentials, consumerAddress) + const accessGranted = await checkCredentials(consumerAddress, credentials, signer) expect(accessGranted).to.equal(true) }) - it('should deny access with empty values in allow lists', () => { + it('should deny access with empty values in allow lists', async () => { const credentials: Credentials = { deny: [], allow: [ @@ -89,10 +102,10 @@ describe('credentials', () => { ] } const consumerAddress = '0x123' - const accessGranted = checkCredentials(credentials, consumerAddress) + const accessGranted = await checkCredentials(consumerAddress, credentials, signer) expect(accessGranted).to.equal(false) }) - it('should allow access with address in allow list', () => { + it('should allow access with address in allow list', async () => { const credentials: Credentials = { deny: [], allow: [ @@ -103,10 +116,10 @@ describe('credentials', () => { ] } const consumerAddress = '0x123' - const accessGranted = checkCredentials(credentials, consumerAddress) + const accessGranted = await checkCredentials(consumerAddress, credentials, signer) expect(accessGranted).to.equal(true) }) - it('should allow access with address not in deny list', () => { + it('should allow access with address not in deny list', async () => { const credentials: Credentials = { allow: [], deny: [ @@ -117,10 +130,10 @@ describe('credentials', () => { ] } const consumerAddress = '0x123' - const accessGranted = checkCredentials(credentials, consumerAddress) + const accessGranted = await checkCredentials(consumerAddress, credentials, signer) expect(accessGranted).to.equal(true) }) - it('should deny access with address in deny list', () => { + it('should deny access with address in deny list', async () => { const credentials: Credentials = { allow: [ { @@ -136,10 +149,10 @@ describe('credentials', () => { ] } const consumerAddress = '0x123' - const accessGranted = checkCredentials(credentials, consumerAddress) + const accessGranted = await checkCredentials(consumerAddress, credentials, signer) expect(accessGranted).to.equal(false) }) - it('should deny access with address not in allow list', () => { + it('should deny access with address not in allow list', async () => { const credentials: Credentials = { allow: [ { @@ -155,54 +168,28 @@ describe('credentials', () => { ] } const consumerAddress = '0x123' - const accessGranted = checkCredentials(credentials, consumerAddress) + const accessGranted = await checkCredentials(consumerAddress, credentials, signer) expect(accessGranted).to.equal(false) }) - it('should check correctly known credentials types', () => { - const credentialsOk: Credentials = { - deny: [ + it('should check match all (*) rules', async () => { + const credentials: Credentials = { + allow: [ { type: CREDENTIALS_TYPES.ADDRESS, - values: ['0x456'] + values: ['*'] } ], - allow: [ - { - type: CREDENTIALS_TYPES.ACCESS_LIST, - values: ['0x456'] - }, + deny: [ { type: CREDENTIALS_TYPES.ADDRESS, - values: ['0x678'] + values: ['0x2222', '0x333'] } ] } - const isKnownType2 = areKnownCredentialTypes(credentialsOk) - expect(isKnownType2).to.equal(true) - }) - it('should check match all (*) rules', () => { - const creds = { - credentials: { - allow: [ - { - type: CREDENTIALS_TYPES.ADDRESS, - values: ['*'] - } - ], - deny: [ - { - type: CREDENTIALS_TYPES.ADDRESS, - values: ['0x2222', '0x333'] - } - ] - } - } - expect(hasAddressMatchAllRule(creds.credentials.allow)).to.be.equal(true) - const creds2 = structuredClone(creds) - creds2.credentials.allow[0].values = ['0x2222', '0x333'] - expect(hasAddressMatchAllRule(creds2.credentials.allow)).to.be.equal(false) + const accessGranted = await checkCredentials('0x123', credentials, signer) + expect(accessGranted).to.equal(true) }) after(async () => { diff --git a/src/test/utils/contracts.ts b/src/test/utils/contracts.ts index 6f9041412..ba1c4112b 100644 --- a/src/test/utils/contracts.ts +++ b/src/test/utils/contracts.ts @@ -59,13 +59,21 @@ export async function deployAccessListContract( const contract = getContract(contractFactoryAddress, contractFactoryAbi, signer) try { + // Get the current nonce to ensure we're using the correct one + const signerAddress = await signer.getAddress() + const currentNonce = await signer.provider.getTransactionCount( + signerAddress, + 'pending' + ) + const tx = await contract.deployAccessListContract( nameAccessList, symbolAccessList, transferable, owner, user, - tokenURI + tokenURI, + { nonce: currentNonce } ) if (!tx) { @@ -99,6 +107,10 @@ export async function deployAndGetAccessListConfig( (await provider.getSigner(2)) as Signer, (await provider.getSigner(3)) as Signer ] + + // Add small delay to ensure previous transactions are settled + await new Promise((resolve) => setTimeout(resolve, 100)) + const txAddress = await deployAccessListContract( owner, // owner is first account networkArtifacts.AccessListFactory, @@ -115,10 +127,15 @@ export async function deployAndGetAccessListConfig( ], ['https://oceanprotocol.com/nft/'] ) + + if (!txAddress) { + console.error('Failed to deploy AccessList - no address returned') + return null + } + console.log('Successfully deployed AccessList at address: ', txAddress) const contractAcessList = getContract(txAddress, AccessList.abi, owner) - // console.log('contractAcessList:', contractAcessList) if (contractAcessList) { const result: AccessListContract = { '8996': [txAddress] diff --git a/src/utils/accessList.ts b/src/utils/accessList.ts new file mode 100644 index 000000000..fbecbe462 --- /dev/null +++ b/src/utils/accessList.ts @@ -0,0 +1,42 @@ +import AccessListJson from '@oceanprotocol/contracts/artifacts/contracts/accesslists/AccessList.sol/AccessList.json' with { type: 'json' } +import { ethers, Signer } from 'ethers' +import { CORE_LOGGER } from './logging/common.js' + +/** + * @param accessList the access list contract address + * @param chainId the chain id to check + * @param addressToCheck the account address to check on the access list + * @param signer signer for the contract part + * @returns true if the account has balanceOf > 0 OR if the accessList is empty OR does not contain info for this chain, false otherwise + */ +export async function checkAddressOnAccessList( + accessListContractAddress: string, + addressToCheck: string, + signer: Signer +): Promise { + if (!accessListContractAddress) { + return true + } + const accessListContract = new ethers.Contract( + accessListContractAddress, + AccessListJson.abi, + signer + ) + try { + // if has at least 1 token than is is authorized + const balance = await accessListContract.balanceOf(addressToCheck) + if (Number(balance) > 0) { + return true + } else { + CORE_LOGGER.error( + `Account ${addressToCheck} is NOT part of the given access list group.` + ) + return false + } + } catch (error) { + CORE_LOGGER.error( + `Failed to check access list ${accessListContractAddress}: ${error.message}` + ) + return false + } +} diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 82d99bde9..c3bfb2a9a 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,15 +1,11 @@ import { ethers, isAddress } from 'ethers' import { CORE_LOGGER } from './logging/common.js' -import { Blockchain, getConfiguration } from './index.js' -import { RPCS } from '../@types/blockchain.js' -import { isDefined } from '../utils/util.js' -import AccessListContract from '@oceanprotocol/contracts/artifacts/contracts/accesslists/AccessList.sol/AccessList.json' with { type: 'json' } -import { getAccountsFromAccessList } from '../utils/credentials.js' -import { OceanNodeConfig } from '../@types/OceanNode.js' -import { LOG_LEVELS_STR } from './logging/Logger.js' +import { getConfiguration } from './index.js' +import { AccessListContract, OceanNodeConfig } from '../@types/OceanNode.js' import { CommonValidation } from './validators.js' import { isERC1271Valid } from '../components/core/utils/nonceHandler.js' - +import { checkSingleCredential } from './credentials.js' +import { CREDENTIALS_TYPES } from '../@types/DDO/Credentials.js' export async function validateAdminSignature( expiryTimestamp: number, signature: string, @@ -40,27 +36,38 @@ export async function validateAdminSignature( CORE_LOGGER.logMessage(`Resolved signer address: ${signerAddress}`) } - const allowedAdmins: string[] = await getAdminAddresses(config) - - if (allowedAdmins.length === 0) { - const errorMsg = "Allowed admins list is empty. Please add admins' addresses." - CORE_LOGGER.logMessage(errorMsg) - return { valid: false, error: errorMsg } - } const currentTimestamp = new Date().getTime() if (currentTimestamp > expiryTimestamp) { const errorMsg = `The expiryTimestamp ${expiryTimestamp} sent for validation is in the past. Therefore signature ${signature} is rejected` CORE_LOGGER.logMessage(errorMsg) return { valid: false, error: errorMsg } } - for (const address of allowedAdmins) { - if ( - ethers.getAddress(address)?.toLowerCase() === - ethers.getAddress(signerAddress)?.toLowerCase() - ) { + const allowedAdmins = await getAdminAddresses(config) + + const { addresses, accessLists } = allowedAdmins + let allowed = await checkSingleCredential( + { type: CREDENTIALS_TYPES.ADDRESS, values: addresses }, + signerAddress, + null + ) + if (allowed) { + return { valid: true, error: '' } + } + for (const chainId of Object.keys(accessLists)) { + allowed = await checkSingleCredential( + { + type: CREDENTIALS_TYPES.ACCESS_LIST, + chainId: parseInt(chainId), + accessList: accessLists[chainId] + }, + signerAddress, + null + ) + if (allowed) { return { valid: true, error: '' } } } + const errorMsg = `The address which signed the message is not on the allowed admins list. Therefore signature ${signature} is rejected` CORE_LOGGER.logMessage(errorMsg) return { valid: false, error: errorMsg } @@ -73,60 +80,25 @@ export async function validateAdminSignature( export async function getAdminAddresses( existingConfig?: OceanNodeConfig -): Promise { +): Promise<{ addresses: string[]; accessLists: any }> { let config: OceanNodeConfig + const ret = { + addresses: [] as string[], + accessLists: undefined as AccessListContract | undefined + } if (!existingConfig) { config = await getConfiguration() } else { config = existingConfig } - const validAddresses: string[] = [] if (config.allowedAdmins && config.allowedAdmins.length > 0) { for (const admin of config.allowedAdmins) { if (isAddress(admin) === true) { - validAddresses.push(admin) - } - } - if (validAddresses.length === 0) { - CORE_LOGGER.log( - LOG_LEVELS_STR.LEVEL_ERROR, - `Invalid format for ETH address from ALLOWED ADMINS.` - ) - } - } - if ( - config.allowedAdminsList && - isDefined(config.supportedNetworks) && - Object.keys(config.allowedAdminsList).length > 0 - ) { - const RPCS: RPCS = config.supportedNetworks - const supportedChains: string[] = Object.keys(config.supportedNetworks) - const accessListsChainsListed = Object.keys(config.allowedAdminsList) - for (const chain of supportedChains) { - const { chainId, network, rpc, fallbackRPCs } = RPCS[chain] - const blockchain = new Blockchain(rpc, network, chainId, fallbackRPCs) - - // check the access lists for this chain - if (accessListsChainsListed.length > 0 && accessListsChainsListed.includes(chain)) { - for (const accessListAddress of config.allowedAdminsList[chainId]) { - // instantiate contract and check addresses present + balanceOf() - const accessListContract = new ethers.Contract( - accessListAddress, - AccessListContract.abi, - blockchain.getSigner() - ) - - const adminsFromAccessList: string[] = await getAccountsFromAccessList( - accessListContract, - chainId - ) - if (adminsFromAccessList.length > 0) { - return validAddresses.concat(adminsFromAccessList) - } - } + ret.addresses.push(admin) } } } - return validAddresses + ret.accessLists = config.allowedAdminsList + return ret } diff --git a/src/utils/config/builder.ts b/src/utils/config/builder.ts index f606fec9e..56e28fa83 100644 --- a/src/utils/config/builder.ts +++ b/src/utils/config/builder.ts @@ -235,7 +235,6 @@ export async function buildMergedConfig(): Promise { const merged = lodash.merge({}, baseConfig, envOverrides) preprocessConfigData(merged) - const parsed = OceanNodeConfigSchema.safeParse(merged) if (!parsed.success) { diff --git a/src/utils/config/schemas.ts b/src/utils/config/schemas.ts index be6836fe7..e3cf3b802 100644 --- a/src/utils/config/schemas.ts +++ b/src/utils/config/schemas.ts @@ -32,13 +32,25 @@ export const SupportedNetworkSchema = z.object({ export const RPCSSchema = z.record(z.string(), SupportedNetworkSchema) -export const AccessListContractSchema = z - .union([ - z.record(z.string(), z.array(z.string())), - z.array(z.any()).transform((): null => null), - z.null() - ]) - .nullable() +export const AccessListContractSchema = z.preprocess( + (val) => { + // If it's not a plain object, normalize to null + if (val === null) return null + // If it's a JSON string, try to parse it + if (typeof val === 'string') { + try { + val = JSON.parse(val) + } catch { + return null + } + } + + if (typeof val !== 'object' || Array.isArray(val)) return null + + return val + }, + z.record(z.string(), z.array(z.string())).nullable() +) export const OceanNodeKeysSchema = z.object({ peerId: z.any().optional(), diff --git a/src/utils/credentials.ts b/src/utils/credentials.ts index d09578da3..0cff6e76d 100644 --- a/src/utils/credentials.ts +++ b/src/utils/credentials.ts @@ -1,107 +1,238 @@ -import { Contract, ethers, EventLog, Signer } from 'ethers' -import AccessList from '@oceanprotocol/contracts/artifacts/contracts/accesslists/AccessList.sol/AccessList.json' with { type: 'json' } +import { Signer } from 'ethers' import { AccessListContract } from '../@types/OceanNode.js' import { CORE_LOGGER } from './logging/common.js' - -import { getNFTContract } from '../components/Indexer/utils.js' +import { Credential, Credentials, MATCH_RULES } from '@oceanprotocol/ddo-js' +import { CREDENTIALS_TYPES } from '../@types/DDO/Credentials.js' +import { checkAddressOnAccessList } from './accessList.js' import { isDefined } from './util.js' -import { getOceanArtifactsAdressesByChainId } from './address.js' -import { Credential, Credentials, CREDENTIALS_TYPES } from '@oceanprotocol/ddo-js' -import { KNOWN_CREDENTIALS_TYPES } from '../@types/DDO/Credentials.js' - -export function findCredential( - credentials: Credential[], - consumerCredentials: Credential -) { - return credentials.find((credential) => { - if (Array.isArray(credential?.values)) { - if (credential.values.length > 0) { - const credentialType = String(credential?.type)?.toLowerCase() - const credentialValues = credential.values.map((v) => String(v)?.toLowerCase()) - return ( - credentialType === consumerCredentials.type && - credentialValues.includes(consumerCredentials.values[0]) - ) - } + +/** + * Main credential checking function with blockchain support for accessList checks + * + * @param consumerAddress The consumer address to check + * @param credentials The credentials object containing allow/deny rules + * @param blockchain Blockchain instance to get signer for on-chain checks + * @returns Promise - true if access is granted, false otherwise + * + * Logic: + * 1. If credentials are undefined/empty, allow access + * 2. Check DENY list first with match_deny strategy (default 'any'): + * - If match_deny='any' and ANY rule matches, DENY access + * - If match_deny='all' and ALL rules match, DENY access + * - Unknown types with match_deny='all' are ignored + * 3. Check ALLOW list with match_allow strategy (default 'all'): + * - If match_allow='any' and ANY rule matches, ALLOW access + * - If match_allow='all' and ALL rules match, ALLOW access + * - Unknown types with match_allow='all' cause DENY + * 4. If no allow list specified, ALLOW access + */ +export async function checkCredentials( + consumerAddress: string, + credentials: Credentials, + signer: Signer +): Promise { + // If credentials are undefined or empty, allow access + if (!isDefined(credentials)) { + return true + } + + const normalizedAddress = consumerAddress.toLowerCase() + // Get matching strategies (with defaults) + const matchDeny: MATCH_RULES = credentials.match_deny || 'any' + const matchAllow: MATCH_RULES = credentials.match_allow || 'all' + + // ======================================== + // STEP 1: Check DENY list (checked first) + // ======================================== + if (isDefined(credentials.deny) && credentials.deny.length > 0) { + const denyResult = await evaluateCredentialList( + credentials.deny, + normalizedAddress, + signer, + matchDeny, + 'deny' + ) + + // If evaluation says to deny, return false + if (denyResult.shouldDeny) { + CORE_LOGGER.logMessage( + `Access denied: Consumer address ${consumerAddress} matched deny rule(s)` + ) + return false } - return false - }) + } + + // ======================================== + // STEP 2: Check ALLOW list + // ======================================== + if (isDefined(credentials.allow) && credentials.allow.length > 0) { + const allowResult = await evaluateCredentialList( + credentials.allow, + normalizedAddress, + signer, + matchAllow, + 'allow' + ) + + return allowResult.shouldAllow + } + + // No allow list specified, grant access by default + return true } -export function hasAddressMatchAllRule(credentials: Credential[]): boolean { - const creds = credentials.find((credential: Credential) => { - if (Array.isArray(credential?.values)) { - if (credential.values.length > 0 && credential.type) { - const filteredValues: string[] = credential.values.filter((value: string) => { - return value?.toLowerCase() === '*' // address - }) - return ( - filteredValues.length > 0 && - credential.type.toLowerCase() === KNOWN_CREDENTIALS_TYPES[0] +/** + * Evaluates a list of credentials (either allow or deny) + */ +async function evaluateCredentialList( + credentialList: Credential[], + consumerAddress: string, + signer: Signer, + matchRule: MATCH_RULES, + listType: 'allow' | 'deny' +): Promise<{ shouldAllow: boolean; shouldDeny: boolean }> { + const matchResults: boolean[] = [] + for (const credential of credentialList) { + const matchResult = await checkSingleCredential(credential, consumerAddress, signer) + + if (matchResult === null) { + // Unknown or unsupported credential type + + if (listType === 'allow' && matchRule === 'all') { + // Unknown type in allow list with match_all='all' -> DENY + CORE_LOGGER.warn( + `Unknown credential type '${credential.type}' in allow list with match_allow='all'. Access denied.` ) + return { shouldAllow: false, shouldDeny: false } } + // For deny list with match_all='all', unknown types are ignored + // For 'any' match, unknown types don't contribute to matching + } else { + matchResults.push(matchResult) + } + } + + // No valid credential checks were performed + if (matchResults.length === 0) { + if (listType === 'allow') { + // No valid allow rules means deny + return { shouldAllow: false, shouldDeny: false } + } else { + // No valid deny rules means don't deny + return { shouldAllow: false, shouldDeny: false } + } + } + + // Apply matching strategy + if (listType === 'deny') { + let shouldDeny = false + if (matchRule === 'any') { + // If ANY rule matches, deny + shouldDeny = matchResults.some((r) => r === true) + } else { + // matchRule === 'all' + // If ALL rules match, deny + shouldDeny = matchResults.every((r) => r === true) } - return false - }) - return isDefined(creds) + return { shouldAllow: false, shouldDeny } + } else { + // listType === 'allow' + let shouldAllow = false + if (matchRule === 'any') { + // If ANY rule matches, allow + shouldAllow = matchResults.some((r) => r === true) + } else { + // matchRule === 'all' + // ALL rules must match + shouldAllow = matchResults.every((r) => r === true) + } + return { shouldAllow, shouldDeny: false } + } } /** - * This method checks credentials - * @param credentials credentials - * @param consumerAddress consumer address + * Checks if a consumer address matches a single credential rule + * + * @param credential The credential rule to check + * @param consumerAddress The consumer address (already normalized) + * @param signer Signer for blockchain checks + * @returns Promise - true if matches, false if doesn't match, null if unknown type */ -export function checkCredentials(credentials: Credentials, consumerAddress: string) { - const consumerCredentials: Credential = { - type: CREDENTIALS_TYPES.ADDRESS, - values: [String(consumerAddress)?.toLowerCase()] - } +export async function checkSingleCredential( + credential: Credential, + consumerAddress: string, + signer: Signer | null +): Promise { + const credentialType = credential.type.toLowerCase() - const accessGranted = true - // check deny access - if (Array.isArray(credentials?.deny) && credentials.deny.length > 0) { - const accessDeny = findCredential(credentials.deny, consumerCredentials) - // credential is on deny list, so it should be blocked access - if (accessDeny) { + // ======================================== + // Handle ADDRESS-based credentials + // ======================================== + if (credentialType === CREDENTIALS_TYPES.ADDRESS.toLowerCase()) { + // Type assertion since we know it's address type + const addressCredential = credential as any + + if (!isDefined(addressCredential.values) || addressCredential.values.length === 0) { return false } - // credential not found, so it really depends if we have a match - } - // check allow access - if (Array.isArray(credentials?.allow) && credentials.allow.length > 0) { - const accessAllow = findCredential(credentials.allow, consumerCredentials) - if (accessAllow || hasAddressMatchAllRule(credentials.allow)) { + + // Check for wildcard (*) + const hasWildcard = addressCredential.values.some( + (value: string) => value.toLowerCase() === '*' + ) + if (hasWildcard) { return true } - return false + + // Check if address is in the list + const normalizedValues = addressCredential.values.map((v: string) => v.toLowerCase()) + return normalizedValues.includes(consumerAddress) } - return accessGranted -} -export function areKnownCredentialTypes(credentials: Credentials): boolean { - if (isDefined(credentials)) { - if (isDefined(credentials.allow) && credentials.allow.length > 0) { - for (const credential of credentials.allow) { - if (!isKnownCredentialType(credential.type)) { - return false - } - } + // ======================================== + // Handle ACCESSLIST-based credentials + // ======================================== + if (credentialType === CREDENTIALS_TYPES.ACCESS_LIST.toLowerCase()) { + const accessListCredential = credential as any + + // AccessList credentials need a contract address to check + if (!isDefined(accessListCredential.accessList)) { + CORE_LOGGER.warn('AccessList credential missing accessList contract address') + return false } - if (isDefined(credentials.deny) && credentials.deny.length > 0) { - for (const credential of credentials.deny) { - if (!isKnownCredentialType(credential.type)) { - return false - } - } + try { + // Check if the consumer address has tokens in the access list contract + const hasAccess = await checkAddressOnAccessList( + accessListCredential.accessList, + consumerAddress, + signer + ) + return hasAccess + } catch (error) { + CORE_LOGGER.error(`Error checking accessList credential: ${error.message}`) + return false } } - return true + + // ======================================== + // Handle VERIFIABLE CREDENTIAL (future support) + // ======================================== + if (credentialType === 'verifiablecredential') { + CORE_LOGGER.warn('VerifiableCredential type is not yet supported') + return null // Unknown/unsupported type + } + + // ======================================== + // Unknown credential type + // ======================================== + CORE_LOGGER.warn(`Unknown credential type: ${credential.type}`) + return null } -// utility function that can be used on multiple access lists /** - * @param accessList the access list contract address + * Checks credential on multi-chain access list + * @param accessList the access list contract addresses by chain * @param chainId the chain id to check * @param addressToCheck the account address to check on the access list * @param signer signer for the contract part @@ -121,14 +252,12 @@ export async function checkCredentialOnAccessList( if (chainsListed.length > 0 && chainsListed.includes(chainId)) { let isAuthorized = false for (const accessListAddress of accessList[chainId]) { - const accessListContract = new ethers.Contract( + const result = await checkAddressOnAccessList( accessListAddress, - AccessList.abi, + addressToCheck, signer ) - // if has at least 1 token than is is authorized - const balance = await accessListContract.balanceOf(addressToCheck) - if (Number(balance) > 0) { + if (result) { isAuthorized = true break } @@ -142,48 +271,15 @@ export async function checkCredentialOnAccessList( } return true } -// from https://github.com/oceanprotocol/ocean-node/issues/808 -// The idea is to use an nft contract and check if one address is on the list by calling 'balanceOf' -// (means user has at least one token) -export async function findAccessListCredentials( - signer: Signer, - contractAddress: string, - address: string -): Promise { - const nftContract: ethers.Contract = getNFTContract(signer, contractAddress) - if (!nftContract) { - return false - } - return await findAccountFromAccessList(nftContract, address) -} - -export async function findAccountFromAccessList( - nftContract: ethers.Contract, - walletAddress: string -): Promise { - try { - const balance = await nftContract.balanceOf(walletAddress) - return Number(balance) > 0 - } catch (err) { - return false - } -} - -export function isKnownCredentialType(credentialType: string): boolean { - return ( - isDefined(credentialType) && - KNOWN_CREDENTIALS_TYPES.findIndex((type) => { - return type.toLowerCase() === credentialType.toLowerCase() - }) > -1 - ) -} /** - * Gets the addresses present on the contract access list (the ones with balanceOf > 1) - * @param contractAcessList - * @param chainId - * @returns - */ + * Gets all addresses present on the contract access list (the ones with balanceOf >= 1) + * @param contractAcessList The access list contract + * @param chainId The chain ID + * @param startBlock Optional start block + * @param endBlock Optional end block + * @returns Array of addresses that have access + export async function getAccountsFromAccessList( contractAcessList: Contract, chainId: number, @@ -193,7 +289,7 @@ export async function getAccountsFromAccessList( const resultAccounts: string[] = [] const networkArtifacts = getOceanArtifactsAdressesByChainId(chainId) // some basic extra checks - if (!networkArtifacts || (startBlock && endBlock && endBlock > startBlock)) { + if (!networkArtifacts || (startBlock && endBlock && endBlock < startBlock)) { return resultAccounts } @@ -205,7 +301,7 @@ export async function getAccountsFromAccessList( )) as Array for (const log of eventLogs) { // check the account address - if (log.args.length === 2 && Number(log.args[1] >= 1)) { + if (log.args.length === 2 && Number(log.args[1]) >= 1) { const address: string = log.args[0] // still has it? const balance = await contractAcessList.balanceOf(address) @@ -221,3 +317,4 @@ export async function getAccountsFromAccessList( } return resultAccounts } + */