Skip to content

Commit

Permalink
fix(connector-besu): error handling of DeployContractSolidityBytecode…
Browse files Browse the repository at this point in the history
…Endpoint

1. Uses the new utility function from the core package in the catch block
so that HTTP `statusCode` is matching the intent of the thrower (e.g.
correctly differentiates between user-error and developer error)
2. Updates the `deployContract` method of the besu connector so that it
correctly specifies the intent of the errors thrown as either user error
or developer error via setting the `statusCode` property of the HTTP errors
to either 4xx or 5xx depending on the cause.
3. Provides a template for future similar changes (of which we'll need
dozens to update all the REST API endpoints)

Depends on hyperledger#2868

Related to but does not conclude: hyperledger#1747

Signed-off-by: Peter Somogyvari <peter.somogyvari@accenture.com>
(cherry picked from commit 5816904)
  • Loading branch information
petermetz committed Nov 15, 2023
1 parent 2510ff5 commit c6dd152
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 84 deletions.
2 changes: 2 additions & 0 deletions packages/cactus-plugin-ledger-connector-besu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@hyperledger/cactus-core-api": "2.0.0-alpha.2",
"axios": "1.5.1",
"express": "4.18.2",
"http-errors": "2.0.0",
"joi": "17.9.1",
"openapi-types": "9.1.0",
"prom-client": "13.2.0",
Expand All @@ -78,6 +79,7 @@
"@hyperledger/cactus-test-tooling": "2.0.0-alpha.2",
"@types/body-parser": "1.19.4",
"@types/express": "4.17.19",
"@types/http-errors": "2.0.4",
"@types/uuid": "9.0.6",
"body-parser": "1.20.2",
"key-encoder": "2.0.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Server as SocketIoServer } from "socket.io";
import type { Socket as SocketIoSocket } from "socket.io";
import type { Express } from "express";
import { Optional } from "typescript-optional";
import createHttpError from "http-errors";

import OAS from "../json/openapi.json";

Expand Down Expand Up @@ -814,93 +815,101 @@ export class PluginLedgerConnectorBesu
const fnTag = `${this.className}#deployContract()`;
Checks.truthy(req, `${fnTag} req`);
if (isWeb3SigningCredentialNone(req.web3SigningCredential)) {
throw new Error(`${fnTag} Cannot deploy contract with pre-signed TX`);
}
const { contractName } = req;
if (req.keychainId != undefined && req.contractName != undefined) {
const keychainPlugin = this.pluginRegistry.findOneByKeychainId(
req.keychainId,
);
Checks.truthy(
keychainPlugin,
`${fnTag} keychain for ID:"${req.keychainId}"`,
throw createHttpError[400](
`${fnTag} Cannot deploy contract with pre-signed TX`,
);
if (!keychainPlugin.has(req.contractName)) {
throw new Error(
`${fnTag} Cannot create an instance of the contract because the contractName and the contractName on the keychain does not match`,
);
}
const networkId = await this.web3.eth.net.getId();
}
const { keychainId, contractName } = req;
if (!keychainId || !req.contractName) {
const errorMessage = `${fnTag} Cannot deploy contract without keychainId and the contractName.`;
throw createHttpError[400](errorMessage);
}

const tmpContract = new this.web3.eth.Contract(req.contractAbi);
const deployment = tmpContract.deploy({
data: req.bytecode,
arguments: req.constructorArgs,
});
const keychainPlugin = this.pluginRegistry.findOneByKeychainId(keychainId);

const abi = deployment.encodeABI();
const data = abi.startsWith("0x") ? abi : `0x${abi}`;
this.log.debug(`Deploying "${req.contractName}" with data %o`, data);
if (!keychainPlugin) {
const errorMessage =
`${fnTag} The plugin registry does not contain` +
` a keychain plugin for ID:"${req.keychainId}"`;
throw createHttpError[400](errorMessage);
}

const web3SigningCredential = req.web3SigningCredential as
| Web3SigningCredentialPrivateKeyHex
| Web3SigningCredentialCactusKeychainRef;
if (!keychainPlugin.has(contractName)) {
const errorMessage =
`${fnTag} Cannot create an instance of the contract instance because` +
`the contractName in the request does not exist on the keychain`;
throw new createHttpError[400](errorMessage);
}

const runTxResponse = await this.transact({
transactionConfig: {
data,
from: web3SigningCredential.ethAccount,
gas: req.gas,
gasPrice: req.gasPrice,
},
consistencyStrategy: {
blockConfirmations: 0,
receiptType: ReceiptType.NodeTxPoolAck,
timeoutMs: req.timeoutMs || 60000,
},
web3SigningCredential,
privateTransactionConfig: req.privateTransactionConfig,
});
const networkId = await this.web3.eth.net.getId();

const keychainHasContract = await keychainPlugin.has(contractName);
if (keychainHasContract) {
this.log.debug(`Keychain has the contract, updating networks...`);

const { transactionReceipt: receipt } = runTxResponse;
const { status, contractAddress } = receipt;

if (status && contractAddress) {
const networkInfo = { address: contractAddress };
const contractStr = await keychainPlugin.get(contractName);
const contractJSON = JSON.parse(contractStr);
this.log.debug("Contract JSON: \n%o", JSON.stringify(contractJSON));
const contract = new this.web3.eth.Contract(
contractJSON.abi,
contractAddress,
);
this.contracts[contractName] = contract;
const tmpContract = new this.web3.eth.Contract(req.contractAbi);
const deployment = tmpContract.deploy({
data: req.bytecode,
arguments: req.constructorArgs,
});

const network = { [networkId]: networkInfo };
contractJSON.networks = network;
const abi = deployment.encodeABI();
const data = abi.startsWith("0x") ? abi : `0x${abi}`;
this.log.debug(`Deploying "${req.contractName}" with data %o`, data);

await keychainPlugin.set(contractName, JSON.stringify(contractJSON));
}
} else {
throw new Error(
`${fnTag} Cannot create an instance of the contract because the contractName and the contractName on the keychain does not match`,
);
}
const web3SigningCredential = req.web3SigningCredential as
| Web3SigningCredentialPrivateKeyHex
| Web3SigningCredentialCactusKeychainRef;

// creating solidity byte code response
const deployResponse: DeployContractSolidityBytecodeV1Response = {
transactionReceipt: runTxResponse.transactionReceipt,
};
const runTxResponse = await this.transact({
transactionConfig: {
data,
from: web3SigningCredential.ethAccount,
gas: req.gas,
gasPrice: req.gasPrice,
},
consistencyStrategy: {
blockConfirmations: 0,
receiptType: ReceiptType.NodeTxPoolAck,
timeoutMs: req.timeoutMs || 60000,
},
web3SigningCredential,
privateTransactionConfig: req.privateTransactionConfig,
});

const keychainHasContract = await keychainPlugin.has(contractName);

return deployResponse;
if (!keychainHasContract) {
const errorMessage =
`${fnTag} Cannot create an instance of the contract instance because` +
`the contractName in the request does not exist on the keychain`;
throw new createHttpError[400](errorMessage);
}
throw new Error(
`${fnTag} Cannot deploy contract without keychainId and the contractName`,
);

this.log.debug(`Keychain has the contract, updating networks...`);

const { transactionReceipt: receipt } = runTxResponse;
const { status, contractAddress } = receipt;

if (status && contractAddress) {
const networkInfo = { address: contractAddress };
const contractStr = await keychainPlugin.get(contractName);
const contractJSON = JSON.parse(contractStr);
this.log.debug("Contract JSON: \n%o", JSON.stringify(contractJSON));
const contract = new this.web3.eth.Contract(
contractJSON.abi,
contractAddress,
);
this.contracts[contractName] = contract;

const network = { [networkId]: networkInfo };
contractJSON.networks = network;

await keychainPlugin.set(contractName, JSON.stringify(contractJSON));
}

// creating solidity byte code response
const deployResponse: DeployContractSolidityBytecodeV1Response = {
transactionReceipt: runTxResponse.transactionReceipt,
};

return deployResponse;
}

