Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
Metroxe committed Apr 11, 2024
2 parents ea2a4c5 + 1d005cb commit e42dbcd
Show file tree
Hide file tree
Showing 87 changed files with 15,496 additions and 2,799 deletions.
117 changes: 109 additions & 8 deletions apps/cli/src/commands/boot-challenger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Vorpal from "vorpal";
import axios from "axios";
import { ethers } from 'ethers';
import { config, createBlsKeyPair, getAssertion, getSignerFromPrivateKey, listenForAssertions, submitAssertionToReferee, EventListenerError } from "@sentry/core";
import { config, createBlsKeyPair, getAssertion, getSignerFromPrivateKey, listenForAssertions, submitAssertionToReferee, EventListenerError, findMissedAssertion, isAssertionSubmitted } from "@sentry/core";

type PromptBodyKey = "secretKeyPrompt" | "walletKeyPrompt" | "webhookUrlPrompt";
type PromptBodyKey = "secretKeyPrompt" | "walletKeyPrompt" | "webhookUrlPrompt" | "instancePrompt";

const INIT_PROMPTS: { [key in PromptBodyKey]: Vorpal.PromptObject } = {
secretKeyPrompt: {
Expand All @@ -22,12 +22,21 @@ const INIT_PROMPTS: { [key in PromptBodyKey]: Vorpal.PromptObject } = {
type: 'input',
name: 'webhookUrl',
message: 'Enter the webhook URL if you want to post errors (optional):',
},
instancePrompt: {
type: 'input',
name: 'instance',
default: "1",
message: 'Enter the number of challenger instance this is (default 1 for main instance):',
}
}

const NUM_ASSERTION_LISTENER_RETRIES: number = 3; //The number of restart attempts if the listener errors
const NUM_ASSERTION_LISTENER_RETRIES: number = 3 as const; //The number of restart attempts if the listener errors
const NUM_CON_WS_ALLOWED_ERRORS: number = 10; //The number of consecutive WS error we allow before restarting the listener

//@dev This has to match NUM_ASSERTION_LISTENER_RETRIES
const ASSERTION_LISTENER_RETRY_DELAYS: [number, number, number] = [30_000, 180_000, 600_000]; //Delays for auto restart the challenger, on the first error it will wait 30 seconds, then 3 minutes then 10 minutes before trying to restart.

// Prompt input cache
let cachedSigner: {
address: string,
Expand All @@ -39,6 +48,9 @@ let lastAssertionTime: number;

let currentNumberOfRetries = 0;

let CHALLENGER_INSTANCE = 1;
const BACKUP_SUBMISSION_DELAY = 300_000; // For every instance we wait 5 minutes + instance number;

const initCli = async (commandInstance: Vorpal.CommandInstance) => {

const { secretKey } = await commandInstance.prompt(INIT_PROMPTS["secretKeyPrompt"]);
Expand Down Expand Up @@ -67,13 +79,39 @@ const initCli = async (commandInstance: Vorpal.CommandInstance) => {

const { webhookUrl }: any = await commandInstance.prompt(INIT_PROMPTS["webhookUrlPrompt"]);
cachedWebhookUrl = webhookUrl;
sendNotification('Challenger has started.', commandInstance);

const { instance }: any = await commandInstance.prompt(INIT_PROMPTS["instancePrompt"]);
CHALLENGER_INSTANCE = Number(instance);

sendNotification(`Challenger instance ${CHALLENGER_INSTANCE} has started.`, commandInstance);
lastAssertionTime = Date.now();

}

const onAssertionConfirmedCb = async (nodeNum: any, commandInstance: Vorpal.CommandInstance) => {
commandInstance.log(`[${new Date().toISOString()}] Assertion confirmed ${nodeNum}. Looking up the assertion information...`);

if (CHALLENGER_INSTANCE != 1) {
commandInstance.log(`[${new Date().toISOString()}] Backup challenger waiting for delay ${(CHALLENGER_INSTANCE) + (BACKUP_SUBMISSION_DELAY / (60 * 1000))} minutes..`);
const currentTime = Date.now();
await new Promise((resolve) => {
setTimeout(resolve, (CHALLENGER_INSTANCE * 60 * 1000) + BACKUP_SUBMISSION_DELAY)
});

try {
const hasSubmitted = await isAssertionSubmitted(nodeNum);
if (hasSubmitted) {
commandInstance.log(`[${new Date().toISOString()}] Assertion already submitted by other instance.`);
lastAssertionTime = currentTime; //So our health check does not spam errors
return;
}
commandInstance.log(`[${new Date().toISOString()}] Backup challenger found assertion not submitted and has to step in.`);
} catch (error) {
commandInstance.log(`[${new Date().toISOString()}] ERROR: Backup challenger isAssertionSubmitted: ${error}`);
sendNotification(`Error Backup challenger instance ${CHALLENGER_INSTANCE} isAssertionSubmitted failed: ${error}`, commandInstance);
}
}

const assertionNode = await getAssertion(nodeNum);
commandInstance.log(`[${new Date().toISOString()}] Assertion data retrieved. Starting the submission process...`);
try {
Expand All @@ -95,7 +133,26 @@ const onAssertionConfirmedCb = async (nodeNum: any, commandInstance: Vorpal.Comm
const checkTimeSinceLastAssertion = async (lastAssertionTime: number, commandInstance: Vorpal.CommandInstance) => {
const currentTime = Date.now();
commandInstance.log(`[${new Date().toISOString()}] The currentTime is ${currentTime}`);
if (currentTime - lastAssertionTime > 70 * 60 * 1000) {

let criticalAmount = (70 * 60 * 1000);
if (CHALLENGER_INSTANCE != 1) {
criticalAmount += (CHALLENGER_INSTANCE * 60 * 1000);
}

if (currentTime - lastAssertionTime > criticalAmount) {

try {
const missedAssertion = await findMissedAssertion();
if (missedAssertion == null) {
const passedSinceLastChallenge = currentTime - lastAssertionTime;
lastAssertionTime = Date.now() - (passedSinceLastChallenge - 60 * 1000); //expect that the challenge got submitted at the correct time and resume with the correct health check warning
return;
}
} catch (error) {
commandInstance.log(`[${new Date().toISOString()}] Failed to findMissedAssertion (${error}).`);
sendNotification(`Error: Backup Challenger instance ${CHALLENGER_INSTANCE} failed to findMissedAssertion`, commandInstance);
}

const timeSinceLastAssertion = Math.round((currentTime - lastAssertionTime) / 60000);
commandInstance.log(`[${new Date().toISOString()}] It has been ${timeSinceLastAssertion} minutes since the last assertion. Please check the Rollup Protocol (${config.rollupAddress}).`);
sendNotification(`It has been ${timeSinceLastAssertion} minutes since the last assertion. Please check the Rollup Protocol (${config.rollupAddress}).`, commandInstance);
Expand All @@ -105,7 +162,7 @@ const checkTimeSinceLastAssertion = async (lastAssertionTime: number, commandIns
const sendNotification = async (message: string, commandInstance: Vorpal.CommandInstance) => {
if (cachedWebhookUrl) {
try {
await axios.post(cachedWebhookUrl, { text: message });
await axios.post(cachedWebhookUrl, { text: `@channel [Instance ${CHALLENGER_INSTANCE}]: ${message}` });
} catch (error) {
commandInstance.log(`[${new Date().toISOString()}] Failed to send notification request ${error && (error as Error).message ? (error as Error).message : error}`);
}
Expand Down Expand Up @@ -138,9 +195,14 @@ const startListener = async (commandInstance: Vorpal.CommandInstance) => {
return;
}

if (errorCount != 0) {
//If the websocket just reconnected automatically we only want to try to re-post the last possibly missed challenge
await processMissedAssertions(commandInstance).catch(() => { });
}

errorCount = 0;

try {
errorCount = 0;
await onAssertionConfirmedCb(nodeNum, commandInstance);
currentNumberOfRetries = 0;
} catch {
Expand All @@ -153,6 +215,31 @@ const startListener = async (commandInstance: Vorpal.CommandInstance) => {
})
}

async function processMissedAssertions(commandInstance: Vorpal.CommandInstance) {
commandInstance.log(`[${new Date().toISOString()}] Looking for missed assertions...`);

const missedAssertionNodeNum = await findMissedAssertion();

if (missedAssertionNodeNum) {
commandInstance.log(`[${new Date().toISOString()}] Found missed assertion with nodeNum: ${missedAssertionNodeNum}. Looking up the assertion information...`);
const assertionNode = await getAssertion(missedAssertionNodeNum);
commandInstance.log(`[${new Date().toISOString()}] Missed assertion data retrieved. Starting the submission process...`);
try {
await submitAssertionToReferee(
cachedSecretKey,
missedAssertionNodeNum,
assertionNode,
cachedSigner!.signer,
);
commandInstance.log(`[${new Date().toISOString()}] Submitted assertion: ${missedAssertionNodeNum}`);
} catch (error) {
sendNotification(`Submit missed assertion Error: ${(error as Error).message}`, commandInstance);
throw error;
}
} else {
commandInstance.log(`[${new Date().toISOString()}] Did not find any missing assertions`);
}
}

/**
* Starts a runtime of the challenger.
Expand Down Expand Up @@ -183,13 +270,27 @@ export function bootChallenger(cli: Vorpal) {
}, 5 * 60 * 1000);

for (; currentNumberOfRetries <= NUM_ASSERTION_LISTENER_RETRIES; currentNumberOfRetries++) {
try {
await processMissedAssertions(commandInstance);
} catch (error) {
//TODO what should we do if this fails, restarting the cmd won't help, it will most probably fail again
commandInstance.log(`[${new Date().toISOString()}] Failed to handle missed assertions - ${(error as Error).message}`);
await sendNotification(`Failed to handle missed assertions - ${(error as Error).message}`, commandInstance);
}

commandInstance.log(`[${new Date().toISOString()}] The challenger is now listening for assertions...`);
await startListener(commandInstance);

if (currentNumberOfRetries + 1 <= NUM_ASSERTION_LISTENER_RETRIES) {
const delayPerRetry = ASSERTION_LISTENER_RETRY_DELAYS[currentNumberOfRetries];
commandInstance.log(`[${new Date().toISOString()}] Challenger restarting after ${delayPerRetry / 1000} seconds`);
sendNotification(`Challenger restarting after ${delayPerRetry / 1000} seconds`, commandInstance);

//Wait for delay per retry
await (new Promise((resolve) => {
setTimeout(resolve, 3000);
setTimeout(resolve, delayPerRetry);
}))

commandInstance.log(`[${new Date().toISOString()}] Challenger restarting with ${NUM_ASSERTION_LISTENER_RETRIES - (currentNumberOfRetries + 1)} attempts left.`);
sendNotification(`Challenger restarting with ${NUM_ASSERTION_LISTENER_RETRIES - (currentNumberOfRetries + 1)} attempts left.`, commandInstance);
}
Expand Down
13 changes: 13 additions & 0 deletions apps/cli/src/commands/display-node-agreement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Vorpal from "vorpal";

/**
* Function to set the rollup address in the Referee contract.
* @param {Vorpal} cli - The Vorpal instance to attach the command to.
*/
export function displayNodeAgreement(cli: Vorpal) {
cli
.command('display-node-agreement', 'Display the Sentry Node Agreement')
.action(async function (this: Vorpal.CommandInstance) {
this.log(`View the Sentry Node Agreement here https://xai.games/sentrynodeagreement/`);
});
}
30 changes: 30 additions & 0 deletions apps/cli/src/commands/start-centralization-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Vorpal from "vorpal";
import { dataCentralizationRuntime } from "@sentry/core";

export function startCentralizationRuntime(cli: Vorpal) {
cli
.command('start-centralization-runtime', 'Start the data centralization runtime')
.action(async function (this: Vorpal.CommandInstance) {
const mongoUriPrompt: Vorpal.PromptObject = {
type: 'password',
name: 'mongoUri',
message: 'Enter the mongodb connection URI:',
mask: '*',
optional: false
};
const { mongoUri } = await this.prompt(mongoUriPrompt);

const commandInstance = this;

const stopRuntime = await dataCentralizationRuntime({ mongoUri, logFunction: (log: string) => commandInstance.log(log) });

// Listen for process termination and call the handler
process.on('SIGINT', async () => {
commandInstance.log(`[${new Date().toISOString()}] The CentralizationRuntime has been terminated manually.`);
stopRuntime();
process.exit();
});

return new Promise((resolve, reject) => { }); // Keep the command alive
});
}
8 changes: 8 additions & 0 deletions apps/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ import { removePromoCode } from './commands/licenses/remove-promo-code.js';
import { eventListener } from './commands/event-listener.js';
import { startKycProcess } from './commands/kyc/start-kyc-process.js';
import { generateRevenueReport } from './commands/licenses/generate-revenue-report.js';
import { displayNodeAgreement } from './commands/display-node-agreement.js';
import { startCentralizationRuntime } from './commands/start-centralization-runtime.js';


import {version} from "@sentry/core";

const cli = new Vorpal();
Expand Down Expand Up @@ -94,8 +98,12 @@ toggleAssertionChecking(cli);
totalSupply(cli);
startKycProcess(cli);
generateRevenueReport(cli);
displayNodeAgreement(cli);
startCentralizationRuntime(cli);

console.log(`Starting Sentry cli version ${version}`);
console.log(`Stake and redeem esXAI at https://app.xai.games`);
console.log("");

cli
.delimiter('sentry-node$')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export function Sidebar() {
</div>
Sentry Wallet
</Link>
<a
onClick={() => window.electron.openExternal('https://app.xai.games')}
className="flex items-center mb-1 text-[15px] text-gray-600 hover:text-gray-400 cursor-pointer gap-2"
>
<XaiLogo className="w-[16px]"/> Staking
</a>

{/*<Link*/}
{/* to="/redeem"*/}
Expand Down
2 changes: 1 addition & 1 deletion apps/web-staking/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ COPY --from=build /app/.next ./.next
COPY --from=build /app/public ./public
COPY --from=base /app/node_modules ./node_modules
COPY package.json .

EXPOSE 3000
CMD ["./node_modules/next/dist/bin/next", "start"]
18 changes: 0 additions & 18 deletions apps/web-staking/config.ts

This file was deleted.

Loading

0 comments on commit e42dbcd

Please sign in to comment.