Skip to content

Commit

Permalink
Merge branch 'main' into chore/docs-and-dx-improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
chapati23 authored Jul 26, 2024
2 parents d92632b + 16fd387 commit aba11cd
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 60 deletions.
12 changes: 12 additions & 0 deletions infra/quicknode.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ resource "quicknode_notification" "notification" {
enabled = true
}

# Creates a new QuickAlert event listener for `MedianUpdated` events on our SortedOracles contract,
# which is used as a health check event to ensure quicknode alerts are firing.
resource "quicknode_notification" "healthcheck" {
name = "Healthcheck event"
network = "celo-mainnet"

# Decoded version: `tx_logs_address == '0xefb84935239dacdecf7c5ba76d8de40b077b7b33' && tx_logs_topic0 == '0xa9981ebfc3b766a742486e898f54959b050a66006dbce1a4155c1f84a08bcf41' && tx_logs_topic1 == '0x000000000000000000000000765de816845861e75a25fca122bb6898b8b1282a'`
expression = "dHhfbG9nc19hZGRyZXNzID09ICcweGVmYjg0OTM1MjM5ZGFjZGVjZjdjNWJhNzZkOGRlNDBiMDc3YjdiMzMnICYmIHR4X2xvZ3NfdG9waWMwID09ICcweGE5OTgxZWJmYzNiNzY2YTc0MjQ4NmU4OThmNTQ5NTliMDUwYTY2MDA2ZGJjZTFhNDE1NWMxZjg0YTA4YmNmNDEnICYmIHR4X2xvZ3NfdG9waWMxID09ICcweDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDc2NWRlODE2ODQ1ODYxZTc1YTI1ZmNhMTIyYmI2ODk4YjhiMTI4MmEn"
destination_ids = [resource.quicknode_destination.destination.id]
enabled = true
}

