Skip to content

Commit adb319f

Browse files
Merge pull request #155 from xai-foundation/develop
Develop
2 parents e262283 + 43e7696 commit adb319f

File tree

107 files changed

+36588
-5055
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+36588
-5055
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
run: pnpm install
3535

3636
- name: Build monorepo
37-
run: pnpm run build
37+
run: npx nx run-many --target=build --all
3838
env:
3939
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4040

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ build
1919
infrastructure/CodeSignTool-*
2020

2121
infrastructure/CodeSignTool-*
22+
23+
24+
.nx/cache

apps/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"main": "dist/index.js",
1010
"bin": "build/sentry-node-cli.js",
1111
"scripts": {
12-
"build": "npm run compile && npm run transpile && pkg ./build/sentry-node-cli.js --targets=node18-linux-x64,node18-macos-x64,node18-win-x64 --out-path=release",
12+
"build": "pnpm run compile && pnpm run transpile && pkg ./build/sentry-node-cli.js --targets=node18-linux-x64,node18-macos-x64,node18-win-x64 --out-path=release",
1313
"clean": "rimraf dist && rimraf release && rimraf build && rimraf tsconfig.tsbuildinfo",
1414
"compile": "tsc",
1515
"lint": "eslint . --ext .ts,.tsx",

apps/cli/project.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "@sentry/cli",
3+
"targets": {
4+
"build": {
5+
"dependsOn": [
6+
{
7+
"target": "build",
8+
"projects": "dependencies"
9+
}
10+
],
11+
"outputs": [
12+
"{projectRoot}/dist",
13+
"{projectRoot}/release",
14+
"{projectRoot}/build",
15+
"{projectRoot}/tsconfig.tsbuildinfo"
16+
]
17+
}
18+
}
19+
}

apps/cli/src/commands/boot-challenger.ts

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ let currentNumberOfRetries = 0;
5151
let CHALLENGER_INSTANCE = 1;
5252
const BACKUP_SUBMISSION_DELAY = 300_000; // For every instance we wait 5 minutes + instance number;
5353

