-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1fe8c27
Showing
14 changed files
with
697 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules/ | ||
.DS_Store | ||
config.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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://" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); |
Oops, something went wrong.