# Creates a new QuickAlert destination that forwards all received `ProposalCreated` transaction receipts to our Cloud Function
resource "quicknode_destination" "destination" {
name = "Cloud Function"
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"test": "npm run test:local",
"test:local": "curl -H \"Content-Type: application/json\" -d @src/proposal-created.fixture.json localhost:8080",
"test:prod": "./test-deployed-function.sh",
"test:healthcheck": "curl -H \"Content-Type: application/json\" -d @src/health-check.fixture.json localhost:8080",
"todo": "git ls-files -c --exclude-standard | grep -v \"package.json\" | xargs grep -n -i --color \"TODO:\\|FIXME:\""
},
"dependencies": {
Expand Down
48 changes: 48 additions & 0 deletions src/health-check.fixture.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[
{
"blockHash": "0x3c3de1a0ae100d33d9a7863ddc59cdf6ec628c42bebf0854e18c462a7e2f6b90",
"blockNumber": "0x1990dc7",
"contractAddress": "",
"cumulativeGasUsed": "0x8e7542",
"effectiveGasPrice": "0x1bf08eb00",
"from": "0xfe9925e6ae9c4cd50ae471b90766aaef37ad307e",
"gasUsed": "0x4a836",
"logs": [
{
"address": "0xefb84935239dacdecf7c5ba76d8de40b077b7b33",
"blockHash": "0x3c3de1a0ae100d33d9a7863ddc59cdf6ec628c42bebf0854e18c462a7e2f6b90",
"blockNumber": "0x1990dc7",
"data": "0x00000000000000000000000000000000000000000000000000000000669f37b9000000000000000000000000000000000000000000007ae21ecba8aa70ef8750",
"logIndex": "0xc5",
"removed": false,
"topics": [
"0x7cebb17173a9ed273d2b7538f64395c0ebf352ff743f1cf8ce66b437a6144213",
"0x000000000000000000000000765de816845861e75a25fca122bb6898b8b1282a",
"0x000000000000000000000000fe9925e6ae9c4cd50ae471b90766aaef37ad307e"
],
"transactionHash": "0x1091e1d6babb1711b30d6328f9d9755cc4e7d5004f9ef88c9526b1642f5f35e1",
"transactionIndex": "0x3"
},
{
"address": "0xefb84935239dacdecf7c5ba76d8de40b077b7b33",
"blockHash": "0x3c3de1a0ae100d33d9a7863ddc59cdf6ec628c42bebf0854e18c462a7e2f6b90",
"blockNumber": "0x1990dc7",
"data": "0x000000000000000000000000000000000000000000007aef59d1fabdfb4cfc30",
"logIndex": "0xc6",
"removed": false,
"topics": [
"0xa9981ebfc3b766a742486e898f54959b050a66006dbce1a4155c1f84a08bcf41",
"0x000000000000000000000000765de816845861e75a25fca122bb6898b8b1282a"
],
"transactionHash": "0x1091e1d6babb1711b30d6328f9d9755cc4e7d5004f9ef88c9526b1642f5f35e1",
"transactionIndex": "0x3"
}
],
"logsBloom": "0x00800000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000004000000000000000020000000000000000000000000400000008400000000100000000000000000000000000000000000000000401000000000000800000000000000000000000000000000000000800000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000",
"status": "0x1",
"to": "0xefb84935239dacdecf7c5ba76d8de40b077b7b33",
"transactionHash": "0x1091e1d6babb1711b30d6328f9d9755cc4e7d5004f9ef88c9526b1642f5f35e1",
"transactionIndex": "0x3",
"type": "0x0"
}
]
14 changes: 12 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,18 @@ export const watchdogNotifier: HttpFunction = async (
const parsedEvents = parseTransactionReceipts(req.body);

for (const parsedEvent of parsedEvents) {
await sendDiscordNotification(parsedEvent.event, parsedEvent.txHash);
await sendTelegramNotification(parsedEvent.event, parsedEvent.txHash);
switch (parsedEvent.event.eventName) {
case "ProposalCreated":
await sendDiscordNotification(parsedEvent.event, parsedEvent.txHash);
await sendTelegramNotification(parsedEvent.event, parsedEvent.txHash);
break;
case "MedianUpdated":
// Acts a health check/heartbeat for the service, as it's a frequently emitted event
console.info("[HealthCheck]: Block", parsedEvent.block);
break;
default:
throw new Error("Unknown event type", parsedEvent.event);
}
}

res.status(200).send("Event successfully processed");
Expand Down
73 changes: 50 additions & 23 deletions src/parse-transaction-receipts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,38 @@ import { decodeEventLog } from "viem";

// Internal
import GovernorABI from "./governor-abi.js";
import { ProposalCreatedEvent } from "./types.js";
import { HealthCheckEvent, ProposalCreatedEvent } from "./types.js";
import hasLogs from "./utils/has-logs.js";
import isHealthCheckEvent from "./utils/is-health-check-event.js";
import isProposalCreatedEvent from "./utils/is-proposal-created-event.js";
import isTransactionReceipt from "./utils/is-transaction-receipt.js";

// For debugging with SortedOracles:
// import SortedOraclesABI from "./sorted-oracles-abi.js";
import SortedOraclesABI from "./sorted-oracles-abi.js";

/**
* Parse request body containing raw transaction receipts
*/
export default function parseTransactionReceipts(
matchedTransactionReceipts: unknown,
): { event: ProposalCreatedEvent; txHash: string }[] {
): {
block?: number;
event: ProposalCreatedEvent | HealthCheckEvent;
txHash: string;
}[] {
const result = [];
if (!Array.isArray(matchedTransactionReceipts)) {
throw new Error(
`Request body is not an array of transaction receipts but was: ${JSON.stringify(matchedTransactionReceipts)}`,
`Request body is not an array of transaction receipts but was: ${JSON.stringify(
matchedTransactionReceipts,
)}`,
);
}

for (const receipt of matchedTransactionReceipts) {
if (!isTransactionReceipt(receipt)) {
throw new Error(
`'receipt' is not of type 'TransactionReceipt': ${JSON.stringify(receipt)}`,
`'receipt' is not of type 'TransactionReceipt': ${JSON.stringify(
receipt,
)}`,
);
}

Expand All @@ -42,24 +49,44 @@ export default function parseTransactionReceipts(
throw new Error("No topics found in log");
}

const event = decodeEventLog({
// For debugging with SortedOracles:
// abi: SortedOraclesABI,
abi: GovernorABI,
data: log.data as `0x${string}`,
topics: log.topics as [
signature: `0x${string}`,
...args: `0x${string}`[],
],
});
try {
const event = decodeEventLog({
abi: GovernorABI,
data: log.data as `0x${string}`,
topics: log.topics as [
signature: `0x${string}`,
...args: `0x${string}`[],
],
});

if (!isProposalCreatedEvent(event)) {
throw new Error(
`Event is not a ProposalCreatedEvent: ${JSON.stringify(event)}`,
);
}
if (isProposalCreatedEvent(event)) {
result.push({
event,
txHash: log.transactionHash,
});
}
// eslint-disable-next-line no-empty
} catch {}

try {
const event = decodeEventLog({
abi: SortedOraclesABI,
data: log.data as `0x${string}`,
topics: log.topics as [
signature: `0x${string}`,
...args: `0x${string}`[],
],
});

result.push({ event, txHash: log.transactionHash });
if (isHealthCheckEvent(event)) {
result.push({
block: Number(receipt.blockNumber),
event,
txHash: log.transactionHash,
});
}
// eslint-disable-next-line no-empty
} catch {}
}
}

Expand Down
35 changes: 0 additions & 35 deletions src/sorted-oracle-report.fixture.json

This file was deleted.

9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,12 @@ export interface ProposalCreatedEvent {
version: number;
};
}

export interface HealthCheckEvent {
eventName: "MedianUpdated";
block: number;
args: {
token: `0x${string}`;
value: bigint;
};
}
21 changes: 21 additions & 0 deletions src/utils/is-health-check-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { DecodeEventLogReturnType } from "viem";
import type SortedOraclesABI from "../sorted-oracles-abi.js";
import type { HealthCheckEvent } from "../types.js";

export default function isHealthCheckEvent(
event: DecodeEventLogReturnType<typeof SortedOraclesABI> | null | undefined,
): event is HealthCheckEvent {
if (
event === null ||
event === undefined ||
typeof event !== "object" ||
!("args" in event)
) {
return false;
}
return (
event.eventName === "MedianUpdated" &&
"token" in event.args &&
"value" in event.args
);
}

0 comments on commit aba11cd

Please sign in to comment.