54+
let isProcessingMissedAssertions = false;
55+
5456
const initCli = async (commandInstance: Vorpal.CommandInstance) => {
5557

5658
const { secretKey } = await commandInstance.prompt(INIT_PROMPTS["secretKeyPrompt"]);
@@ -124,6 +126,11 @@ const onAssertionConfirmedCb = async (nodeNum: any, commandInstance: Vorpal.Comm
124126
commandInstance.log(`[${new Date().toISOString()}] Submitted assertion: ${nodeNum}`);
125127
lastAssertionTime = Date.now();
126128
} catch (error) {
129+
if (error && (error as Error).message && (error as Error).message.includes('execution reverted: "9"')) {
130+
commandInstance.log(`[${new Date().toISOString()}] Could not submit challenge because it was already submitted`);
131+
lastAssertionTime = Date.now();
132+
return;
133+
}
127134
commandInstance.log(`[${new Date().toISOString()}] Submit Assertion Error: ${(error as Error).message}`);
128135
sendNotification(`Submit Assertion Error: ${(error as Error).message}`, commandInstance);
129136
throw error;
@@ -162,7 +169,7 @@ const checkTimeSinceLastAssertion = async (lastAssertionTime: number, commandIns
162169
const sendNotification = async (message: string, commandInstance: Vorpal.CommandInstance) => {
163170
if (cachedWebhookUrl) {
164171
try {
165-
await axios.post(cachedWebhookUrl, { text: `@channel [Instance ${CHALLENGER_INSTANCE}]: ${message}` });
172+
await axios.post(cachedWebhookUrl, { text: `<!channel> [Instance ${CHALLENGER_INSTANCE}]: ${message}` });
166173
} catch (error) {
167174
commandInstance.log(`[${new Date().toISOString()}] Failed to send notification request ${error && (error as Error).message ? (error as Error).message : error}`);
168175
}
@@ -186,18 +193,29 @@ const startListener = async (commandInstance: Vorpal.CommandInstance) => {
186193
const listener = listenForAssertions(
187194
async (nodeNum: any, blockHash: any, sendRoot: any, event: any, error?: EventListenerError) => {
188195
if (error) {
196+
197+
if (isStopping) {
198+
// If we stopped manually we don't want to process the error from the closing event.
199+
return;
200+
}
201+
189202
errorCount++;
190203
// We should allow a defined number of consecutive WS errors before restarting the websocket at all
191204
if (errorCount > NUM_CON_WS_ALLOWED_ERRORS) {
192205
stopListener(listener);
193206
resolve(error);
207+
return;
194208
}
195-
return;
196-
}
197209

198-
if (errorCount != 0) {
199-
//If the websocket just reconnected automatically we only want to try to re-post the last possibly missed challenge
210+
// If the error is an automatic reconnect after close it takes 1 second for the listener to restart,
211+
// it can happen, that we miss an assertion in that second,
212+
// for that we wait 1 second before checking if we missed an assertion in between, after that the websocket should be back running
213+
await new Promise((resolve) => {
214+
setTimeout(resolve, 1000);
215+
});
200216
await processMissedAssertions(commandInstance).catch(() => { });
217+
218+
return;
201219
}
202220

203221
errorCount = 0;
@@ -216,29 +234,50 @@ const startListener = async (commandInstance: Vorpal.CommandInstance) => {
216234
}
217235

218236
async function processMissedAssertions(commandInstance: Vorpal.CommandInstance) {
237+
if (isProcessingMissedAssertions) {
238+
return Promise.resolve();
239+
}
240+
219241
commandInstance.log(`[${new Date().toISOString()}] Looking for missed assertions...`);
242+
isProcessingMissedAssertions = true;
220243

221-
const missedAssertionNodeNum = await findMissedAssertion();
244+
let missedAssertionNodeNum;
245+
try {
246+
missedAssertionNodeNum = await findMissedAssertion();
247+
} catch (error: any) {
248+
commandInstance.log(`[${new Date().toISOString()}] Error looking for missed assertion: ${error}`);
249+
sendNotification(`Error looking for missed assertion ${error && error.message ? error.message : error}`, commandInstance);
250+
}
222251

223252
if (missedAssertionNodeNum) {
224-
commandInstance.log(`[${new Date().toISOString()}] Found missed assertion with nodeNum: ${missedAssertionNodeNum}. Looking up the assertion information...`);
225-
const assertionNode = await getAssertion(missedAssertionNodeNum);
226-
commandInstance.log(`[${new Date().toISOString()}] Missed assertion data retrieved. Starting the submission process...`);
227253
try {
254+
commandInstance.log(`[${new Date().toISOString()}] Found missed assertion with nodeNum: ${missedAssertionNodeNum}. Looking up the assertion information...`);
255+
const assertionNode = await getAssertion(missedAssertionNodeNum);
256+
commandInstance.log(`[${new Date().toISOString()}] Missed assertion data retrieved. Starting the submission process...`);
257+
228258
await submitAssertionToReferee(
229259
cachedSecretKey,
230260
missedAssertionNodeNum,
231261
assertionNode,
232262
cachedSigner!.signer,
233263
);
234264
commandInstance.log(`[${new Date().toISOString()}] Submitted assertion: ${missedAssertionNodeNum}`);
265+
235266
} catch (error) {
267+
isProcessingMissedAssertions = false;
268+
269+
if (error && (error as Error).message && (error as Error).message.includes('execution reverted: "9"')) {
270+
commandInstance.log(`[${new Date().toISOString()}] Could not submit challenge because it was already submitted`);
271+
return;
272+
}
236273
sendNotification(`Submit missed assertion Error: ${(error as Error).message}`, commandInstance);
237274
throw error;
238275
}
239276
} else {
240277
commandInstance.log(`[${new Date().toISOString()}] Did not find any missing assertions`);
241278
}
279+
280+
isProcessingMissedAssertions = false;
242281
}
243282

244283
/**
@@ -252,6 +291,7 @@ export function bootChallenger(cli: Vorpal) {
252291
.action(async function (this: Vorpal.CommandInstance) {
253292

254293
const commandInstance = this;
294+
currentNumberOfRetries = 0;
255295

256296
if (!cachedSigner || !cachedSecretKey) {
257297
await initCli(commandInstance);
@@ -271,6 +311,7 @@ export function bootChallenger(cli: Vorpal) {
271311

272312
for (; currentNumberOfRetries <= NUM_ASSERTION_LISTENER_RETRIES; currentNumberOfRetries++) {
273313
try {
314+
isProcessingMissedAssertions = false;
274315
await processMissedAssertions(commandInstance);
275316
} catch (error) {
276317
//TODO what should we do if this fails, restarting the cmd won't help, it will most probably fail again

apps/cli/src/commands/operator-control/operator-runtime.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Vorpal from "vorpal";
22
import Logger from "../../utils/Logger.js"
3-
import { getSignerFromPrivateKey, operatorRuntime, listOwnersForOperator, Challenge, PublicNodeBucketInformation } from "@sentry/core";
3+
import { getSignerFromPrivateKey, operatorRuntime, Challenge, PublicNodeBucketInformation, getSentryWalletsForOperator } from "@sentry/core";
44

55
/**
66
* Starts a runtime of the operator.
@@ -39,21 +39,37 @@ export function bootOperator(cli: Vorpal) {
3939
// If useWhitelist is false, selectedOwners will be undefined
4040
let selectedOwners;
4141
if (useWhitelist) {
42-
42+
4343
const operatorAddress = await signer.getAddress();
44-
const owners = await listOwnersForOperator(operatorAddress);
44+
const { wallets, pools } = await getSentryWalletsForOperator(null, operatorAddress);
45+
46+
const choices: Array<{ name: string, value: string }> = [];
47+
48+
wallets.forEach(w => {
49+
choices.push({
50+
name: `Owner: ${w.address}${operatorAddress.toLowerCase() == w.address.toLowerCase() ? " (your wallet)" : ""}`,
51+
value: w.address
52+
})
53+
})
54+
55+
pools.forEach(p => {
56+
choices.push({
57+
name: `Pool: ${p.metadata[0]} (${p.address})`,
58+
value: p.address
59+
})
60+
})
4561

4662
const ownerPrompt: Vorpal.PromptObject = {
4763
type: 'checkbox',
4864
name: 'selectedOwners',
49-
message: 'Select the owners for the operator to run for:',
50-
choices: [operatorAddress, ...owners]
65+
message: 'Select the owners/pools for the operator to run for:',
66+
choices,
5167
};
5268

5369
const result = await this.prompt(ownerPrompt);
5470
selectedOwners = result.selectedOwners;
5571

56-
console.log("selectedOwners", selectedOwners);
72+
Logger.log("selectedOwners", selectedOwners);
5773

5874
if (!selectedOwners || selectedOwners.length < 1) {
5975
throw new Error("No owners selected. Please select at least one owner.")
@@ -83,6 +99,16 @@ export function bootOperator(cli: Vorpal) {
8399
}
84100
);
85101

102+
103+
// Listen for process termination and call the handler
104+
process.on('SIGINT', async () => {
105+
if (stopFunction) {
106+
stopFunction();
107+
}
108+
Logger.log(`The operator has been terminated manually.`);
109+
process.exit();
110+
});
111+
86112
return new Promise((resolve, reject) => { }); // Keep the command alive
87113
})
88114
.cancel(() => {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "@sentry/sentry-client-desktop",
3+
"targets": {
4+
"build": {
5+
"outputs": [
6+
"{projectRoot}/dist",
7+
"{projectRoot}/release",
8+
"{projectRoot}/dist-electron",
9+
"{projectRoot}/tsconfig.tsbuildinfo"
10+
]
11+
}
12+
}
13+
}

apps/sentry-client-desktop/src/features/drawer/WhitelistDrawer.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {AiOutlineInfoCircle} from "react-icons/ai";
1010

1111
export function WhitelistDrawer() {
1212
const setDrawerState = useSetAtom(drawerStateAtom);
13-
const {owners} = useAtomValue(chainStateAtom);
13+
const {owners, pools} = useAtomValue(chainStateAtom);
1414
const {data, setData} = useStorage();
1515
const [selected, setSelected] = useState<string[]>([]);
1616
const {sentryRunning, stopRuntime} = useOperatorRuntime();
@@ -52,10 +52,10 @@ export function WhitelistDrawer() {
5252

5353
const getDropdownItems = () => (
5454
<div>
55-
{owners.map((wallet, i) => (
55+
{owners.map((wallet) => (
5656
<div
5757
className="p-2 cursor-pointer hover:bg-gray-100"
58-
key={`whitelist-item-${i}`}
58+
key={`whitelist-item-${wallet}`}
5959
>
6060
<XaiCheckbox
6161
onClick={() => toggleSelected(wallet)}
@@ -65,6 +65,19 @@ export function WhitelistDrawer() {
6565
</XaiCheckbox>
6666
</div>
6767
))}
68+
{pools.map((pool) => (
69+
<div
70+
className="p-2 cursor-pointer hover:bg-gray-100"
71+
key={`whitelist-item-${pool}`}
72+
>
73+
<XaiCheckbox
74+
onClick={() => toggleSelected(pool)}
75+
condition={selected.includes(pool)}
76+
>
77+
{pool}
78+
</XaiCheckbox>
79+
</div>
80+
))}
6881
</div>
6982
);
7083

@@ -107,7 +120,7 @@ export function WhitelistDrawer() {
107120
</Tooltip>
108121
</div>
109122
{getOperatorItem()}
110-
<p className="text-[12px]">Assigned Wallets</p>
123+
<p className="text-[12px]">Assigned Wallets/Pools</p>
111124
{getDropdownItems()}
112125
</div>
113126
</div>

apps/sentry-client-desktop/src/features/home/SentryWallet.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,15 +374,15 @@ export function SentryWallet() {
374374
<div>
375375
<div className="w-full h-auto flex flex-col py-3 pl-10">
376376
<p className="text-sm uppercase text-[#A3A3A3] mb-2">
377-
View Wallet
377+
View Wallet/Pools
378378
</p>
379379
<div className="flex flex-row gap-2">
380380
<div>
381381
<div
382382
onClick={() => setIsOpen(!isOpen)}
383383
className={`flex items-center justify-between w-[538px] border-[#A3A3A3] border-r border-l border-t ${!isOpen ? "border-b" : "pb-[9px]"} border-[#A3A3A3] p-2`}
384384
>
385-
<p>{selectedWallet || `All assigned wallets (${data?.whitelistedWallets ? data.whitelistedWallets.length : owners.length})`}</p>
385+
<p>{selectedWallet || `All assigned wallets/pools (${data?.whitelistedWallets ? data.whitelistedWallets.length : owners.length})`}</p>
386386
<IoIosArrowDown
387387
className={`h-[15px] transform ${isOpen ? "rotate-180 transition-transform ease-in-out duration-300" : "transition-transform ease-in-out duration-300"}`}
388388
/>
@@ -434,7 +434,7 @@ export function SentryWallet() {
434434
className={`flex flex-row justify-center items-center gap-2 text-[15px] border border-[#E5E5E5] px-4 py-2`}
435435
>
436436
<LuListChecks className="h-[15px]"/>
437-
Allowed wallets
437+
Allowed wallets/pools
438438
</button>
439439

440440
<button

apps/sentry-client-desktop/src/features/home/dashboard/SentryNodeStatusCard.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,9 @@ export function SentryNodeStatusCard() {
4343

4444

4545
function formatTimeAgo(createdTimestamp: number): string {
46-
const minutesDifference = Math.floor((new Date().getTime() - new Date(createdTimestamp).getTime()) / (1000 * 60));
47-
const unit = minutesDifference < 60 ? 'm' : 'h';
48-
const value = minutesDifference < 60 ? minutesDifference : Math.floor(minutesDifference / 60);
49-
50-
return `Last challenge ${value}${unit} ago`;
46+
const formatDate = new Intl.DateTimeFormat("en-US", { year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric" }).format(createdTimestamp);
47+
48+
return `Last challenge ${formatDate}`;
5149
}
5250

5351

0 commit comments

Comments
 (0)