Skip to content

Commit

Permalink
seems to be working
Browse files Browse the repository at this point in the history
  • Loading branch information
denisu committed Apr 28, 2024
0 parents commit 1fe8c27
Show file tree
Hide file tree
Showing 14 changed files with 697 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
.DS_Store
config.json
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# warp.green Validator Monitoring

A small tool to monitor validators of the warp.green bridge. It checks if a validators relay is reachable and if the validators public key participates in signing messages. It starts a local webserver for querying the current status of a specific validator, which can be added to any monitoring service (BetterUptime, etc.).

## Getting Started

1. Clone the repository
2. Run `npm install`
3. Run `npm run build`
4. Rename `config.json.example` to `config.json` and add one more validators to monitor
5. Run `npm start`

### Query Validator Status

Query the status of a validator by sending a GET request to `/check/{validators_pubkey}`. The response will be either `204` on success, or `500` on failure with a JSON containing the error.
10 changes: 10 additions & 0 deletions config.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"port": 3030,
"host": "127.0.0.1",
"validators": [
{
"pubkey": "af123...",
"relay": "wss://"
}
]
}
78 changes: 78 additions & 0 deletions dist/connections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isRelayConnected = exports.addConnection = void 0;
const ws_1 = __importDefault(require("ws"));
const events_1 = require("./events");
const noble_secp256k1_1 = require("noble-secp256k1");
const connectedRelays = {};
function addConnection(validator) {
let pingInterval;
let pongTimeout;
const ws = new ws_1.default(validator.relay, {
headers: {
"User-Agent": process.env.AGENT || "validator monitoring",
},
});
const log = (message) => {
console.log(validator.relay, "-", message);
};
const reconnect = () => {
log("disconnected, reconnecting in 5 sec...");
connectedRelays[validator.relay] = false;
clearInterval(pingInterval);
clearTimeout(pongTimeout);
setTimeout(() => addConnection(validator), 5000);
};
const connectTimeout = setTimeout(() => ws.terminate(), 5000);
ws.on("open", () => {
clearTimeout(connectTimeout);
log("connected");
connectedRelays[validator.relay] = true;
// NOTE: pubkey filter is not working in current nostr-rs-relay
// authors: [validator.pubkey]
// https://github.com/scsibug/nostr-rs-relay/issues/189
ws.send(JSON.stringify(["REQ", "events", { kinds: [1], limit: 500 }]));
// ping every 10s, if no pong is received within the timeout, terminate
pingInterval = setInterval(() => {
ws.ping();
pongTimeout = setTimeout(() => ws.terminate(), 5000);
}, 10000);
});
ws.on("message", function message(data) {
var _a, _b, _c, _d;
try {
const event = JSON.parse(data.toString());
// ignore non-event messages or events not from this validator
if (event[0] !== "EVENT" || event[2].pubkey !== validator.pubkey)
return;
if (!noble_secp256k1_1.schnorr.verify(event[2].sig, event[2].id, validator.pubkey)) {
log("invalid signature");
return;
}
// event has r & c tags -> it's a bridging tx
if (((_b = (_a = event[2]) === null || _a === void 0 ? void 0 : _a.tags[0]) === null || _b === void 0 ? void 0 : _b[0]) === "r" && ((_d = (_c = event[2]) === null || _c === void 0 ? void 0 : _c.tags[1]) === null || _d === void 0 ? void 0 : _d[0]) === "c") {
const rc = event[2].tags[0][1] + event[2].tags[1][1];
log("event: " + rc);
(0, events_1.addEvent)({
rc,
pubkey: validator.pubkey,
created_at: event[2].created_at,
});
}
}
catch (err) {
// log("could not parse message: " + data.toString());
}
});
ws.on("close", reconnect);
ws.on("error", () => ws.terminate());
ws.on("pong", () => clearTimeout(pongTimeout));
}
exports.addConnection = addConnection;
function isRelayConnected(relay) {
return connectedRelays[relay];
}
exports.isRelayConnected = isRelayConnected;
48 changes: 48 additions & 0 deletions dist/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isPubkeyParticipating = exports.addEvent = void 0;
const events = {};
function addEvent(event) {
return __awaiter(this, void 0, void 0, function* () {
// first time seeing this event?
if (!events[event.rc]) {
events[event.rc] = {
created_at: event.created_at * 1000,
pubkeys: [],
};
}
// add pubkey to event, this tells us that the pubkey has signed this event
events[event.rc].pubkeys.push(event.pubkey);
// determine newest event
const newest = Object.values(events).reduce((acc, curr) => {
return acc.created_at > curr.created_at ? acc : curr;
});
// remove previous events older than 5 minutes but keep the newest event
for (const rc in events) {
if (events[rc] === newest)
continue;
if (events[rc].created_at < Date.now() - 5 * 60 * 1000) {
delete events[rc];
}
}
});
}
exports.addEvent = addEvent;
// checks if pubkey has signed all recent events
// this could be either the last known event or all events from the last 5 minutes
function isPubkeyParticipating(pubkey) {
const pubkeys = Object.values(events)
.filter((event) => event.created_at < Date.now() - 10000) // exclude brand new events (might not have propagated yet)
.map((event) => event.pubkeys);
return pubkeys.every((pubs) => pubs.length > 0 && pubs.includes(pubkey));
}
exports.isPubkeyParticipating = isPubkeyParticipating;
46 changes: 46 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const hono_1 = require("hono");
const node_server_1 = require("@hono/node-server");
const connections_1 = require("./connections");
const events_1 = require("./events");
const app = new hono_1.Hono();
const config = require(process.env.CONFIG || "../config.json");
// connect to each validator and track events
config.validators.forEach(connections_1.addConnection);
// check route (for use in monitoring application)
app.get("/check/:pubkey", (c) => __awaiter(void 0, void 0, void 0, function* () {
const pubkey = c.req.param("pubkey");
// get validator from pubkey
const validator = config.validators.find((validator) => validator.pubkey === pubkey);
if (!validator) {
return c.json({ error: "Unknown Pubkey" }, 400);
}
if (!(0, connections_1.isRelayConnected)(validator.relay)) {
console.log("check for", validator.relay, "FAILED (not connected)");
return c.json({ error: "Relay is not connected" }, 500);
}
if (!(0, events_1.isPubkeyParticipating)(pubkey)) {
console.log("check for", validator.relay, "FAILED (not participating)");
return c.json({ error: "Pubkey does not participate in signing" }, 500);
}
console.log("check for", validator.relay, "OK");
// all good!
return c.json(null, 204);
}));
(0, node_server_1.serve)({
fetch: app.fetch,
port: config.port,
hostname: config.hostname,
}, (info) => {
console.log(`Listening on http://${info.address}:${info.port}`);
});
2 changes: 2 additions & 0 deletions dist/types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
Loading

0 comments on commit 1fe8c27

Please sign in to comment.