Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(demo): handling of multiple preconfs in the same slot
Browse files Browse the repository at this point in the history
thedevbirb committed Jul 4, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent d074de9 commit 4e61972
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
@@ -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;
@@ -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`);
@@ -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);
@@ -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}`,
@@ -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);
@@ -301,6 +328,7 @@ export default function Home() {
<Button
className="max-w-sm"
onClick={() => sendPreconfirmation()}
disabled={cannotSendPreconf}
>
Send Preconfirmation
</Button>
3 changes: 3 additions & 0 deletions bolt-web-demo/frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -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
@@ -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 = {
@@ -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"),
@@ -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);
@@ -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);
}

0 comments on commit 4e61972

Please sign in to comment.