Skip to content

Commit

Permalink
fix(demo): handling of multiple preconfs in the same slot
Browse files Browse the repository at this point in the history
  • Loading branch information
thedevbirb committed Jul 4, 2024
1 parent d074de9 commit 4e61972
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 16 deletions.
36 changes: 32 additions & 4 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 } from "react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import { createPreconfPayload } from "@/lib/wallet";
import { PRIVATE_KEY, createPreconfPayload } from "@/lib/wallet";
import { EventType } from "@/lib/types";
import { Progress } from "@/components/ui/progress";
import { ethers } from "ethers";
import { PRECONF_SLOT_IN_ADVANCE, SLOTS_PER_EPOCH } from "@/lib/utils";

type Event = {
message: string;
Expand Down Expand Up @@ -37,6 +39,18 @@ export default function Home() {
const [beaconClientUrl, setBeaconClientUrl] = useState("");
const [providerUrl, setProviderUrl] = useState("");
const [explorerUrl, setExplorerUrl] = useState("");
const [currentSlot, setCurrentSlot] = useState(0);
const [nonceAtLastSentPreconf, setNonceAtLastSentPreconf] = useState<
number | undefined
>();

// Cannot send preconfirmation if we're near the end of the epoch
const cannotSendPreconf = useMemo(
() =>
currentSlot % SLOTS_PER_EPOCH >=
SLOTS_PER_EPOCH - PRECONF_SLOT_IN_ADVANCE,
[currentSlot],
);

useEffect(() => {
fetch(`${SERVER_URL}/retry-port-events`);
Expand All @@ -52,6 +66,7 @@ export default function Home() {
console.info("Event from server:", event);

if (event.type === EventType.NEW_SLOT) {
setCurrentSlot(Number(event.message));
if (Number(event.message) === preconfSlot + 64) {
setPreconfFinalized(true);
setFinalizationTimerActive(false);
Expand Down Expand Up @@ -160,7 +175,19 @@ export default function Home() {
setFinalizationTime(0);

try {
const { payload, txHash } = await createPreconfPayload(providerUrl);
const provider = new ethers.JsonRpcProvider(providerUrl);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
const currentNonce = await wallet.getNonce();
const nonce = nonceAtLastSentPreconf
? nonceAtLastSentPreconf
: currentNonce;

const { payload, txHash } = await createPreconfPayload({
providerUrl,
slot: currentSlot + PRECONF_SLOT_IN_ADVANCE,
nonce,
});

setPreconfSlot(payload.slot);
dispatchEvent({
message: `Preconfirmation request sent for tx: ${txHash} at slot ${payload.slot}`,
Expand All @@ -180,7 +207,7 @@ export default function Home() {
});
if (res.status === 200) {
console.log("Preconfirmation response was successful");
setPreconfTimerActive(false);
setNonceAtLastSentPreconf(nonce + 1);
}
} catch (e) {
console.error(e);
Expand Down Expand Up @@ -301,6 +328,7 @@ export default function Home() {
<Button
className="max-w-sm"
onClick={() => sendPreconfirmation()}
disabled={cannotSendPreconf}
>
Send Preconfirmation
</Button>
Expand Down
3 changes: 3 additions & 0 deletions bolt-web-demo/frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

export const SLOTS_PER_EPOCH = 32;
export const PRECONF_SLOT_IN_ADVANCE = 2;
32 changes: 20 additions & 12 deletions bolt-web-demo/frontend/src/lib/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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 =
export const PRIVATE_KEY =
"39725efee3fb28614de3bacaffe4cc4bd8c436257e2c8bb887c4b5c4be45e76d";

type InclusionRequestPayload = {
Expand All @@ -12,17 +12,25 @@ type InclusionRequestPayload = {
signature: string;
};

export async function createPreconfPayload(
providerUrl: string
): Promise<{ payload: InclusionRequestPayload; txHash: string }> {
/**
* @param nonceIncrease to add in case more than one preconf is sent for the same slot
*/
export async function createPreconfPayload(data: {
providerUrl: string;
slot: number;
nonce: number;
}): Promise<{
payload: InclusionRequestPayload;
txHash: string;
}> {
// Create a Wallet instance from a private key
const provider = new ethers.JsonRpcProvider(providerUrl);
const provider = new ethers.JsonRpcProvider(data.providerUrl);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// Define the transaction
const tx: TransactionRequest = {
chainId: (await provider.getNetwork()).chainId,
nonce: await wallet.getNonce(),
nonce: data.nonce,
from: await wallet.getAddress(),
to: "0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD",
value: ethers.parseEther("0.0069420"),
Expand All @@ -37,13 +45,10 @@ export async function createPreconfPayload(
const populated = await wallet.populateCall(tx);
const signedTx = await wallet.signTransaction(populated);
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);
const slotBytes = numberToLittleEndianBytes(data.slot);
const txHashBytes = hexToBytes(txHash);
const message = new Uint8Array(slotBytes.length + txHashBytes.length);
message.set(slotBytes);
Expand All @@ -52,12 +57,15 @@ export async function createPreconfPayload(
const messageDigest = keccak256(message);
const signature = wallet.signingKey.sign(messageDigest).serialized;

return { payload: { slot, tx: signedTx, signature }, txHash };
return {
payload: { slot: data.slot, tx: signedTx, signature },
txHash,
};
}

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

0 comments on commit 4e61972

Please sign in to comment.