diff --git a/metadata/freeComputeAlgo.json b/metadata/freeComputeAlgo.json deleted file mode 100644 index 880968d..0000000 --- a/metadata/freeComputeAlgo.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "fileObject": { - "type": "url", - "url": "https://raw.githubusercontent.com/oceanprotocol/ocean-cli/refs/heads/main/metadata/pythonAlgo.json", - "method": "GET" - }, - "meta": { - "container": { - "image": "oceanprotocol/algo_dockers", - "tag": "python-branin", - "entrypoint": "python $ALGO" - } - } -} diff --git a/metadata/freeComputeAlgoJS.json b/metadata/freeComputeAlgoJS.json new file mode 100644 index 0000000..aa23b80 --- /dev/null +++ b/metadata/freeComputeAlgoJS.json @@ -0,0 +1,14 @@ +{ + "fileObject": { + "type": "url", + "url": "https://raw.githubusercontent.com/oceanprotocol/test-algorithm/master/javascript/algo.js", + "method": "GET" + }, + "meta": { + "container": { + "entrypoint": "node $ALGO", + "image": "node", + "tag": "latest" + } + } +} diff --git a/metadata/freeComputeAlgoPY.json b/metadata/freeComputeAlgoPY.json new file mode 100644 index 0000000..4dc3934 --- /dev/null +++ b/metadata/freeComputeAlgoPY.json @@ -0,0 +1,18 @@ +{ + "fileObject": { + "type": "url", + "url": "https://raw.githubusercontent.com/oceanprotocol/vscode-extension/82f5691f34a9055b6f33a82d52b1a4f12bff367d/metadata/pyAlgoDDO.json", + "method": "GET" + }, + "meta": { + "container": { + "image": "oceanprotocol/algo_dockers", + "tag": "python-branin", + "entrypoint": "python $ALGO", + "termsAndConditions": true + }, + "additionalInformation": { + "termsAndConditions": true + } + } +} diff --git a/metadata/jsAlgo.json b/metadata/jsAlgo.json deleted file mode 100644 index ac0d874..0000000 --- a/metadata/jsAlgo.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "@context": ["https://w3id.org/did/v1"], - "id": "", - "nftAddress": "", - "version": "4.1.0", - "chainId": null, - "metadata": { - "created": "2023-08-01T17:09:39Z", - "updated": "2023-08-01T17:09:39Z", - "type": "algorithm", - "name": "CLi Algo", - "description": "Cli algo", - "author": "OPF", - "license": "https://market.oceanprotocol.com/terms", - "additionalInformation": { - "termsAndConditions": true - }, - "algorithm": { - "language": "", - "version": "0.1", - "container": { - "entrypoint": "node $ALGO", - "image": "node", - "tag": "latest", - "checksum": "sha256:1155995dda741e93afe4b1c6ced2d01734a6ec69865cc0997daf1f4db7259a36" - } - } - }, - "services": [ - { - "id": "db164c1b981e4d2974e90e61bda121512e6909c1035c908d68933ae4cfaba6b0", - "type": "compute", - "files": { - "datatokenAddress": "0x0", - "nftAddress": "0x0", - "files": [ - { - "url": "https://raw.githubusercontent.com/oceanprotocol/test-algorithm/master/javascript/algo.js", - "contentType": "text/js", - "encoding": "UTF-8" - } - ] - }, - "timeout": 86400, - "compute": { - "allowRawAlgorithm": false, - "allowNetworkAccess": true, - "publisherTrustedAlgorithmPublishers": [], - "publisherTrustedAlgorithms": [] - } - } - ], - "stats": { - "allocated": 0, - "orders": 0, - "price": { - "value": "0" - } - }, - "nft": { - "address": "", - "name": "Ocean Data NFT", - "symbol": "OCEAN-NFT", - "state": 5, - "tokenURI": "", - "owner": "", - "created": "" - } -} diff --git a/metadata/jsIPFSAlgo.json b/metadata/jsIPFSAlgo.json deleted file mode 100644 index 62a3bbe..0000000 --- a/metadata/jsIPFSAlgo.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "@context": ["https://w3id.org/did/v1"], - "id": "", - "nftAddress": "", - "version": "4.1.0", - "chainId": null, - "metadata": { - "created": "2023-08-01T17:09:39Z", - "updated": "2023-08-01T17:09:39Z", - "type": "algorithm", - "name": "avreage price real estate algo", - "description": "Cli algo", - "author": "OPF", - "license": "https://market.oceanprotocol.com/terms", - "additionalInformation": { - "termsAndConditions": true - }, - "algorithm": { - "language": "", - "version": "0.1", - "container": { - "entrypoint": "node $ALGO", - "image": "node", - "tag": "latest", - "checksum": "sha256:1155995dda741e93afe4b1c6ced2d01734a6ec69865cc0997daf1f4db7259a36" - } - } - }, - "services": [ - { - "id": "1234567890", - "type": "compute", - "files": { - "datatokenAddress": "0x0", - "nftAddress": "0x0", - "files": [ - { - "hash": "QmQN9hWcqN7chF3qrAL5HpZSkfc9kvjSH7o4LaGVMxs9gu", - "type": "ipfs" - } - ] - }, - "timeout": 86400, - "compute": { - "allowRawAlgorithm": false, - "allowNetworkAccess": true, - "publisherTrustedAlgorithmPublishers": [], - "publisherTrustedAlgorithms": [] - } - } - ], - "stats": { - "allocated": 0, - "orders": 0, - "price": { - "value": "0" - } - }, - "nft": { - "address": "", - "name": "Ocean Data NFT", - "symbol": "OCEAN-NFT", - "state": 5, - "tokenURI": "", - "owner": "", - "created": "" - } -} diff --git a/metadata/pythonAlgo.json b/metadata/pythonAlgo.json deleted file mode 100644 index 93bdaca..0000000 --- a/metadata/pythonAlgo.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "@context": ["https://w3id.org/did/v1"], - "id": "", - "nftAddress": "", - "version": "4.1.0", - "chainId": null, - "metadata": { - "created": "2023-08-01T17:09:39Z", - "updated": "2023-08-01T17:09:39Z", - "type": "algorithm", - "name": "CLi Algo", - "description": "Cli algo", - "author": "OPF", - "license": "https://market.oceanprotocol.com/terms", - "additionalInformation": { - "termsAndConditions": true - }, - "algorithm": { - "language": "python", - "format": "docker-image", - "version": "0.1", - "container": { - "entrypoint": "python $ALGO", - "image": "oceanprotocol/algo_dockers", - "tag": "python-branin", - "checksum": "sha256:8221d20c1c16491d7d56b9657ea09082c0ee4a8ab1a6621fa720da58b09580e4" - } - } - }, - "services": [ - { - "id": "db164c1b981e4d2974e90e61bda121512e6909c1035c908d68933ae4cfaba6b0", - "type": "access", - "files": { - "datatokenAddress": "0x0", - "nftAddress": "0x0", - "files": [ - { - "type": "url", - "url": "https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/branin_and_gpr/gpr.py", - "method": "GET" - } - ] - }, - "timeout": 86400, - "serviceEndpoint": "http://10.84.128.6:8001" - } - ], - "stats": { - "allocated": 0, - "orders": 0, - "price": { - "value": "0" - } - }, - "nft": { - "address": "", - "name": "Ocean Data NFT", - "symbol": "OCEAN-NFT", - "state": 5, - "tokenURI": "", - "owner": "", - "created": "" - } -} diff --git a/metadata/simpleComputeDataset.json b/metadata/simpleComputeDataset.json deleted file mode 100644 index 478b419..0000000 --- a/metadata/simpleComputeDataset.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "@context": ["https://w3id.org/did/v1"], - "id": "", - "nftAddress": "", - "version": "4.1.0", - "chainId": null, - "metadata": { - "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", - "tags": ["test"], - "author": "oceanprotocol", - "license": "https://market.oceanprotocol.com/terms", - "additionalInformation": { - "termsAndConditions": true - } - }, - "services": [ - { - "id": "ccb398c50d6abd5b456e8d7242bd856a1767a890b537c2f8c10ba8b8a10e6025", - "type": "compute", - "files": { - "datatokenAddress": "0x0", - "nftAddress": "0x0", - "files": [ - { - "type": "url", - "url": "https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/branin_and_gpr/branin.arff", - "method": "GET" - } - ] - }, - "datatokenAddress": "", - "serviceEndpoint": "http://10.84.128.6:8001", - "timeout": 86400, - "compute": { - "allowRawAlgorithm": false, - "allowNetworkAccess": true, - "publisherTrustedAlgorithmPublishers": [], - "publisherTrustedAlgorithms": [] - } - } - ], - "event": {}, - "nft": { - "address": "", - "name": "Ocean Data NFT", - "symbol": "OCEAN-NFT", - "state": 5, - "tokenURI": "", - "owner": "", - "created": "" - }, - "purgatory": { - "state": false - }, - "datatokens": [], - "stats": { - "allocated": 0, - "orders": 0, - "price": { - "value": "0" - } - } -} diff --git a/metadata/simpleDownloadDataset.json b/metadata/simpleDownloadDataset.json deleted file mode 100644 index 5a8240e..0000000 --- a/metadata/simpleDownloadDataset.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "@context": ["https://w3id.org/did/v1"], - "id": "", - "nftAddress": "", - "version": "4.1.0", - "chainId": null, - "metadata": { - "created": "2021-12-20T14:35:20Z", - "updated": "2021-12-20T14:35:20Z", - "type": "dataset", - "name": "ocean-cli demo asset", - "description": "asset published using ocean cli tool", - "tags": ["test"], - "author": "oceanprotocol", - "license": "https://market.oceanprotocol.com/terms", - "additionalInformation": { - "termsAndConditions": true - } - }, - "services": [ - { - "id": "ccb398c50d6abd5b456e8d7242bd856a1767a890b537c2f8c10ba8b8a10e6025", - "type": "access", - "files": { - "datatokenAddress": "0x0", - "nftAddress": "0x0", - "files": [ - { - "type": "url", - "url": "https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-abstract10.xml.gz-rss.xml", - "method": "GET" - } - ] - }, - "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": [], - "stats": { - "allocated": 0, - "orders": 0, - "price": { - "value": "2" - } - } -} diff --git a/metadata/simpleIPFSComputeDataset.json b/metadata/simpleIPFSComputeDataset.json deleted file mode 100644 index 5bba914..0000000 --- a/metadata/simpleIPFSComputeDataset.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "@context": ["https://w3id.org/did/v1"], - "id": "", - "nftAddress": "", - "version": "4.1.0", - "chainId": null, - "metadata": { - "created": "2021-12-20T14:35:20Z", - "updated": "2021-12-20T14:35:20Z", - "type": "dataset", - "name": "dubai real estate data", - "description": "dubai real estate dataset published using ocean.js cli tool", - "tags": ["test"], - "author": "oceanprotocol", - "license": "https://market.oceanprotocol.com/terms", - "additionalInformation": { - "termsAndConditions": true - } - }, - "services": [ - { - "id": "ccb398c50d6abd5b456e8d7242bd856a1767a890b537c2f8c10ba8b8a10e6025", - "type": "compute", - "files": { - "datatokenAddress": "0x0", - "nftAddress": "0x0", - "files": [ - { - "type": "ipfs", - "hash": "QmdXLHfMwupDZbMXmTD1qA8QAiVfHdt1NiGwvokppdUxj4" - } - ] - }, - "datatokenAddress": "", - "serviceEndpoint": "https://v4.provider.oceanprotocol.com", - "timeout": 86400, - "compute": { - "allowRawAlgorithm": false, - "allowNetworkAccess": true, - "publisherTrustedAlgorithmPublishers": [], - "publisherTrustedAlgorithms": [] - } - } - ], - "event": {}, - "nft": { - "address": "", - "name": "Ocean Data NFT", - "symbol": "OCEAN-NFT", - "state": 5, - "tokenURI": "", - "owner": "", - "created": "" - }, - "purgatory": { - "state": false - }, - "datatokens": [], - "stats": { - "allocated": 0, - "orders": 0, - "price": { - "value": "0" - } - } -} diff --git a/src/extension.ts b/src/extension.ts index 8bc8953..f2fc141 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,13 +1,19 @@ import * as vscode from 'vscode' -import { Aquarius, Asset } from '@oceanprotocol/lib' import { OceanProtocolViewProvider } from './viewProvider' import { ethers } from 'ethers' import * as fs from 'fs' -import { createAssetUtil } from './helpers/publish' import fetch from 'cross-fetch' import { OceanP2P } from './helpers/oceanNode' -import { download } from './helpers/download' -import { computeStart } from './helpers/freeCompute' +import { + checkComputeStatus, + computeLogsChannel, + computeStart, + delay, + getComputeLogs, + getComputeResult, + saveResults +} from './helpers/compute' +import { generateOceanSignature } from './helpers/signature' globalThis.fetch = fetch const node = new OceanP2P() @@ -31,289 +37,225 @@ export async function activate(context: vscode.ExtensionContext) { outputChannel.appendLine('Ocean Protocol extension is now active!') console.log('Ocean Protocol extension is now active!') - const nodeId = await startOceanNode() + try { + // Create and register the webview provider + const provider = new OceanProtocolViewProvider(context.extensionUri) + console.log('Created OceanProtocolViewProvider') - const provider = new OceanProtocolViewProvider(context.extensionUri, nodeId) - - context.subscriptions.push( - vscode.window.registerWebviewViewProvider( + const registration = vscode.window.registerWebviewViewProvider( OceanProtocolViewProvider.viewType, - provider - ) - ) - - let getAssetDetails = vscode.commands.registerCommand( - 'ocean-protocol.getAssetDetails', - async (config: any, did: string) => { - outputChannel.appendLine('\n\nGetting asset details...') - if (!did) { - vscode.window.showErrorMessage('No DID provided.') - return + provider, + { + // This ensures the webview is retained even when not visible + webviewOptions: { retainContextWhenHidden: true } } - - if (!config.aquariusUrl) { - vscode.window.showErrorMessage('No Aquarius URL provided.') - return + ) + console.log('Registered webview provider') + + // Add to subscriptions + context.subscriptions.push(registration) + console.log('Added registration to subscriptions') + + // Create a test command to verify the webview is accessible + let testCommand = vscode.commands.registerCommand('ocean-protocol.test', () => { + console.log('Test command executed') + if (provider.resolveWebviewView) { + console.log('Webview is available') + } else { + console.log('Webview is not available') } - - try { - const aquariusUrl = new URL(config.aquariusUrl).toString() - const aquarius = new Aquarius(aquariusUrl) - const asset = await aquarius.resolve(did) - - outputChannel.appendLine(`Asset details: ${JSON.stringify(asset)}`) - - if (asset) { - const details = ` - Name: ${asset.metadata.name}\n - Type: ${asset.metadata.type}\n - Description: ${asset.metadata.description}\n - Author: ${asset.metadata.author}\n - ` - vscode.window.showInformationMessage(details) - } else { - vscode.window.showInformationMessage('Asset not found.') - } - } catch (error) { - console.error('Error details:', error) - if (error instanceof Error) { - vscode.window.showErrorMessage(`Error getting asset details: ${error.message}`) - } else { - vscode.window.showErrorMessage( - `An unknown error occurred while getting asset details.` - ) + }) + context.subscriptions.push(testCommand) + + // Rest of your existing startComputeJob command registration... + let startComputeJob = vscode.commands.registerCommand( + 'ocean-protocol.startComputeJob', + async ( + config: any, + algorithmPath: string, + resultsFolderPath: string, + privateKey: string | undefined, + nodeUrl: string, + datasetPath?: string + ) => { + console.log('Starting compute job...') + console.log('Config:', config) + console.log('Dataset path:', datasetPath) + console.log('Algorithm path:', algorithmPath) + console.log('Results folder path:', resultsFolderPath) + console.log('Node URL:', nodeUrl) + if (!config || !algorithmPath || !nodeUrl) { + vscode.window.showErrorMessage('Missing required parameters.') + return } - } - } - ) - - let publishAsset = vscode.commands.registerCommand( - 'ocean-protocol.publishAsset', - async (config: any, filePath: string, privateKey: string) => { - outputChannel.appendLine('\n\nPublishing file...') - if (!config) { - vscode.window.showErrorMessage('No config provided.') - return - } - if (!filePath) { - vscode.window.showErrorMessage('No file path provided.') - return - } - - if (!privateKey) { - vscode.window.showErrorMessage('No private key provided.') - return - } - vscode.window.showInformationMessage('Publishing asset') - - try { - // Read the file - const fileContent = fs.readFileSync(filePath, 'utf8') - console.log('File content read successfully.') - outputChannel.appendLine('File content read successfully.') - - const asset: Asset = JSON.parse(fileContent) - console.log('Asset JSON parsed successfully.') - - // Set up the signer - console.log(config.rpcUrl) - const provider = new ethers.providers.JsonRpcProvider(config.rpcUrl) - - const signer = new ethers.Wallet(privateKey, provider) - - const aquarius = new Aquarius(config.aquariusUrl) - - const urlAssetId = await createAssetUtil( - asset.nft.name, - asset.nft.symbol, - signer, - asset.services[0].files, - asset, - config.providerUrl, - aquarius, - true // encryptDDO - ) - - vscode.window.showInformationMessage( - `Asset published successfully. ID: ${urlAssetId}` - ) - - outputChannel.appendLine(`\n\nAsset published successfully. ID: ${urlAssetId}`) - } catch (error) { - console.error('Error details:', error) - if (error instanceof Error) { - vscode.window.showErrorMessage(`Error publishing asset: ${error.message}`) - } else { - vscode.window.showErrorMessage( - `An unknown error occurred while publishing the asset.` - ) + const progressOptions = { + location: vscode.ProgressLocation.Notification, + title: 'Compute Job Status', + cancellable: false } - } - } - ) - - let getOceanPeers = vscode.commands.registerCommand( - 'ocean-protocol.getOceanPeers', - async () => { - try { - outputChannel.appendLine('\n\nGetting Ocean Node Peers...') - const peers = await node.getOceanPeers() - if (peers && peers.length > 0) { - vscode.window.showInformationMessage(`Ocean Peers:\n${peers.join('\n')}`) - } else { - vscode.window.showInformationMessage('No Ocean Peers found.') - } - } catch (error) { - console.error('Error getting Ocean Peers:', error) - if (error instanceof Error) { - vscode.window.showErrorMessage(`Error getting Ocean Peers: ${error.message}`) - } else { - vscode.window.showErrorMessage( - 'An unknown error occurred while getting Ocean Peers.' - ) - } - } - } - ) - - let downloadAsset = vscode.commands.registerCommand( - 'ocean-protocol.downloadAsset', - async (config: any, filePath: string, privateKey: string, assetDid: string) => { - outputChannel.appendLine('\n\nDownloading asset...') - if (!config) { - vscode.window.showErrorMessage('No config provided.') - return - } - if (!assetDid) { - vscode.window.showErrorMessage('No DID provided.') - return - } - if (!filePath) { - vscode.window.showErrorMessage('No file path provided.') - return - } - - if (!privateKey) { - vscode.window.showErrorMessage('No private key provided.') - return - } - - try { - // Set up the signer - const provider = new ethers.providers.JsonRpcProvider(config.rpcUrl) - const signer = new ethers.Wallet(privateKey, provider) - console.log(`Signer: ${signer}`) + console.log('Progress options:', progressOptions) - // Test provider connectivity try { - const network = provider.network - vscode.window.showInformationMessage(`Connected to network: ${network}`) - } catch (networkError) { - console.error('Error connecting to network:', networkError) - vscode.window.showErrorMessage( - `Error connecting to network: ${networkError.message}` - ) - return - } - - const aquarius = new Aquarius(config.aquariusUrl) - - await download( - assetDid, - signer, - filePath, - aquarius, - undefined, - config.providerUrl - ) - - vscode.window.showInformationMessage( - `Asset download successfully. Path: ${filePath}` - ) - - outputChannel.appendLine(`Asset download successfully. Path: ${filePath}`) - } catch (error) { - console.error('Error details:', error) - outputChannel.appendLine(`Error details: ${filePath}`) - if (error instanceof Error) { - vscode.window.showErrorMessage(`Error downloading asset: ${error.message}`) - outputChannel.appendLine(`Error downloading asset: ${error.message}`) - } else { - vscode.window.showErrorMessage( - `An unknown error occurred while downloading the asset.` - ) - outputChannel.appendLine( - `An unknown error occurred while downloading the asset.` - ) + await vscode.window.withProgress(progressOptions, async (progress) => { + // Initial setup + progress.report({ message: 'Starting compute job...' }) + + // Generate a random wallet if no private key provided + const signer = privateKey + ? new ethers.Wallet(privateKey) + : ethers.Wallet.createRandom() + + if (!privateKey) { + console.log('Generated new wallet address:', signer.address) + vscode.window.showInformationMessage( + `Using generated wallet with address: ${signer.address}` + ) + } + console.log('Signer created') + + // Read files + let dataset + if (datasetPath) { + const datasetContent = await fs.promises.readFile(datasetPath, 'utf8') + dataset = JSON.parse(datasetContent) + } + const algorithmContent = await fs.promises.readFile(algorithmPath, 'utf8') + const algorithm = JSON.parse(algorithmContent) + console.log('Dataset read successfully') + console.log('Algorithm read successfully') + + // nonce equals date in milliseconds + const nonce = Date.now() + console.log('Nonce: ', nonce) + + // Start compute job + const computeResponse = await computeStart( + algorithm, + signer, + nodeUrl, + dataset, + nonce + ) + console.log('Compute result received:', computeResponse) + const jobId = computeResponse.jobId + console.log('Job ID:', jobId) + + computeLogsChannel.show() + computeLogsChannel.appendLine(`Starting compute job with ID: ${jobId}`) + + // Start fetching logs periodically + + const index = 0 + + console.log('Generating signature for retrieval...') + progress.report({ message: 'Generating signature for retrieval...' }) + computeLogsChannel.appendLine('Generating signature for retrieval...') + const signatureResult = await generateOceanSignature({ + signer, + consumerAddress: signer.address, + jobId, + index, + nonce + }) + + const logInterval = setInterval(async () => { + await getComputeLogs( + nodeUrl, + jobId, + signer.address, + nonce, + signatureResult.signature + ) + }, 5000) + + // Monitor job status + progress.report({ message: 'Monitoring compute job status...' }) + computeLogsChannel.appendLine('Monitoring compute job status...') + + while (true) { + console.log('Checking job status...') + const status = await checkComputeStatus(nodeUrl, jobId) + console.log('Job status:', status) + console.log('Status text:', status.statusText) + progress.report({ message: `${status.statusText}` }) + computeLogsChannel.appendLine(`Job status: ${status.statusText}`) + + if (status.statusText === 'Job finished') { + // Clear the logging interval + clearInterval(logInterval) + // Generate signature for result retrieval + + // Retrieve results + progress.report({ message: 'Retrieving compute results...' }) + computeLogsChannel.appendLine('Retrieving compute results...') + const results = await getComputeResult( + nodeUrl, + jobId, + signer.address, + signatureResult.signature, + index, + nonce + ) + + // Save results + progress.report({ message: 'Saving results...' }) + computeLogsChannel.appendLine('Saving results...') + const filePath = await saveResults(results, resultsFolderPath) + + vscode.window.showInformationMessage( + `Compute job completed successfully! Results saved to: ${filePath}` + ) + computeLogsChannel.appendLine( + `Compute job completed successfully! Results saved to: ${filePath}` + ) + + // Open the saved file in a new editor window + const uri = vscode.Uri.file(filePath) + const document = await vscode.workspace.openTextDocument(uri) + await vscode.window.showTextDocument(document, { preview: false }) + + vscode.window.showInformationMessage( + `Compute job completed successfully! Results opened in editor.` + ) + computeLogsChannel.appendLine( + `Compute job completed successfully! Results opened in editor.` + ) + + break + } + + if ( + status.statusText.toLowerCase().includes('error') || + status.statusText.toLowerCase().includes('failed') + ) { + throw new Error(`Job failed with status: ${status.statusText}`) + } + + await delay(5000) // Wait 5 seconds before checking again + } + }) + } catch (error) { + console.error('Error details:', error) + if (error instanceof Error) { + vscode.window.showErrorMessage(`Error with compute job: ${error.message}`) + } else { + vscode.window.showErrorMessage( + 'An unknown error occurred with the compute job.' + ) + } } } - } - ) - - let startComputeJob = vscode.commands.registerCommand( - 'ocean-protocol.startComputeJob', - async ( - config: any, - datasetPath: string, - algorithmPath: string, - privateKey: string, - nodeUrl: string - ) => { - if (!config) { - vscode.window.showErrorMessage('No config provided.') - return - } - if (!privateKey) { - vscode.window.showErrorMessage('No private key provided.') - return - } - if (!datasetPath) { - vscode.window.showErrorMessage('No dataset file path provided.') - return - } - if (!algorithmPath) { - vscode.window.showErrorMessage('No algorithm file path provided.') - return - } - if (!nodeUrl) { - vscode.window.showErrorMessage('No ocean node url provided.') - return - } + ) + context.subscriptions.push(startComputeJob) + } catch (error) { + console.error('Error during extension activation:', error) + outputChannel.appendLine(`Error during extension activation: ${error}`) + } +} - try { - const provider = new ethers.providers.JsonRpcProvider(config.rpcUrl) - const signer = new ethers.Wallet(privateKey, provider) - - // Read the dataset file - const datasetContent = fs.readFileSync(datasetPath, 'utf8') - const dataset = JSON.parse(datasetContent) - - // Read the algorithm file - const algorithmContent = fs.readFileSync(algorithmPath, 'utf8') - const algorithm = JSON.parse(algorithmContent) - - await computeStart(dataset, algorithm, signer, nodeUrl) - - vscode.window.showInformationMessage('Compute job started successfully!') - } catch (error) { - console.error('Error details:', error) - if (error instanceof Error) { - vscode.window.showErrorMessage(`Error starting compute job: ${error.message}`) - } else { - vscode.window.showErrorMessage( - `An unknown error occurred while starting the compute job.` - ) - } - } - } - ) - - context.subscriptions.push( - getAssetDetails, - publishAsset, - downloadAsset, - getOceanPeers, - startComputeJob - ) +// Add deactivation handling +export function deactivate() { + console.log('Ocean Protocol extension is being deactivated') + outputChannel.appendLine('Ocean Protocol extension is being deactivated') } diff --git a/src/helpers/compute.ts b/src/helpers/compute.ts new file mode 100644 index 0000000..ce642b0 --- /dev/null +++ b/src/helpers/compute.ts @@ -0,0 +1,201 @@ +import * as vscode from 'vscode' +import { Signer } from 'ethers' +import fs from 'fs' +import axios from 'axios' +import path from 'path' + +interface ComputeStatus { + owner: string + jobId: string + dateCreated: string + dateFinished: string | null + status: number + statusText: string + results: Array<{ + filename: string + filesize: number + type: string + index: number + }> +} + +interface ComputeResponse { + owner: string + jobId: string + dateCreated: string + dateFinished: null + status: number + statusText: string + results: any[] + agreementId: string + expireTimestamp: number + environment: string +} + +export async function computeStart( + algorithm: any, + signer: Signer, + nodeUrl: string, + dataset?: any, + nonce: number = 1 +): Promise { + console.log('Starting free compute job using provider: ', nodeUrl) + const consumerAddress: string = await signer.getAddress() + + try { + const requestBody = { + command: 'freeStartCompute', + consumerAddress: consumerAddress, + environment: + '0x7d187e4c751367be694497ead35e2937ece3c7f3b325dcb4f7571e5972d092bd-0x071ead74e903edeb2ad40d196f03db09f70811ede01f3e111fd5106f52b388ee', + nonce: nonce, + signature: '0x123', + datasets: dataset ? [dataset] : [], + algorithm: algorithm + } + + console.log('Sending compute request with body:', requestBody) + + const response = await axios.post(`${nodeUrl}/directCommand`, requestBody) + + console.log('Free Start Compute response: ' + JSON.stringify(response.data)) + + if (!response.data || response.data.length === 0) { + throw new Error('Empty response from compute start') + } + + return response.data[0] + } catch (e) { + console.error('Free start compute error: ', e) + if (e.response) { + console.error('Error response data:', e.response.data) + console.error('Error response status:', e.response.status) + console.error('Error response headers:', e.response.headers) + } + throw e + } +} + +export async function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +export async function checkComputeStatus( + nodeUrl: string, + jobId: string +): Promise { + const response = await axios.post(`${nodeUrl}/directCommand`, { + command: 'getComputeStatus', + jobId + }) + return response.data[0] +} + +export async function getComputeResult( + nodeUrl: string, + jobId: string, + consumerAddress: string, + signature: string, + index: number = 0, + nonce: number = 0 +) { + try { + console.log('Getting compute result for jobId:', jobId) + console.log('Using consumerAddress:', consumerAddress) + console.log('Using signature:', signature) + console.log('Using index:', index) + console.log('Using nonce:', nonce) + + const response = await axios.post(`${nodeUrl}/directCommand`, { + command: 'getComputeResult', + jobId, + consumerAddress, + signature, + index, + nonce + }) + return response.data + } catch (error) { + console.error('Error getting compute result:', error) + throw error + } +} + +export async function saveResults( + results: any, + destinationFolder?: string +): Promise { + try { + // Use provided destination folder or default to './results' + const resultsDir = destinationFolder || path.join(process.cwd(), 'results') + + // Ensure results directory exists + if (!fs.existsSync(resultsDir)) { + console.log('Creating results directory at:', resultsDir) + await fs.promises.mkdir(resultsDir, { recursive: true }) + } + + const timestamp = new Date().toISOString().replace(/[:.]/g, '-') + const fileName = `compute-results-${timestamp}.txt` + const filePath = path.join(resultsDir, fileName) + + console.log('Saving results to:', filePath) + console.log('Results content:', JSON.stringify(results, null, 2)) + + // Write the file + await fs.promises.writeFile(filePath, JSON.stringify(results, null, 2), 'utf-8') + + // Verify file was created + if (!fs.existsSync(filePath)) { + throw new Error(`Failed to create file at ${filePath}`) + } + + return filePath + } catch (error) { + console.error('Error saving results:', error) + console.error('Results directory:', destinationFolder || './results') + console.error('Results:', results) + throw new Error(`Failed to save results: ${error.message}`) + } +} + +// Create output channel for compute logs +export const computeLogsChannel = vscode.window.createOutputChannel('Ocean Compute Logs') + +export async function getComputeLogs( + nodeUrl: string, + jobId: string, + consumerAddress: string, + nonce: number, + signature: string +): Promise { + try { + // Make request to get compute logs + const response = await fetch(`${nodeUrl}/directCommand`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + command: 'getComputeStreamableLogs', + jobId, + consumerAddress, + nonce, + signature + }) + }) + + if (!response.ok) { + throw new Error(`Failed to get compute logs: ${response.statusText}`) + } + + const logs = await response.json() + if (logs && Array.isArray(logs)) { + logs.forEach((log) => { + computeLogsChannel.appendLine(log) + }) + } + } catch (error) { + console.error('Error fetching compute logs:', error) + } +} diff --git a/src/helpers/download.ts b/src/helpers/download.ts deleted file mode 100644 index 17a58db..0000000 --- a/src/helpers/download.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - Aquarius, - Datatoken, - ProviderInstance, - ConfigHelper, - orderAsset -} from '@oceanprotocol/lib' -import { Signer } from 'ethers' -import { promises as fs } from 'fs' -import * as path from 'path' - -export async function downloadFile( - url: string, - downloadPath: string, - index?: number -): Promise { - const response = await fetch(url) - if (!response.ok) { - throw new Error('Response error.') - } - - const defaultName = !isNaN(index) && index > -1 ? `file_${index}.out` : 'file.out' - let filename: string - - try { - // try to get it from headers - filename = response.headers - .get('content-disposition') - .match(/attachment;filename=(.+)/)[1] - } catch { - filename = defaultName - } - - const filePath = path.join(downloadPath, filename) - const data = await response.arrayBuffer() - - try { - await fs.writeFile(filePath, Buffer.from(data)) - } catch (err) { - throw new Error('Error while saving the file:', err.message) - } - - return filename -} - -export async function download( - did: string, - owner: Signer, - pathString: string = '.', - aquariusInstance: Aquarius, - macOsProviderUrl?: string, - providerUrl?: string -) { - const dataDdo = await aquariusInstance.waitForAqua(did) - if (!dataDdo) { - console.error('Error fetching DDO ' + did + '. Does this asset exists?') - return - } - let providerURI - if (!providerUrl) { - providerURI = - macOsProviderUrl && dataDdo.chainId === 8996 - ? macOsProviderUrl - : dataDdo.services[0].serviceEndpoint - } else { - providerURI = providerUrl - } - console.log('Downloading asset using provider: ', providerURI) - const { chainId } = await owner.provider.getNetwork() - console.log('Chain ID:', chainId) - const config = new ConfigHelper().getConfig(chainId) - console.log('Config:', config) - const datatoken = new Datatoken(owner, chainId) - - const tx = await orderAsset(dataDdo, owner, config, datatoken, providerURI) - - if (!tx) { - console.error('Error ordering access for ' + did + '. Do you have enough tokens?') - return - } - - const orderTx = await tx.wait() - - const urlDownloadUrl = await ProviderInstance.getDownloadUrl( - dataDdo.id, - dataDdo.services[0].id, - 0, - orderTx.transactionHash, - providerURI, - owner - ) - try { - const path = pathString ? pathString : '.' - const { filename } = await downloadFile(urlDownloadUrl, path) - console.log('File downloaded successfully:', path + '/' + filename) - } catch (e) { - console.log(`Download url dataset failed: ${e}`) - } -} diff --git a/src/helpers/freeCompute.ts b/src/helpers/freeCompute.ts deleted file mode 100644 index 703805a..0000000 --- a/src/helpers/freeCompute.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ProviderInstance } from '@oceanprotocol/lib' -import { Signer } from 'ethers' - -export async function computeStart( - dataset: any, - algorithm: any, - signer: Signer, - nodeUrl: string -) { - console.log('Starting free compute job using provider: ', nodeUrl) - const consumerAddress: string = await signer.getAddress() - - const nonce = (await ProviderInstance.getNonce(nodeUrl, await signer.getAddress())) + 1 - console.log('Nonce: ', nonce) - - try { - const response = await fetch(nodeUrl + '/directCommand', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - command: 'freeStartCompute', - consumerAddress: consumerAddress, - nonce: nonce, - signature: '0x123', - datasets: [dataset], - algorithm: algorithm - }) - }) - console.log('Free Start Compute response: ' + JSON.stringify(response)) - } catch (e) { - console.error('Free start compute error: ', e) - } -} diff --git a/src/helpers/publish.ts b/src/helpers/publish.ts deleted file mode 100644 index ac9f62c..0000000 --- a/src/helpers/publish.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Aquarius, createAsset, ZERO_ADDRESS, Datatoken4 } from '@oceanprotocol/lib' -import { Signer } from 'ethers' - -export async function createAssetUtil( - name: string, - symbol: string, - owner: Signer, - assetUrl: any, - ddo: any, - providerUrl: string, - aquariusInstance: Aquarius, - encryptDDO: boolean = true, - templateIndex: number = 1, - providerFeeToken?: string, - macOsProviderUrl?: string -) { - return await createAsset( - name, - symbol, - owner, - assetUrl, - templateIndex, - ddo, - encryptDDO, - macOsProviderUrl || providerUrl, - providerFeeToken || ZERO_ADDRESS, - aquariusInstance - ) -} diff --git a/src/helpers/signature.ts b/src/helpers/signature.ts new file mode 100644 index 0000000..3489b72 --- /dev/null +++ b/src/helpers/signature.ts @@ -0,0 +1,70 @@ +import { ethers, Signer } from 'ethers' + +export interface SignatureParams { + signer: Signer + consumerAddress: string + jobId: string + index?: number + nonce?: number +} + +export interface SignatureResult { + signature: string + walletAddress: string + hashedMessage: string + recoveredAddress: string + isValid: boolean +} + +export async function generateOceanSignature({ + signer, + consumerAddress, + jobId, + index = 0, + nonce = 1 +}: SignatureParams): Promise { + try { + // Create message string + const message = consumerAddress + jobId + index.toString() + nonce + + // Hash the message exactly as done in Ocean Protocol + const consumerMessage = ethers.utils.solidityKeccak256( + ['bytes'], + [ethers.utils.hexlify(ethers.utils.toUtf8Bytes(message))] + ) + + // Convert to bytes and sign + const messageHashBytes = ethers.utils.arrayify(consumerMessage) + const signature = await signer.signMessage(messageHashBytes) + + // Get wallet address from signer + const walletAddress = await signer.getAddress() + + // Verify using both methods like Ocean Protocol does + const addressFromHashSignature = ethers.utils.verifyMessage( + consumerMessage, + signature + ) + const addressFromBytesSignature = ethers.utils.verifyMessage( + messageHashBytes, + signature + ) + + const isValid = + addressFromHashSignature.toLowerCase() === consumerAddress.toLowerCase() || + addressFromBytesSignature.toLowerCase() === consumerAddress.toLowerCase() + + return { + signature, + walletAddress, + hashedMessage: consumerMessage, + recoveredAddress: addressFromHashSignature, + isValid + } + } catch (error) { + if (error instanceof Error) { + throw new Error(`Failed to generate signature: ${error.message}`) + } + throw error + } +} diff --git a/src/viewProvider.ts b/src/viewProvider.ts index 21d4ef9..a02e863 100644 --- a/src/viewProvider.ts +++ b/src/viewProvider.ts @@ -5,72 +5,110 @@ export class OceanProtocolViewProvider implements vscode.WebviewViewProvider { private _view?: vscode.WebviewView - constructor( - private readonly _extensionUri: vscode.Uri, - private nodeId: string - ) {} + constructor(private readonly _extensionUri: vscode.Uri) {} public resolveWebviewView( webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken ) { - this._view = webviewView + console.log('resolveWebviewView called') - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [this._extensionUri] - } + try { + this._view = webviewView - webviewView.webview.html = this._getHtmlForWebview(webviewView.webview) + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [this._extensionUri] + } - webviewView.webview.onDidReceiveMessage((data) => { - switch (data.type) { - case 'getAssetDetails': - vscode.commands.executeCommand( - 'ocean-protocol.getAssetDetails', - data.config, - data.did - ) - break - case 'publishAsset': - vscode.commands.executeCommand( - 'ocean-protocol.publishAsset', - data.config, - data.filePath, - data.privateKey - ) - break - case 'downloadAsset': - vscode.commands.executeCommand( - 'ocean-protocol.downloadAsset', - data.config, - data.filePath, - data.privateKey, - data.assetDid - ) - break - case 'openFilePicker': - this.openFilePicker(data.elementId) - break - case 'getOceanPeers': - vscode.commands.executeCommand('ocean-protocol.getOceanPeers') - break - case 'startComputeJob': - vscode.commands.executeCommand( - 'ocean-protocol.startComputeJob', - data.config, - data.datasetPath, - data.algorithmPath, - data.privateKey, - data.nodeUrl - ) - break + // Get the currently active file for algorithm + const activeEditor = vscode.window.activeTextEditor + const currentFilePath = activeEditor?.document.uri.fsPath + + webviewView.webview.html = this._getHtmlForWebview(webviewView.webview) + + // If there's an active file, use it as the algorithm + if (currentFilePath && currentFilePath.endsWith('.json')) { + console.log('Setting default algorithm:', currentFilePath) + webviewView.webview.postMessage({ + type: 'fileSelected', + filePath: currentFilePath, + elementId: 'selectedAlgorithmPath', + isDefault: true + }) } - }) + + // Listen for active editor changes + vscode.window.onDidChangeActiveTextEditor((editor) => { + if (editor && editor.document.uri.fsPath.endsWith('.json')) { + console.log( + 'Active editor changed, new algorithm file:', + editor.document.uri.fsPath + ) + webviewView.webview.postMessage({ + type: 'fileSelected', + filePath: editor.document.uri.fsPath, + elementId: 'selectedAlgorithmPath', + isDefault: true + }) + } + }) + + webviewView.webview.onDidReceiveMessage(async (data) => { + console.log('Received message from webview:', data) + + try { + switch (data.type) { + case 'openFilePicker': + console.log('Opening file picker for:', data.elementId) + await this.openFilePicker(data.elementId) + break + case 'selectResultsFolder': { + console.log('Opening folder picker') + const folderUri = await vscode.window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: 'Select Results Folder' + }) + + if (folderUri && folderUri[0]) { + console.log('Selected folder:', folderUri[0].fsPath) + webviewView.webview.postMessage({ + type: 'resultsFolder', + path: folderUri[0].fsPath + }) + } + break + } + case 'startComputeJob': + console.log('Starting compute job with data:', data) + await vscode.commands.executeCommand( + 'ocean-protocol.startComputeJob', + data.config, + data.algorithmPath, + data.resultsFolderPath, + data.privateKey, + data.nodeUrl, + data.datasetPath + ) + break + } + } catch (error) { + console.error('Error handling message:', error) + vscode.window.showErrorMessage(`Error handling message: ${error}`) + } + }) + } catch (error) { + console.error('Error in resolveWebviewView:', error) + vscode.window.showErrorMessage(`Failed to resolve webview: ${error}`) + } } private async openFilePicker(elementId: string) { + console.log('openFilePicker called with elementId:', elementId) + const options: vscode.OpenDialogOptions = { canSelectMany: false, openLabel: 'Select', @@ -79,28 +117,31 @@ export class OceanProtocolViewProvider implements vscode.WebviewViewProvider { } } - const fileUri = await vscode.window.showOpenDialog(options) - if (fileUri && fileUri[0]) { - this._view?.webview.postMessage({ - type: 'fileSelected', - filePath: fileUri[0].fsPath, - elementId: elementId - }) + try { + const fileUri = await vscode.window.showOpenDialog(options) + console.log('File picker result:', fileUri) + + if (fileUri && fileUri[0]) { + console.log('Posting message back to webview') + this._view?.webview.postMessage({ + type: 'fileSelected', + filePath: fileUri[0].fsPath, + elementId: elementId + }) + } + } catch (error) { + console.error('Error in openFilePicker:', error) } } private _getHtmlForWebview(webview: vscode.Webview) { - // Escape the nodeId to prevent XSS - const nodeId = this.nodeId - .replace(/&/g, '&') - .replace(//g, '>') return ` + Ocean Protocol Extension -
-
- Setup -
-
-
- - - - - - - - -
-
-
- -
-
- Get Asset Details -
-
-
- - - -
-
-
- -
-
- Publish Asset -
-
-
- -
- - -
-
-
- -
-
- Start Compute Job -
-
-
- - -
- - - -
- - - - - -
-
-
- -
-
- P2P -
-
-
- -
${nodeId || 'Connecting...'}
-
- -
-
-
-
- Download Asset -
-
-
- - - - - -
-
- - - + window.addEventListener('message', event => { + const message = event.data; + console.log('Received message:', message); + + switch (message.type) { + case 'fileSelected': + if (message.elementId === 'selectedDatasetPath') { + selectedDatasetPath = message.filePath; + const element = document.getElementById('selectedDatasetPath'); + element.innerHTML = 'Selected dataset: ' + message.filePath + ''; + } else if (message.elementId === 'selectedAlgorithmPath') { + selectedAlgorithmPath = message.filePath; + isUsingDefaultAlgorithm = message.isDefault || false; + const element = document.getElementById('selectedAlgorithmPath'); + const prefix = isUsingDefaultAlgorithm ? 'Current algorithm file: ' : 'Selected algorithm: '; + element.innerHTML = '' + prefix + '' + message.filePath + ''; + } else { + selectedFilePath = message.filePath; + document.getElementById('selectedFilePath').textContent = 'Selected file: ' + message.filePath; + } + break; + case 'nodeId': + document.getElementById('nodeIdDisplay').textContent = message.nodeId; + break; + case 'resultsFolder': + selectedResultsFolderPath = message.path; + document.getElementById('selectedResultsFolderPath').textContent = message.path; + break; + } + }); + `