Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement notifications in error handling logic #52

Merged
merged 46 commits into from
Oct 11, 2024

Conversation

jahabeebs
Copy link
Collaborator

🤖 Linear

Closes GRT-59

Description

  • Adds a simple notification service to the error handling logic

@jahabeebs jahabeebs self-assigned this Sep 26, 2024
Copy link

linear bot commented Sep 26, 2024

@jahabeebs jahabeebs force-pushed the feat/grt-60-error-handling-2 branch from 1437553 to 7ac5c81 Compare September 26, 2024 17:05
Comment on lines 22 to 48
constructor(config: DiscordNotifierConfig) {
const intents = new IntentsBitField().add(
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildMessages,
);
this.client = new Client({ intents });
this.config = config;
this.readyPromise = this.initialize();
}

/**
* Initializes the Discord notifier by logging in with the bot token and waiting for the "ready" event.
* @returns {Promise<void>} A promise that resolves when the Discord bot is ready.
* @throws {Error} If the initialization fails.
*/
private async initialize(): Promise<void> {
try {
await this.client.login(this.config.discordBotToken);
await new Promise<void>((resolve) => {
this.client.once("ready", () => {
console.log("Discord bot is ready");
resolve();
});
});
} catch (error) {
console.error("Failed to initialize Discord notifier:", error);
throw error;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i kinda don't like the any usage on context tbh although I get it jajaj, but it's unsafe (and with some stricter rules from the new template, linter wouldn't allow you to type any)
maybe we can use generics here?

cc @0xyaco @0xkenj1 thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get around context any I suppose we would have to define a context type for each error (or classify them into groups) but I think this might take a while--let's see what the others think

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's standardize the context to:

{
  request: Request,
  response?: Response,
  dispute?: Dispute,
  event?: EboEvent<EboEventName>,
  registry: EboRegistry
}

This should be enough for our use cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think I need to do this in the error handling PR rather than this notification PR though

Comment on lines 52 to 53
discordBotToken: process.env.DISCORD_BOT_TOKEN!,
discordChannelId: process.env.DISCORD_CHANNEL_ID!,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of retrieving it from env, it's better from a config or env object previously validated with Zod
the notifier is kinda like the logger, so injecting it as argument makes sense, wdyt?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DI is the way to go here, I agree!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored to use DI and zod

@jahabeebs jahabeebs requested a review from 0xnigir1 September 30, 2024 22:26
@jahabeebs jahabeebs force-pushed the feat/grt-59-implement-notifications branch from a73d337 to e03e697 Compare October 1, 2024 16:16
Copy link
Collaborator

@0xnigir1 0xnigir1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm to me now, just a few small comments & then is good to go

Comment on lines 64 to 65
DISCORD_BOT_TOKEN: envData.DISCORD_BOT_TOKEN || "",
DISCORD_CHANNEL_ID: envData.DISCORD_CHANNEL_ID || "",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does the empty string mean on Discord configuration?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was because of type issues that I had before implementing zod but now it's not needed so I deleted--thanks for pointing it out

Comment on lines 77 to 83
private formatErrorMessage(error: Error, context: any): string {
return `**Error:** ${error.name} - ${error.message}\n**Context:**\n\`\`\`json\n${JSON.stringify(
context,
null,
2,
)}\n\`\`\``;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk if context could have a bigint field, but it will break since JSON is not compatible natively with bigints
two alternatives:

  • write a wrapper of stringify and manually handle bigint cases
  • use stringify from Viem which already handles this case

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a super nice catch.

Apparently there's a way to do this "natively" by just using a second param of JSON.stringify. This SO answer has more info about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great catch, switched it out with viem's stringify. I checked out the stack overflow link but it seems like using the native JSON.stringify requires intermediate types and I think it would be best just to use viem's helper method for this

Base automatically changed from feat/grt-60-error-handling-2 to dev October 8, 2024 14:07
…notifications

# Conflicts:
#	packages/automated-dispute/src/exceptions/customContractError.ts
#	packages/automated-dispute/src/exceptions/errorFactory.ts
#	packages/automated-dispute/src/exceptions/errorHandler.ts
#	packages/automated-dispute/src/exceptions/index.ts
#	packages/automated-dispute/src/providers/protocolProvider.ts
#	packages/automated-dispute/src/services/eboActor.ts
#	packages/automated-dispute/src/services/index.ts
#	packages/automated-dispute/src/types/errorTypes.ts
#	packages/automated-dispute/tests/exceptions/errorFactory.spec.ts
#	packages/automated-dispute/tests/services/eboActor.spec.ts
#	packages/automated-dispute/tests/services/eboActor/onResponseProposed.spec.ts
#	pnpm-lock.yaml
@jahabeebs
Copy link
Collaborator Author

@0xyaco I fixed the merge conflicts here and kind of had to redo most of the notification logic because in the error handling issue we changed where we do the notification logic...so here's a brief summary of the changes

  • For our custom contract errors we are doing the this.notificationService.notifyError logic now within errorHandler.ts. You can see that we do a strategy.shouldNotify check there rather than exporting notifyError as a separate function
  • I thought it would be more elegant to initialize the error handler with the notifications service (injection) so now we do this.errorHandler.handle in eboActor
  • Removed notification service logic from the onXXX handlers in eboActor because these errors will bubble up to processEvents which is where the notification logic will happen
  • I saw a bunch of places in eboProcessor where we had TODOs for adding notification logic so I added notification logic there--not sure if you were ok with using our notificationService for this or if you had different notification logic in mind
  • Added the notification service to test mocks

There are some strange test errors happening with eboProcess.spec.ts--I've been digging around seeing if I missed a mock somewhere
but it's strange because I'm not using encodeFunctionData or toJSON anywhere so I have to do a bit more digging

@ebo-agent/automated-dispute:test: AbiFunctionNotFoundError: Function "toJSON" not found on ABI.
@ebo-agent/automated-dispute:test: Make sure you are using the correct ABI and that the function exists on it.
@ebo-agent/automated-dispute:test: 
@ebo-agent/automated-dispute:test: Docs: https://viem.sh/docs/contract/encodeFunctionData
@ebo-agent/automated-dispute:test: Version: [email protected]
@ebo-agent/automated-dispute:test:  ❯ prepareEncodeFunctionData ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/utils/abi/prepareEncodeFunctionData.ts:101:22
@ebo-agent/automated-dispute:test:  ❯ ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/utils/abi/encodeFunctionData.ts:85:12
@ebo-agent/automated-dispute:test:  ❯ encodeFunctionData ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/utils/abi/encodeFunctionData.ts:86:5
@ebo-agent/automated-dispute:test:  ❯ simulateContract ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/actions/public/simulateContract.ts:228:20
@ebo-agent/automated-dispute:test:  ❯ ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/utils/getAction.ts:41:22
@ebo-agent/automated-dispute:test:  ❯ Proxy.<anonymous> ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/actions/getContract.ts:585:16
@ebo-agent/automated-dispute:test:  ❯ printComplexValue ../../node_modules/.pnpm/@[email protected]/node_modules/@vitest/pretty-format/dist/index.js:943:24
@ebo-agent/automated-dispute:test:  ❯ printer ../../node_modules/.pnpm/@[email protected]/node_modules/@vitest/pretty-format/dist/index.js:1046:10
@ebo-agent/automated-dispute:test:  ❯ printObjectProperties ../../node_modules/.pnpm/@[email protected]/node_modules/@vitest/pretty-format/dist/index.js:121:21
@ebo-agent/automated-dispute:test:  ❯ printComplexValue ../../node_modules/.pnpm/@[email protected]/node_modules/@vitest/pretty-format/dist/index.js:980:196
@ebo-agent/automated-dispute:test: 
@ebo-agent/automated-dispute:test: ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
@ebo-agent/automated-dispute:test: Serialized Error: { details: undefined, docsPath: '/docs/contract/encodeFunctionData', metaMessages: undefined, shortMessage: 'Function "toJSON" not found on ABI.\nMake sure you are using the correct ABI and that the function exists on it.', version: '[email protected]', walk: 'Function<walk>' }
@ebo-agent/automated-dispute:test: This error originated in "tests/services/eboProcessor.spec.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
@ebo-agent/automated-dispute:test: The latest test that might've caused the error is "keeps the last block checked unaltered when something fails during sync". It might mean one of the following:
@ebo-agent/automated-dispute:test: - The error was thrown, while Vitest was running this test.
@ebo-agent/automated-dispute:test: - If the error occurred after the test had been completed, this was the last documented test before it was thrown.
@ebo-agent/automated-dispute:test: 
@ebo-agent/automated-dispute:test: ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
@ebo-agent/automated-dispute:test: AbiFunctionNotFoundError: Function "toJSON" not found on ABI.
@ebo-agent/automated-dispute:test: Make sure you are using the correct ABI and that the function exists on it.
@ebo-agent/automated-dispute:test: 
@ebo-agent/automated-dispute:test: Docs: https://viem.sh/docs/contract/encodeFunctionData
@ebo-agent/automated-dispute:test: Version: [email protected]
@ebo-agent/automated-dispute:test:  ❯ prepareEncodeFunctionData ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/utils/abi/prepareEncodeFunctionData.ts:101:22
@ebo-agent/automated-dispute:test:  ❯ ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/utils/abi/encodeFunctionData.ts:85:12
@ebo-agent/automated-dispute:test:  ❯ encodeFunctionData ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/utils/abi/encodeFunctionData.ts:86:5
@ebo-agent/automated-dispute:test:  ❯ writeContract ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/actions/wallet/writeContract.ts:159:16
@ebo-agent/automated-dispute:test:  ❯ writeContract ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/clients/decorators/wallet.ts:713:30
@ebo-agent/automated-dispute:test:  ❯ Proxy.<anonymous> ../../node_modules/.pnpm/[email protected][email protected][email protected]/node_modules/viem/actions/getContract.ts:726:16
@ebo-agent/automated-dispute:test:  ❯ printComplexValue ../../node_modules/.pnpm/@[email protected]/node_modules/@vitest/pretty-format/dist/index.js:943:24
@ebo-agent/automated-dispute:test:  ❯ printer ../../node_modules/.pnpm/@[email protected]/node_modules/@vitest/pretty-format/dist/index.js:1046:10
@ebo-agent/automated-dispute:test:  ❯ printObjectProperties ../../node_modules/.pnpm/@[email protected]/node_modules/@vitest/pretty-format/dist/index.js:121:21
@ebo-agent/automated-dispute:test:  ❯ printComplexValue ../../node_modules/.pnpm/@vitest+pretty-format@2.

Copy link
Collaborator

@0xyaco 0xyaco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice choice of using injection for the error handler init and using a notitications service!

Left some comments, let me know if you need some help with those tests failing. The PR is almost ready. 🤏

* @param {DiscordNotifierConfig} config - The configuration object for the DiscordNotifier.
* @returns {Promise<DiscordNotifier>} A promise that resolves to a DiscordNotifier instance.
*/
public static async create(config: DiscordNotifierConfig): Promise<DiscordNotifier> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's pass an ILogger param here to DI a logger instead of using console.

this.notificationService = notificationService;
}

public async handle(error: CustomContractError, logger: ILogger): Promise<void> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop the ILogger param here and put it in the constructor.

console.log("Error notification sent to Discord");
} catch (err) {
console.error("Failed to send error notification to Discord:", err);
throw err;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not throw here for the moment, just error logging needed.

Ideally we should have redundant ways of notifying (like Discord, SMS, email, etc.); but for the moment, we don't want the whole agent to fail if Discord's API is down + there are third party monitoring solutions that are designed to do this so.. In case the operator needs more notification redundancy, they can always install some of those tools.

@jahabeebs
Copy link
Collaborator Author

@0xyaco Implemented your suggestions and fixed the tests ✅

  • Now passing ILogger param to create() instead of using console
  • Moving ILogger param in errorHandler to constructor
  • Just logging rather than throwing error in notifyError in discordNotifier

Also the reason the test was throwing that weird error was because of a serialization error in an expect() statement so I broke up the expect statement more to avoid the issue

@jahabeebs jahabeebs requested a review from 0xyaco October 10, 2024 17:51
0xyaco
0xyaco previously approved these changes Oct 10, 2024
Copy link
Collaborator

@0xyaco 0xyaco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such a weird bug the one with the tests lol.

Looking good!

0xnigir1
0xnigir1 previously approved these changes Oct 10, 2024
Copy link
Collaborator

@0xnigir1 0xnigir1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, just a smol comment but you can tackle it on a different PR 🫡

Comment on lines 64 to 84
public async notifyError(error: Error, context: any): Promise<void> {
try {
const channel = await this.client.channels.fetch(this.config.discordChannelId);
if (!channel || !channel.isTextBased()) {
throw new Error("Discord channel not found or is not text-based");
}
const errorMessage = this.formatErrorMessage(error, context);
await (channel as TextChannel).send(errorMessage);
this.logger.info("Error notification sent to Discord");
} catch (err) {
this.logger.error(`Failed to send error notification to Discord: ${err}`);
}
}

/**
* Formats the error message to be sent to Discord.
* @param {Error} error - The error object.
* @param {any} context - Additional context information.
* @returns {string} The formatted error message.
*/
private formatErrorMessage(error: Error, context: any): string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typing context as unknown instead of any

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…notifications

# Conflicts:
#	packages/automated-dispute/tests/mocks/eboActor.mocks.ts
#	packages/automated-dispute/tests/services/eboProcessor.spec.ts
@jahabeebs jahabeebs dismissed stale reviews from 0xnigir1 and 0xyaco via 5d05618 October 11, 2024 16:56
@jahabeebs jahabeebs requested review from 0xnigir1 and 0xyaco October 11, 2024 16:59
@jahabeebs jahabeebs merged commit 997f88c into dev Oct 11, 2024
5 checks passed
@jahabeebs jahabeebs deleted the feat/grt-59-implement-notifications branch October 11, 2024 17:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants