Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multiple preconfirmations for the same slot in demo #110

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 50 additions & 10 deletions bolt-web-demo/frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import io from "socket.io-client";
import Image from "next/image";
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo, useCallback } from "react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import { createPreconfPayload } from "@/lib/wallet";
import { EventType } from "@/lib/types";
import { Progress } from "@/components/ui/progress";
import { ethers } from "ethers";
import { PRIVATE_KEY, SERVER_URL } from "@/lib/constants";

type Event = {
message: string;
Expand All @@ -16,8 +18,6 @@ type Event = {
link?: string;
};

export const SERVER_URL = "http://localhost:3001";

export default function Home() {
const [events, setEvents] = useState<Array<Event>>([]);

Expand All @@ -37,6 +37,16 @@ export default function Home() {
const [beaconClientUrl, setBeaconClientUrl] = useState("");
const [providerUrl, setProviderUrl] = useState("");
const [explorerUrl, setExplorerUrl] = useState("");
const [preconfirmationRequests, setPreconfirmationRequests] = useState<
Array<{ slot: number; count: number }>
>([]);
const [nonce, setNonce] = useState(0);

const wallet = useMemo(() => {
const provider = new ethers.JsonRpcProvider(providerUrl);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
return wallet;
}, [providerUrl]);

useEffect(() => {
fetch(`${SERVER_URL}/retry-port-events`);
Expand All @@ -49,17 +59,26 @@ export default function Home() {
const newSocket = io(SERVER_URL, { autoConnect: true });

newSocket.on("new-event", (event: Event) => {
console.info("Event from server:", event);
// console.info("Event from server:", event);

if (event.type === EventType.NEW_SLOT) {
if (Number(event.message) === preconfSlot + 64) {
const slot = Number(event.message);
if (slot === preconfSlot + 64) {
setPreconfFinalized(true);
setFinalizationTimerActive(false);
dispatchEvent({
message: `Preconfirmed transaction finalized at slot ${event.message}`,
timestamp: new Date().toISOString(),
});
}

// Drop old requests
setPreconfirmationRequests((prev) =>
prev.filter((req) => req.slot >= slot),
);

// Update the nonce
wallet.getNonce().then((nonce) => setNonce(nonce));
}

// If the event has a special type, handle it differently
Expand Down Expand Up @@ -105,7 +124,7 @@ export default function Home() {
return () => {
newSocket.close();
};
}, [explorerUrl, preconfSlot]);
}, [explorerUrl, preconfSlot, wallet]);

useEffect(() => {
let interval: any = null;
Expand Down Expand Up @@ -149,7 +168,7 @@ export default function Home() {
return () => clearInterval(interval);
}, [finalizationTimerActive]);

async function sendPreconfirmation() {
const sendPreconfirmation = useCallback(async () => {
// Reset state
setEvents([]);
setPreconfSent(true);
Expand All @@ -160,10 +179,31 @@ export default function Home() {
setFinalizationTime(0);

try {
const { payload, txHash } = await createPreconfPayload(providerUrl);
const nonceWithPreconfs =
nonce +
preconfirmationRequests
.map((req) => req.count)
.reduce((acc, c) => acc + c, 0);

const { payload, txHash } = await createPreconfPayload(
wallet,
nonceWithPreconfs,
);

setPreconfirmationRequests((prev) => {
for (let i = 0; i < prev.length; i++) {
if (prev[i].slot === payload.slot) {
prev[i] = { ...prev[i], count: prev[i].count + 1 };
return [...prev];
}
}
prev.push({ slot: payload.slot, count: 1 });
return [...prev];
});

setPreconfSlot(payload.slot);
dispatchEvent({
message: `Preconfirmation request sent for tx: ${txHash} at slot ${payload.slot}`,
message: `Preconfirmation request sent for tx: ${txHash} at slot ${payload.slot} with nonce ${nonceWithPreconfs}`,
timestamp: new Date().toISOString(),
});

Expand All @@ -185,7 +225,7 @@ export default function Home() {
} catch (e) {
console.error(e);
}
}
}, [preconfirmationRequests, nonce, wallet]);

function dispatchEvent(event: Event) {
setEvents((prev) => [event, ...prev]);
Expand Down
5 changes: 5 additions & 0 deletions bolt-web-demo/frontend/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Test private key, for which address[0] holds 1000 ETH in the Kurtosis devnet
export const PRIVATE_KEY =
"39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d";
Comment on lines +2 to +3
Copy link
Contributor

@coderabbitai coderabbitai bot Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Risk: Hardcoded Private Key

Hardcoding a private key is a significant security risk as it can lead to unauthorized access and misuse. Consider using environment variables or a secure vault to manage sensitive information.

- export const PRIVATE_KEY = "39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d";
+ export const PRIVATE_KEY = process.env.PRIVATE_KEY || "";
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const PRIVATE_KEY =
"39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d";
export const PRIVATE_KEY = process.env.PRIVATE_KEY || "";
Tools
Gitleaks

2-3: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

Copy link
Contributor Author

@thedevbirb thedevbirb Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yolo

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thedevbirb, I understand that it might seem convenient to hardcode the private key for testing purposes, but it's crucial to avoid this practice even in development environments. Hardcoding sensitive information like private keys can lead to significant security vulnerabilities. Using environment variables or a secure vault to manage such data is a safer approach. This helps prevent unauthorized access and misuse.

Let me know if you need any assistance in implementing these changes.

export const KURTOSIS_CHAIN_ID = 3151908;
export const SERVER_URL = "http://localhost:3001";
thedevbirb marked this conversation as resolved.
Show resolved Hide resolved
21 changes: 6 additions & 15 deletions bolt-web-demo/frontend/src/lib/wallet.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { SERVER_URL } from "@/app/page";
import { TransactionRequest, keccak256 } from "ethers";
import { ethers } from "ethers";

// Test private key, for which address[0] holds 1000 ETH in the Kurtosis devnet
const PRIVATE_KEY =
"39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d";
import { KURTOSIS_CHAIN_ID, SERVER_URL } from "./constants";

type InclusionRequestPayload = {
slot: number;
Expand All @@ -13,16 +9,13 @@ type InclusionRequestPayload = {
};

export async function createPreconfPayload(
providerUrl: string
wallet: ethers.Wallet,
nonce: number,
): Promise<{ payload: InclusionRequestPayload; txHash: string }> {
// Create a Wallet instance from a private key
const provider = new ethers.JsonRpcProvider(providerUrl);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// Define the transaction
const tx: TransactionRequest = {
chainId: (await provider.getNetwork()).chainId,
nonce: await wallet.getNonce(),
chainId: KURTOSIS_CHAIN_ID,
nonce: nonce,
from: await wallet.getAddress(),
to: "0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD",
value: ethers.parseEther("0.0069420"),
Expand All @@ -39,8 +32,6 @@ export async function createPreconfPayload(
const txHash = keccak256(signedTx);
const slot = (await getLatestSlot()) + 2;

console.log("preconf target slot: ", slot);

// Create a signature over the request fields "slot" and "tx" using the same signer
// to authenticate the preconfirmation request through bolt.
const slotBytes = numberToLittleEndianBytes(slot);
Expand All @@ -57,7 +48,7 @@ export async function createPreconfPayload(

export async function getLatestSlot(): Promise<number> {
const slotResponse = await fetch(`${SERVER_URL}/latest-slot`).then(
(response) => response.json()
(response) => response.json(),
);
return Number(slotResponse.slot);
}
Expand Down
14 changes: 8 additions & 6 deletions scripts/start-demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ echo "Starting the web demo."

# Define the commands as an array
commands=(
"cd bolt-web-demo/frontend && yarn && yarn dev"
"cd bolt-web-demo/backend && yarn && yarn dev"
"cd bolt-web-demo/frontend && yarn && yarn dev"
"cd bolt-web-demo/backend && yarn && yarn dev"
)

# Function to quit all processes on Ctrl+C
quit_all() {
echo "Caught SIGINT, quitting all processes."
pids=($(jobs -p))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shellcheck Warning: Prefer mapfile or read -a

To avoid splitting command output, use mapfile or read -a. This ensures that the output is handled correctly, especially if it contains spaces.

- pids=($(jobs -p))
+ mapfile -t pids < <(jobs -p)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pids=($(jobs -p))
mapfile -t pids < <(jobs -p)
Tools
Shellcheck

[warning] 14-14: Prefer mapfile or read -a to split command output (or quote to avoid splitting).

(SC2207)

for pid in "${pids[@]}"; do
kill "$pid" # Ensure to kill each child process
kill "$pid" # Ensure to kill each child process
done
wait # Wait for all processes to exit before script exits
wait # Wait for all processes to exit before script exits
exit
}

Expand All @@ -25,10 +25,12 @@ trap 'quit_all' SIGINT
# Start the commands in the background
for command in "${commands[@]}"; do
echo "Starting: $command"
eval "$command" & # Use eval to handle complex commands with CD and chaining
eval "$command" & # Use eval to handle complex commands with CD and chaining
done

# Open the browser
sleep 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice DX improvement 😄


# Open the browser
if [ "$(uname)" = "Darwin" ]; then
open "http://localhost:3000"
elif [ "$(expr substr $(uname -s) 1 5)" = "Linux" ]; then
Expand Down
Loading