public async signTransaction(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Express, Request, Response } from "express";
import type { Express, Request, Response } from "express";

import {
IWebServiceEndpoint,
Expand All @@ -14,7 +14,10 @@ import {
IAsyncProvider,
} from "@hyperledger/cactus-common";

import { registerWebServiceEndpoint } from "@hyperledger/cactus-core";
import {
handleRestEndpointException,
registerWebServiceEndpoint,
} from "@hyperledger/cactus-core";

import { PluginLedgerConnectorBesu } from "../plugin-ledger-connector-besu";
import { DeployContractSolidityBytecodeV1Request } from "../generated/openapi/typescript-axios";
Expand Down Expand Up @@ -86,18 +89,16 @@ export class DeployContractSolidityBytecodeEndpoint
}

public async handleRequest(req: Request, res: Response): Promise<void> {
const fnTag = `${this.className}#handleRequest()`;
const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`;
this.log.debug(reqTag);
const reqBody: DeployContractSolidityBytecodeV1Request = req.body;
try {
const resBody = await this.options.connector.deployContract(reqBody);
res.json(resBody);
} catch (ex) {
this.log.error(`Crash while serving ${reqTag}`, ex);
res.status(500).json({
message: "Internal Server Error",
error: ex?.stack || ex?.message,
});
const errorMsg = `${reqTag} ${fnTag} Failed to deploy contract:`;
handleRestEndpointException({ errorMsg, log: this.log, error: ex, res });
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ test(testCase, async (t: Test) => {
});

const listenOptions: IListenOptions = {
hostname: "localhost",
hostname: "127.0.0.1",
port: 0,
server,
};
Expand Down
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7479,10 +7479,12 @@ __metadata:
"@hyperledger/cactus-test-tooling": 2.0.0-alpha.2
"@types/body-parser": 1.19.4
"@types/express": 4.17.19
"@types/http-errors": 2.0.4
"@types/uuid": 9.0.6
axios: 1.5.1
body-parser: 1.20.2
express: 4.18.2
http-errors: 2.0.0
joi: 17.9.1
key-encoder: 2.0.3
openapi-types: 9.1.0
Expand Down Expand Up @@ -12909,6 +12911,13 @@ __metadata:
languageName: node
linkType: hard

"@types/http-errors@npm:2.0.4":
version: 2.0.4
resolution: "@types/http-errors@npm:2.0.4"
checksum: 1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3
languageName: node
linkType: hard

"@types/http-proxy@npm:^1.17.8":
version: 1.17.9
resolution: "@types/http-proxy@npm:1.17.9"
Expand Down

0 comments on commit c6dd152

Please sign in to comment.