diff --git a/Go/Go/go.mod b/Go/Go/go.mod deleted file mode 100644 index ca3c1cb..0000000 --- a/Go/Go/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module simple_chaincode - -go 1.22.4 diff --git a/Go/Go/go.sum b/Go/Go/go.sum deleted file mode 100644 index e69de29..0000000 diff --git a/Go/Go/main.go b/Go/Go/main.go deleted file mode 100644 index adbfe7c..0000000 --- a/Go/Go/main.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("Starting the Go debugger test") - - counter := 0 - for i := 0; i < 5; i++ { - counter++ - fmt.Println("Counter value:", counter) - - } - - fmt.Println("Final Counter value:", counter) -} diff --git a/Go/__debug_bin2763616131 b/Go/__debug_bin2763616131 deleted file mode 100755 index 0ce3583..0000000 Binary files a/Go/__debug_bin2763616131 and /dev/null differ diff --git a/Go/go.mod b/Go/go.mod deleted file mode 100644 index ca3c1cb..0000000 --- a/Go/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module simple_chaincode - -go 1.22.4 diff --git a/Go/go.sum b/Go/go.sum deleted file mode 100644 index e69de29..0000000 diff --git a/Go/main.go b/Go/main.go deleted file mode 100644 index e41b054..0000000 --- a/Go/main.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("Starting the Go debugger test") - - counter := 0 - - for i := 0; i < 5; i++ { - counter++ - fmt.Printf("Counter value at iteration %d: %d\n", i, counter) - } - - fmt.Println("Final Counter value:", counter) -} diff --git a/docs/images/block-query.png b/docs/images/block-query.png new file mode 100644 index 0000000..1068f5e Binary files /dev/null and b/docs/images/block-query.png differ diff --git a/docs/images/debugger-launched-message.png b/docs/images/debugger-launched-message.png new file mode 100644 index 0000000..a118df5 Binary files /dev/null and b/docs/images/debugger-launched-message.png differ diff --git a/docs/images/debugger-start.png b/docs/images/debugger-start.png new file mode 100644 index 0000000..6458035 Binary files /dev/null and b/docs/images/debugger-start.png differ diff --git a/docs/images/delve-output.png b/docs/images/delve-output.png new file mode 100644 index 0000000..234807d Binary files /dev/null and b/docs/images/delve-output.png differ diff --git a/docs/images/extension-start.png b/docs/images/extension-start.png new file mode 100644 index 0000000..dcaa8d0 Binary files /dev/null and b/docs/images/extension-start.png differ diff --git a/docs/images/extension-ui-launch.png b/docs/images/extension-ui-launch.png new file mode 100644 index 0000000..610c60a Binary files /dev/null and b/docs/images/extension-ui-launch.png differ diff --git a/docs/images/other-interface.png b/docs/images/other-interface.png new file mode 100644 index 0000000..bff6649 Binary files /dev/null and b/docs/images/other-interface.png differ diff --git a/docs/images/select-chaincode-file.png b/docs/images/select-chaincode-file.png new file mode 100644 index 0000000..6639123 Binary files /dev/null and b/docs/images/select-chaincode-file.png differ diff --git a/docs/images/upload-network.png b/docs/images/upload-network.png new file mode 100644 index 0000000..638e56d Binary files /dev/null and b/docs/images/upload-network.png differ diff --git a/docs/images/upload-wallet.png b/docs/images/upload-wallet.png new file mode 100644 index 0000000..25141dc Binary files /dev/null and b/docs/images/upload-wallet.png differ diff --git a/docs/images/variables-interface.png b/docs/images/variables-interface.png new file mode 100644 index 0000000..bb6e420 Binary files /dev/null and b/docs/images/variables-interface.png differ diff --git a/docs/usage-guide.md b/docs/usage-guide.md new file mode 100644 index 0000000..838318a --- /dev/null +++ b/docs/usage-guide.md @@ -0,0 +1,102 @@ +# ๐Ÿ” Hyperledger Fabric Debugger - Usage Guide + +Welcome to the usage guide for the **Hyperledger Fabric Debugger** VSCode extension. This tool is designed to help developers debug chaincode, interact with local Fabric networks, and manage wallets directly from VSCode. + +--- + +## Table of Contents +- [Installation](#installation) +- [Launching the Extension](#launching-the-extension) +- [Using the Extension Features](#using-the-extension-features) +- [Running the Debugger](#running-the-debugger) +- [Example Flow](#example-flow) +- [Troubleshooting](#troubleshooting) +- [Questions or Feedback?](#questions-or-feedback) + +--- + +## โš™๏ธ Installation + +Before using the debugger, ensure the extension is installed using the `vsce` tool. + +```bash +npx vsce package +code --install-extension Hyperledger-Fabric-Debugger.vsix +``` + +## ๐Ÿš€ Launching the Extension +Open VSCode and navigate to the Run & Debug tab on the sidebar. + +Click the "Run Extension" button. +![Start extension](./images/extension-start.png) + +This will open the Extension Development Host, where the debugger's custom UI will appear. +![Extension UI Launch](./images/extension-ui-launch.png) + +## ๐Ÿงฉ Using the Extension Features +Once the extension UI is open, youโ€™ll see the main dashboard with the following actions: + +- **Upload Network Configuration** (e.g., connection profile) +![Upload Network](./images/upload-network.png) + +- **Upload Wallet** +![Upload Wallet](./images/upload-wallet.png) + +- **Start Local Network** (e.g., using fabric-samples) + +- **Query Blocks** +![Query Blocks](./images/block-query.png) + +## ๐Ÿ› Running the Debugger +To start debugging your chaincode: + +1. Click **Run & Debug**. + +2. Select the configuration: **Debug Hyperledger Fabric**. +![Start Debugger](./images/debugger-start.png) + +3. A file prompt will appear โ€” select your `chaincode.go` file. +![File Prompt Window](./images/select-chaincode-file.png) + +4. The debugger will attach using **Delve**. + +5. Set breakpoints in your code. +![Debugger attached](./images/debugger-launched-message.png) + +Youโ€™ll see the standard VSCode debug interface: + +Variables +![Debugger interface](./images/variables-interface.png) + +Watch expressions +Call stack +Breakpoints panel +![Debugger interface](./images/other-interface.png) + +**Debug Console output from Delve** +![Debugger Message](./images/delve-output.png) + +6. Use a Fabric client to invoke chaincode. + +## โœ… Example Flow +- Upload network config & wallet. +- Upload your `chaincode.go` file. +- Add breakpoints. +- Use a Fabric client to invoke transactions. +- Observe how the debugger halts at breakpoints, shows local variables, and logs chaincode behavior in real time. + +## ๐Ÿงผ Troubleshooting +If you encounter errors like Delve DAP server not found: + +- Ensure `dlv` is installed on your machine: +```bash + go install github.com/go-delve/delve/cmd/dlv@latest + + ``` + +Restart the extension host and re-select the debug config. + +## ๐Ÿ™‹ Questions or Feedback? + +Found a bug? Have a suggestion? +Please open an issue or pull request on our [GitHub repository](https://github.com/your-org/your-repo). diff --git a/extension.js b/extension.js index 297c59c..d2fa5bc 100644 --- a/extension.js +++ b/extension.js @@ -24,24 +24,22 @@ const { } = require("./src/blockReader/blockchainExplorer.js"); const { log } = require("console"); let loadedConnectionProfile = null; -let factory; function activate(context) { console.log("Activating Fabric Debugger extension..."); const factory = new DelveDebugAdapterDescriptorFactory(); - context.subscriptions.push( - vscode.debug.registerDebugAdapterDescriptorFactory("delve", factory) - ); - context.subscriptions.push(factory); - console.log("Fabric Debugger extension Registered"); - + context.subscriptions.push( + vscode.debug.registerDebugAdapterDescriptorFactory("delve", factory) + ); + context.subscriptions.push(factory); + console.log("Fabric Debugger extension Registered"); + generateEnvFile(); + generateLaunchConfig(); const fabricDebuggerPathNew = "C:\\Users\\chinm\\fabric-debugger"; -======= const fabricDebuggerPath = context.extensionPath; - let greenButton = vscode.commands.registerCommand("myview.button1", () => { const platform = process.platform; let command; @@ -83,38 +81,48 @@ function activate(context) { context.subscriptions.push(greenButton); context.subscriptions.push(redButton); - const outputChannel = vscode.window.createOutputChannel("Chaincode Invocation"); - let disposableExtractFunctions = vscode.commands.registerCommand('extension.extractFunctions', function () { - const editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showInformationMessage('No active editor. Open a chaincode file.'); - return; - } - const filePath = editor.document.fileName; - const text = editor.document.getText(); - let functions = []; + const outputChannel = vscode.window.createOutputChannel( + "Chaincode Invocation" + ); + let disposableExtractFunctions = vscode.commands.registerCommand( + "extension.extractFunctions", + function () { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showInformationMessage( + "No active editor. Open a chaincode file." + ); + return; + } + const filePath = editor.document.fileName; + const text = editor.document.getText(); + let functions = []; - if (isGoChaincodeFile(filePath)) { - functions = extractGoFunctions(text); - } + if (isGoChaincodeFile(filePath)) { + functions = extractGoFunctions(text); + } - const filteredFunctions = filterIntAndStringFunctions(functions); - const uniqueFunctions = [...new Set(filteredFunctions)]; - storeFunctions(uniqueFunctions, context); + const filteredFunctions = filterIntAndStringFunctions(functions); + const uniqueFunctions = [...new Set(filteredFunctions)]; + storeFunctions(uniqueFunctions, context); - vscode.window.showInformationMessage(`Extracted and stored ${uniqueFunctions.length} unique functions with int or string parameters.`); + vscode.window.showInformationMessage( + `Extracted and stored ${uniqueFunctions.length} unique functions with int or string parameters.` + ); - showStoredFunctions(context, outputChannel); - }); + showStoredFunctions(context, outputChannel); + } + ); context.subscriptions.push(disposableExtractFunctions); function isGoChaincodeFile(filePath) { - return filePath.toLowerCase().endsWith('.go'); + return filePath.toLowerCase().endsWith(".go"); } function extractGoFunctions(code) { const functionDetails = []; - const regex = /func\s*\((\w+)\s+\*SmartContract\)\s*(\w+)\s*\((.*?)\)\s*(\w*)/g; + const regex = + /func\s*\((\w+)\s+\*SmartContract\)\s*(\w+)\s*\((.*?)\)\s*(\w*)/g; let match; while ((match = regex.exec(code)) !== null) { @@ -127,30 +135,37 @@ function activate(context) { } function filterIntAndStringFunctions(functions) { - return functions.filter(func => /int|string/.test(func.params)).map(func => `${func.name}(${func.params})`); + return functions + .filter((func) => /int|string/.test(func.params)) + .map((func) => `${func.name}(${func.params})`); } function storeFunctions(functions, context) { - let storedFunctions = context.workspaceState.get('storedFunctions', []); + let storedFunctions = context.workspaceState.get("storedFunctions", []); storedFunctions = [...new Set([...storedFunctions, ...functions])]; - context.workspaceState.update('storedFunctions', storedFunctions); + context.workspaceState.update("storedFunctions", storedFunctions); } function showStoredFunctions(context, outputChannel) { - const storedFunctions = context.workspaceState.get('storedFunctions', []); - - vscode.window.showQuickPick(storedFunctions, { - placeHolder: 'Select a function to invoke', - canPickMany: false - }).then(selectedFunction => { - if (selectedFunction) { - vscode.window.showInformationMessage(`Selected: ${selectedFunction}`); - promptForArgumentsSequentially(selectedFunction, outputChannel); - } - }); + const storedFunctions = context.workspaceState.get("storedFunctions", []); + + vscode.window + .showQuickPick(storedFunctions, { + placeHolder: "Select a function to invoke", + canPickMany: false, + }) + .then((selectedFunction) => { + if (selectedFunction) { + vscode.window.showInformationMessage(`Selected: ${selectedFunction}`); + promptForArgumentsSequentially(selectedFunction, outputChannel); + } + }); } - async function promptForArgumentsSequentially(selectedFunction, outputChannel) { + async function promptForArgumentsSequentially( + selectedFunction, + outputChannel + ) { const functionPattern = /(\w+)\((.*)\)/; const match = functionPattern.exec(selectedFunction); @@ -160,13 +175,15 @@ function activate(context) { } const functionName = match[1]; - const paramList = match[2].split(',').map(param => param.trim()); + const paramList = match[2].split(",").map((param) => param.trim()); let argumentValues = []; for (let param of paramList) { if (/int/.test(param)) { - const input = await vscode.window.showInputBox({ prompt: `Enter an integer value for ${param}` }); + const input = await vscode.window.showInputBox({ + prompt: `Enter an integer value for ${param}`, + }); const intValue = parseInt(input, 10); if (isNaN(intValue)) { vscode.window.showErrorMessage(`Invalid integer value for ${param}.`); @@ -174,7 +191,9 @@ function activate(context) { } argumentValues.push(intValue); } else if (/string/.test(param)) { - const input = await vscode.window.showInputBox({ prompt: `Enter a string value for ${param}` }); + const input = await vscode.window.showInputBox({ + prompt: `Enter a string value for ${param}`, + }); if (!input) { vscode.window.showErrorMessage(`Invalid string value for ${param}.`); return; @@ -183,19 +202,23 @@ function activate(context) { } } - const finalArgs = argumentValues.join(', '); + const finalArgs = argumentValues.join(", "); outputChannel.show(); outputChannel.appendLine(`Function: ${functionName}`); outputChannel.appendLine(`Arguments: ${finalArgs}`); - vscode.window.showInformationMessage(`Arguments captured. Press "Invoke" to execute the command.`, "Invoke").then(selection => { - if (selection === "Invoke") { - invokeChaincode(functionName, argumentValues); - } - }); + vscode.window + .showInformationMessage( + `Arguments captured. Press "Invoke" to execute the command.`, + "Invoke" + ) + .then((selection) => { + if (selection === "Invoke") { + invokeChaincode(functionName, argumentValues); + } + }); } - const hyperledgerProvider = new fabricsamples(); const treeViewProviderFabric = new TreeViewProvider( "fabric-network", @@ -762,9 +785,7 @@ function activate(context) { try { const latestBlockNumber = await getLatestBlockNumber( loadedConnectionProfile, - "mychannel", - "blockNumber", - 5 + "mychannel" ); if (!latestBlockNumber) { vscode.window.showErrorMessage( @@ -845,192 +866,222 @@ function activate(context) { `Debugging terminated: ${session.name}` ); }) - context.subscriptions.push( - vscode.commands.registerCommand("chaincode.Packagechaincode", async() => { - try { - vscode.window.showInformationMessage('Packaging chaincode'); - - const editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showErrorMessage('No active editor with chaincode file.'); - return; - } - const chaincodePath = path.dirname(editor.document.fileName); - - const packager = new BasePackager(); - const packageBuffer = await packager.package(chaincodePath, 'go'); - const outputPath = path.join(chaincodePath, 'chaincode.tar.gz'); - fs.writeFileSync(outputPath, packageBuffer); - - vscode.window.showInformationMessage(`Chaincode packaged successfully at: ${outputPath}`); + ); + + context.subscriptions.push( + vscode.commands.registerCommand("chaincode.Packagechaincode", async () => { + try { + vscode.window.showInformationMessage("Packaging chaincode"); + + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage( + "No active editor with chaincode file." + ); + return; + } + const chaincodePath = path.dirname(editor.document.fileName); + + const packager = new BasePackager(); + const packageBuffer = await packager.package(chaincodePath, "go"); + const outputPath = path.join(chaincodePath, "chaincode.tar.gz"); + fs.writeFileSync(outputPath, packageBuffer); + + vscode.window.showInformationMessage( + `Chaincode packaged successfully at: ${outputPath}` + ); } catch (error) { - vscode.window.showErrorMessage(`Error packaging chaincode: ${error.message}`); + vscode.window.showErrorMessage( + `Error packaging chaincode: ${error.message}` + ); } - }) - ); - - context.subscriptions.push( - vscode.commands.registerCommand("chaincode.Installchaincode", async() => { - try { - vscode.window.showInformationMessage('Installing chaincode'); - - const ccpPath = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: false, - filters: { JSON: ['json'] }, - openLabel: 'Select Connection Profile', - }); - - if (!ccpPath || ccpPath.length === 0) { - vscode.window.showErrorMessage('No connection profile selected.'); - return; - } - - const ccp = JSON.parse(fs.readFileSync(ccpPath[0].fsPath, 'utf8')); - const walletPath = path.join(__dirname, '..', 'wallet'); - const wallet = await Wallets.newFileSystemWallet(walletPath); - - const gateway = new Gateway(); - await gateway.connect(ccp, { - wallet, - identity: 'admin', - discovery: { enabled: true, asLocalhost: true }, - }); - - const client = gateway.getClient(); - const peers = Object.keys(ccp.peers || {}); - - const chaincodePackagePath = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: false, - filters: { TAR: ['gz'] }, - openLabel: 'Select Chaincode Package', - }); - - if (!chaincodePackagePath || chaincodePackagePath.length === 0) { - vscode.window.showErrorMessage('No chaincode package selected.'); - return; - } - - const packageBuffer = fs.readFileSync(chaincodePackagePath[0].fsPath); - - for (const peer of peers) { - const installRequest = { - targets: [client.getPeer(peer)], - chaincodePackage: packageBuffer, - }; - - const response = await client.installChaincode(installRequest); - if (response && response[0]?.response?.status === 200) { - vscode.window.showInformationMessage(`Chaincode installed on peer: ${peer}`); - } else { - vscode.window.showErrorMessage(`Failed to install chaincode on peer: ${peer}`); - } + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("chaincode.Installchaincode", async () => { + try { + vscode.window.showInformationMessage("Installing chaincode"); + + const ccpPath = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectMany: false, + filters: { JSON: ["json"] }, + openLabel: "Select Connection Profile", + }); + + if (!ccpPath || ccpPath.length === 0) { + vscode.window.showErrorMessage("No connection profile selected."); + return; + } + + const ccp = JSON.parse(fs.readFileSync(ccpPath[0].fsPath, "utf8")); + const walletPath = path.join(__dirname, "..", "wallet"); + const wallet = await Wallets.newFileSystemWallet(walletPath); + + const gateway = new Gateway(); + await gateway.connect(ccp, { + wallet, + identity: "admin", + discovery: { enabled: true, asLocalhost: true }, + }); + + const client = gateway.getClient(); + const peers = Object.keys(ccp.peers || {}); + + const chaincodePackagePath = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectMany: false, + filters: { TAR: ["gz"] }, + openLabel: "Select Chaincode Package", + }); + + if (!chaincodePackagePath || chaincodePackagePath.length === 0) { + vscode.window.showErrorMessage("No chaincode package selected."); + return; + } + + const packageBuffer = fs.readFileSync(chaincodePackagePath[0].fsPath); + + for (const peer of peers) { + const installRequest = { + targets: [client.getPeer(peer)], + chaincodePackage: packageBuffer, + }; + + const response = await client.installChaincode(installRequest); + if (response && response[0]?.response?.status === 200) { + vscode.window.showInformationMessage( + `Chaincode installed on peer: ${peer}` + ); + } else { + vscode.window.showErrorMessage( + `Failed to install chaincode on peer: ${peer}` + ); } - - gateway.disconnect(); + } + + gateway.disconnect(); } catch (error) { - vscode.window.showErrorMessage(`Error installing chaincode: ${error.message}`); + vscode.window.showErrorMessage( + `Error installing chaincode: ${error.message}` + ); } - }) - ); - - context.subscriptions.push( - vscode.commands.registerCommand("chaincode.Approvechaincode", async() => { - try { - vscode.window.showInformationMessage('Approving chaincode definition...'); - - const ccpPath = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: false, - filters: { JSON: ['json'] }, - openLabel: 'Select Connection Profile', - }); - - if (!ccpPath || ccpPath.length === 0) { - vscode.window.showErrorMessage('No connection profile selected.'); - return; - } - - const ccp = JSON.parse(fs.readFileSync(ccpPath[0].fsPath, 'utf8')); - const walletPath = path.join(__dirname, '..', 'wallet'); - const wallet = await Wallets.newFileSystemWallet(walletPath); - - const gateway = new Gateway(); - await gateway.connect(ccp, { - wallet, - identity: 'admin', - discovery: { enabled: true, asLocalhost: true }, - }); - - const network = await gateway.getNetwork('mychannel'); - const contract = network.getContract('lscc'); - - const approveRequest = { - chaincodeName: 'mychaincode', - chaincodeVersion: '1.0', - sequence: 1, - chaincodePackageId: '', // Replace with actual Package ID from install - }; - - await contract.submitTransaction('approveChaincodeDefinitionForMyOrg', JSON.stringify(approveRequest)); - - vscode.window.showInformationMessage('Chaincode definition approved successfully.'); - gateway.disconnect(); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("chaincode.Approvechaincode", async () => { + try { + vscode.window.showInformationMessage( + "Approving chaincode definition..." + ); + + const ccpPath = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectMany: false, + filters: { JSON: ["json"] }, + openLabel: "Select Connection Profile", + }); + + if (!ccpPath || ccpPath.length === 0) { + vscode.window.showErrorMessage("No connection profile selected."); + return; + } + + const ccp = JSON.parse(fs.readFileSync(ccpPath[0].fsPath, "utf8")); + const walletPath = path.join(__dirname, "..", "wallet"); + const wallet = await Wallets.newFileSystemWallet(walletPath); + + const gateway = new Gateway(); + await gateway.connect(ccp, { + wallet, + identity: "admin", + discovery: { enabled: true, asLocalhost: true }, + }); + + const network = await gateway.getNetwork("mychannel"); + const contract = network.getContract("lscc"); + + const approveRequest = { + chaincodeName: "mychaincode", + chaincodeVersion: "1.0", + sequence: 1, + chaincodePackageId: "", // Replace with actual Package ID from install + }; + + await contract.submitTransaction( + "approveChaincodeDefinitionForMyOrg", + JSON.stringify(approveRequest) + ); + + vscode.window.showInformationMessage( + "Chaincode definition approved successfully." + ); + gateway.disconnect(); } catch (error) { - vscode.window.showErrorMessage(`Error approving chaincode: ${error.message}`); + vscode.window.showErrorMessage( + `Error approving chaincode: ${error.message}` + ); } - - }) - ) - - context.subscriptions.push( - vscode.commands.registerCommand("chaincode.Commitchaincode", async() => { - try { - vscode.window.showInformationMessage('Committing chaincode definition...'); - - const ccpPath = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: false, - filters: { JSON: ['json'] }, - openLabel: 'Select Connection Profile', - }); - - if (!ccpPath || ccpPath.length === 0) { - vscode.window.showErrorMessage('No connection profile selected.'); - return; - } - - const ccp = JSON.parse(fs.readFileSync(ccpPath[0].fsPath, 'utf8')); - const walletPath = path.join(__dirname, '..', 'wallet'); - const wallet = await Wallets.newFileSystemWallet(walletPath); - - const gateway = new Gateway(); - await gateway.connect(ccp, { - wallet, - identity: 'admin', - discovery: { enabled: true, asLocalhost: true }, - }); - - const network = await gateway.getNetwork('mychannel'); - const contract = network.getContract('lscc'); - - const commitRequest = { - chaincodeName: 'mychaincode', - chaincodeVersion: '1.0', - sequence: 1, - endorsementPolicy: '', // Add your endorsement policy if needed - }; - - await contract.submitTransaction('commitChaincodeDefinition', JSON.stringify(commitRequest)); - - vscode.window.showInformationMessage('Chaincode definition committed successfully.'); - gateway.disconnect(); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand("chaincode.Commitchaincode", async () => { + try { + vscode.window.showInformationMessage( + "Committing chaincode definition..." + ); + + const ccpPath = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectMany: false, + filters: { JSON: ["json"] }, + openLabel: "Select Connection Profile", + }); + + if (!ccpPath || ccpPath.length === 0) { + vscode.window.showErrorMessage("No connection profile selected."); + return; + } + + const ccp = JSON.parse(fs.readFileSync(ccpPath[0].fsPath, "utf8")); + const walletPath = path.join(__dirname, "..", "wallet"); + const wallet = await Wallets.newFileSystemWallet(walletPath); + + const gateway = new Gateway(); + await gateway.connect(ccp, { + wallet, + identity: "admin", + discovery: { enabled: true, asLocalhost: true }, + }); + + const network = await gateway.getNetwork("mychannel"); + const contract = network.getContract("lscc"); + + const commitRequest = { + chaincodeName: "mychaincode", + chaincodeVersion: "1.0", + sequence: 1, + endorsementPolicy: "", // Add your endorsement policy if needed + }; + + await contract.submitTransaction( + "commitChaincodeDefinition", + JSON.stringify(commitRequest) + ); + + vscode.window.showInformationMessage( + "Chaincode definition committed successfully." + ); + gateway.disconnect(); } catch (error) { - vscode.window.showErrorMessage(`Error committing chaincode: ${error.message}`); + vscode.window.showErrorMessage( + `Error committing chaincode: ${error.message}` + ); } - }) - ) + }) ); async function generateWallet(context, connectionProfileName) { @@ -1171,7 +1222,6 @@ function activate(context) { const walletPath = path.join(os.homedir(), "wallets"); const wallet = await Wallets.newFileSystemWallet(walletPath); - if (!loadedConnectionProfile) { vscode.window.showErrorMessage("No connection profile loaded."); return; @@ -1183,7 +1233,7 @@ function activate(context) { return; } - const identityName = identities[0]; + const identityName = identities[0]; const gateway = new Gateway(); await gateway.connect(loadedConnectionProfile, { @@ -1192,10 +1242,13 @@ function activate(context) { discovery: { enabled: true, asLocalhost: false }, }); - - const channelName = Object.keys(loadedConnectionProfile.channels || {})[0]; + const channelName = Object.keys( + loadedConnectionProfile.channels || {} + )[0]; if (!channelName) { - vscode.window.showErrorMessage("No channel found in the connection profile."); + vscode.window.showErrorMessage( + "No channel found in the connection profile." + ); return; } @@ -1208,26 +1261,246 @@ function activate(context) { return; } - const chaincodeName = chaincodes[0]; + const chaincodeName = chaincodes[0]; const network = await gateway.getNetwork(channelName); const contract = network.getContract(chaincodeName); - const result = await contract.submitTransaction(functionName, ...args); vscode.window.showInformationMessage( `Transaction invoked successfully. Result: ${result.toString()}` ); console.log("Transaction result:", result.toString()); - await gateway.disconnect(); } catch (error) { - vscode.window.showErrorMessage(`Error invoking chaincode: ${error.message}`); + vscode.window.showErrorMessage( + `Error invoking chaincode: ${error.message}` + ); console.error("Error invoking chaincode:", error); } } } +async function generateEnvFile() { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { + vscode.window.showErrorMessage("No workspace found."); + return; + } + + const workspacePath = workspaceFolders[0].uri.fsPath; + const envFilePath = path.join(workspacePath, "chaincode.env"); + + if (fs.existsSync(envFilePath)) { + vscode.window.showInformationMessage("chaincode.env already exists."); + return; + } + + const defaultEnvContent = `CHAINCODE_SERVER_ADDRESS=localhost:9999 +CHAINCODE_ID= +CHAINCODE_TLS_DISABLED=false +CHAINCODE_TLS_CERT= +CHAINCODE_TLS_KEY= +CHAINCODE_CLIENT_CA_CERT= +CORE_PEER_ADDRESS=`; + + try { + fs.writeFileSync(envFilePath, defaultEnvContent); + vscode.window.showInformationMessage( + "chaincode.env file generated successfully." + ); + } catch (error) { + console.error("Error generating chaincode.env:", error); + vscode.window.showErrorMessage( + `Failed to create chaincode.env: ${error.message}` + ); + } +} + +async function generateLaunchConfig() { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders) { + vscode.window.showErrorMessage("No workspace found."); + return; + } + + const workspacePath = workspaceFolders[0].uri.fsPath; + const launchJsonPath = path.join(workspacePath, ".vscode", "launch.json"); + const envFilePath = path.join(workspacePath, "chaincode.env"); + + function readEnvConfig() { + const defaultConfig = { + chaincodeServerAddress: "localhost:9999", + chaincodeId: "", + tlsEnabled: false, + tlsCertFile: "", + tlsKeyFile: "", + tlsRootCertFile: "", + peerAddress: "", + }; + + if (!fs.existsSync(envFilePath)) { + vscode.window.showWarningMessage( + "chaincode.env not found. Using default configuration." + ); + return defaultConfig; + } + + try { + const envContent = fs.readFileSync(envFilePath, "utf8").trim(); + const envConfig = { ...defaultConfig }; + + // Split into lines, ignoring empty ones + const envLines = envContent + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#")); + + for (const line of envLines) { + // Ensure we only split at the first "=" + const [key, ...valueParts] = line.split("="); + if (!key || valueParts.length === 0) continue; + + const keyTrimmed = key.trim(); + const valueTrimmed = valueParts.join("=").trim(); + // Join back in case value had `=` in it + + switch (keyTrimmed) { + case "CHAINCODE_SERVER_ADDRESS": + envConfig.chaincodeServerAddress = valueTrimmed; + break; + case "CHAINCODE_ID": + envConfig.chaincodeId = valueTrimmed; + break; + case "CHAINCODE_TLS_DISABLED": + envConfig.tlsEnabled = valueTrimmed.toLowerCase() === "true"; + break; + case "CHAINCODE_TLS_CERT": + envConfig.tlsCertFile = valueTrimmed; + break; + case "CHAINCODE_TLS_KEY": + envConfig.tlsKeyFile = valueTrimmed; + break; + case "CHAINCODE_CLIENT_CA_CERT": + envConfig.tlsRootCertFile = valueTrimmed; + break; + case "CORE_PEER_ADDRESS": + envConfig.peerAddress = valueTrimmed; + break; + + default: + console.warn(`Ignoring unrecognized key: ${keyTrimmed}`); + } + } + + if (!envConfig.chaincodeId) { + vscode.window.showWarningMessage( + "Warning: CHAINCODE_ID not found in chaincode.env" + ); + } + + return envConfig; + } catch (error) { + console.error("Error reading env file:", error); + vscode.window.showErrorMessage( + `Error reading chaincode.env: ${error.message}` + ); + return defaultConfig; + } + } + + async function findChaincodeEntryPoint(workspacePath) { + const options = { + canSelectMany: false, + openLabel: "Select Chaincode Entry Point", + filters: { + Go: ["go"], + }, + }; + + const fileUri = await vscode.window.showOpenDialog(options); + if (!fileUri || fileUri.length === 0) { + throw new Error("No chaincode file selected."); + } + + let selectedPath = fileUri[0].fsPath; + if (selectedPath.startsWith(workspacePath)) { + selectedPath = + "${workspaceFolder}" + selectedPath.substring(workspacePath.length); + } + + return selectedPath; + } + + async function updateLaunchConfig() { + try { + const envConfig = readEnvConfig(); + const programPath = await findChaincodeEntryPoint(workspacePath); + const launchConfig = { + version: "0.2.0", + configurations: [ + { + name: "Hyperledger Fabric Debugger", + type: "delve", + request: "launch", + mode: "debug", + program: programPath, + env: { + CHAINCODE_SERVER_ADDRESS: envConfig.chaincodeServerAddress, + CHAINCODE_ID: envConfig.chaincodeId, + CHAINCODE_TLS_DISABLED: envConfig.tlsEnabled.toString(), + ...(envConfig.tlsEnabled && { + CHAINCODE_TLS_CERT: envConfig.tlsCertFile, + CHAINCODE_TLS_KEY: envConfig.tlsKeyFile, + CHAINCODE_CLIENT_CA_CERT: envConfig.tlsRootCertFile, + }), + }, + args: envConfig.peerAddress + ? [`--peer.address=${envConfig.peerAddress}`] + : [], + port: 2345, + }, + ], + }; + + fs.mkdirSync(path.dirname(launchJsonPath), { recursive: true }); + + fs.writeFileSync(launchJsonPath, JSON.stringify(launchConfig, null, 2)); + vscode.window.showInformationMessage( + "launch.json updated for Fabric chaincode debugging." + ); + } catch (error) { + console.error("Error updating launch config:", error); + vscode.window.showErrorMessage( + `Failed to update launch configuration: ${error.message}` + ); + } + } + + await updateLaunchConfig(); + + if (fs.existsSync(envFilePath)) { + let restartTimeout; + fs.watch(envFilePath, (eventType) => { + if (eventType === "change") { + vscode.window.showInformationMessage( + "Detected changes in chaincode.env. Updating debug configurations..." + ); + + clearTimeout(restartTimeout); + restartTimeout = setTimeout(() => { + updateLaunchConfig().then(() => { + vscode.commands.executeCommand("workbench.action.debug.stop"); + setTimeout(() => { + vscode.commands.executeCommand("workbench.action.debug.start"); + }, 1000); + }); + }, 1500); + } + }); + } +} + function extractNetworkDetails(profile) { const organizations = Object.keys(profile.organizations || {}); const peers = Object.values(profile.peers || {}).map((peer) => peer.url); @@ -1332,11 +1605,10 @@ function extractWalletDetails(walletData) { console.warn("Missing required wallet data fields:"); } } - -function deactivate() { - console.log("Deactivating Fabric Debugger extension..."); } +function deactivate() {} + module.exports = { activate, deactivate, diff --git a/package.json b/package.json index a93575a..0d57ca5 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,10 @@ "type": "number", "default": 2345, "description": "Port number for the Delve debugger" + }, + "env": { + "type": "object", + "description": "Environment variables for chaincode debugging" } } } @@ -261,13 +265,13 @@ "icon": "media/wallet.png" }, { - "command":"chaincode.Packagechaincode", + "command": "chaincode.Packagechaincode", "title": "Package chaincode", "icon": "$(packagecc)" }, { "command": "chaincode.Installchaincode", - "title" : "Install chaincode", + "title": "Install chaincode", "icon": "$(installcc)" }, { diff --git a/src/blockReader/blockQueries.js b/src/blockReader/blockQueries.js index d6c04e0..631b6c3 100644 --- a/src/blockReader/blockQueries.js +++ b/src/blockReader/blockQueries.js @@ -28,7 +28,7 @@ function extractCredentials(loadedConnectionProfile) { async function connectToFabric( loadedConnectionProfile, blockNumber, - channelName = "mychannel", + channelName, contractName = "qscc" ) { let gateway; @@ -63,6 +63,7 @@ async function connectToFabric( console.error("Error: Block data is not a Buffer:", blockData); throw new Error("Expected raw binary data (Buffer) for blockData."); } + const decodedBlock = await decodeBlock(blockData); return decodedBlock; @@ -90,8 +91,8 @@ async function decodeBlock(blockData) { } const root = await protobuf.load( - "/Users/claudiaemmanuel/vscode/fabric-debugger/src/protos/block.proto" //modify this to the file location on your local computer + "/Users/claudiaemmanuel/vscode/fabric-debugger/src/protos/block.proto" ); const Block = root.lookupType("common.Block"); const message = Block.decode(blockData); @@ -106,8 +107,8 @@ async function decodeBlock(blockData) { async function decodeChainInfo(binaryData) { const root = await protobuf.load( - "/Users/claudiaemmanuel/vscode/fabric-debugger/src/protos/common.proto" //modify this to the file location on your local computer + "/Users/claudiaemmanuel/vscode/fabric-debugger/src/protos/common.proto" ); const ChainInfo = root.lookupType("common.BlockchainInfo"); const message = ChainInfo.decode(binaryData); @@ -118,10 +119,7 @@ async function decodeChainInfo(binaryData) { return chainInfoObject; } -async function getLatestBlockNumber( - loadedConnectionProfile, - channelName = "mychannel" -) { +async function getLatestBlockNumber(loadedConnectionProfile, channelName) { let gateway; try { diff --git a/src/blockReader/blockchainExplorer.js b/src/blockReader/blockchainExplorer.js index 1d63371..11341b9 100644 --- a/src/blockReader/blockchainExplorer.js +++ b/src/blockReader/blockchainExplorer.js @@ -8,17 +8,21 @@ class BlockchainTreeDataProvider { } refresh(blocks) { + console.log("Refreshing with blocks:", blocks); this.blockData = blocks; this.onDidChangeTreeDataEvent.fire(); } getChildren(element) { if (!element) { - return this.blockData.map((block) => ({ - label: `Block ${block.header.number}`, - collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - data: block, - })); + return this.blockData.map((block) => { + console.log("Block data:", block); + return { + label: `Block ${block.header.number}`, + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + data: block, + }; + }); } else { const block = element.data; const transactions = block?.data?.data || []; @@ -29,22 +33,24 @@ class BlockchainTreeDataProvider { return []; } - // Block summary node + const timestamp = + transactions.length > 0 + ? new Date( + transactions[0]?.payload?.header?.channel_header?.timestamp + ).toUTCString() + : "Unknown"; + const summaryNode = { label: `Summary`, - description: `Height: ${block.header.number}, Transactions: ${ - transactions.length - }, Timestamp: ${new Date(block.header.data_hash).toUTCString()}`, + description: `Height: ${block.header.number}, Transactions: ${transactions.length}, Timestamp: ${timestamp}`, collapsibleState: vscode.TreeItemCollapsibleState.None, }; - // Transaction nodes const transactionNodes = transactions.map((tx, index) => { + const txId = tx?.payload?.header?.channel_header?.tx_id || "N/A"; return { label: `Transaction ${index + 1}`, - description: `Transaction ID: ${ - tx?.payload?.header?.channel_header?.tx_id || "N/A" - }`, + description: `Transaction ID: ${txId}`, collapsibleState: vscode.TreeItemCollapsibleState.None, }; }); diff --git a/src/debugAdapter/DelveDebugAdapterDescriptorFactory.js b/src/debugAdapter/DelveDebugAdapterDescriptorFactory.js index d958a09..ed2760a 100644 --- a/src/debugAdapter/DelveDebugAdapterDescriptorFactory.js +++ b/src/debugAdapter/DelveDebugAdapterDescriptorFactory.js @@ -11,9 +11,10 @@ class DelveDebugAdapterDescriptorFactory { const config = session.configuration; const program = config.program; const port = config.port || 2345; - console.log( - `Hyperledger Fabric Debugger Config: Port=${port}, Program=${program}` - ); + // console.log( + // `Hyperledger Fabric Debugger Config: Port=${port}, Program=${program}` + // ); + if (!program) { vscode.window.showErrorMessage("No program specified to debug.");