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

chore(demo): add status section #79

Merged
merged 10 commits into from
Jun 9, 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
36 changes: 16 additions & 20 deletions bolt-web-demo/backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@ app.post("/events", (req, res) => {
const { message } = req.body;

if (!message) {
console.error("No message provided");
res.status(400).send("No message provided");
} else {
// Remove time measurements from the message
const messageWithoutMeasurements = message.replace(/ in .+$/g, "");
// Deduplicate events
if (EVENTS_SET.has(messageWithoutMeasurements)) {
res.status(200).send("OK");
return;
}
EVENTS_SET.add(messageWithoutMeasurements);
// // Remove time measurements from the message
// const messageWithoutMeasurements = message.replace(/ in .+$/g, "");
// // Deduplicate events
// if (EVENTS_SET.has(messageWithoutMeasurements)) {
// console.warn(
// "Duplicate event received, discarding:",
// messageWithoutMeasurements
// );
// res.status(200).send("OK");
// return;
// }
// EVENTS_SET.add(messageWithoutMeasurements);

// Broadcast the message to all connected WebSocket clients
io.emit("new-event", { message, timestamp: new Date().toISOString() });
Expand Down Expand Up @@ -129,7 +134,7 @@ async function sendDevnetEvents() {
sendDevnetEvents();
})();

// Poll for the slot number until we reach slot 128
// Poll for the latest slot number
(async () => {
let beaconClientUrl = DEVNET_ENDPOINTS?.[EventType.BEACON_CLIENT_URL_FOUND];

Expand All @@ -138,23 +143,14 @@ async function sendDevnetEvents() {
beaconClientUrl = DEVNET_ENDPOINTS?.[EventType.BEACON_CLIENT_URL_FOUND];
}

LATEST_SLOT = await getSlot(beaconClientUrl);
while (LATEST_SLOT <= 128) {
while (true) {
LATEST_SLOT = await getSlot(beaconClientUrl);
await new Promise((resolve) => setTimeout(resolve, 1000));

io.emit("new-event", {
type: EventType.NEW_SLOT,
message: LATEST_SLOT,
timestamp: new Date().toISOString(),
});
}

if (LATEST_SLOT > 128) {
io.emit("new-event", {
type: EventType.NEW_SLOT,
message: 128,
timestamp: new Date().toISOString(),
});
await new Promise((resolve) => setTimeout(resolve, 1000));
}
})();
214 changes: 151 additions & 63 deletions bolt-web-demo/frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ export const SERVER_URL = "http://localhost:3001";
export default function Home() {
const [events, setEvents] = useState<Array<Event>>([]);

const [preconfSent, setPreconfSent] = useState<boolean>(false);
const [preconfSlot, setPreconfSlot] = useState<number>(-1);
const [preconfIncluded, setPreconfIncluded] = useState<boolean>(false);

const [timerActive, setTimerActive] = useState<boolean>(false);
const [time, setTime] = useState(0);

const [newSlotNumber, setNewSlotNumber] = useState<number>(-1);
const [beaconClientUrl, setBeaconClientUrl] = useState<string>("");
const [providerUrl, setProviderUrl] = useState<string>("");
const [explorerUrl, setExplorerUrl] = useState<string>("");
const [preconfSent, setPreconfSent] = useState(false);
const [preconfSlot, setPreconfSlot] = useState(-1);
const [preconfIncluded, setPreconfIncluded] = useState(false);
const [preconfFinalized, setPreconfFinalized] = useState(false);

const [preconfTimerActive, setPreconfTimerActive] = useState(false);
const [preconfTime, setPreconfTime] = useState(0);
const [inclusionTimerActive, setInclusionTimerActive] = useState(false);
const [inclusionTime, setInclusionTime] = useState(0);
const [finalizationTimerActive, setFinalizationTimerActive] = useState(false);
const [finalizationTime, setFinalizationTime] = useState(0);

const [newSlotNumber, setNewSlotNumber] = useState(-1);
const [beaconClientUrl, setBeaconClientUrl] = useState("");
const [providerUrl, setProviderUrl] = useState("");
const [explorerUrl, setExplorerUrl] = useState("");

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

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

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

// If the event has a special type, handle it differently
switch (event.type) {
Expand Down Expand Up @@ -77,6 +94,7 @@ export default function Home() {
event.message.toLowerCase().includes("verified merkle proof for tx")
) {
setPreconfIncluded(true);
setInclusionTimerActive(false);
dispatchEvent({
message: `Preconfirmed transaction included at slot ${preconfSlot}`,
link: `${explorerUrl}/slot/${preconfSlot}`,
Expand All @@ -93,22 +111,54 @@ export default function Home() {
useEffect(() => {
let interval: any = null;

if (timerActive) {
if (preconfTimerActive) {
interval = setInterval(() => {
setTime((prev) => prev + 2);
setPreconfTime((prev) => prev + 2);
}, 2);
} else {
clearInterval(interval);
}

return () => clearInterval(interval);
}, [timerActive]);
}, [preconfTimerActive]);

useEffect(() => {
let interval: any = null;

if (inclusionTimerActive) {
interval = setInterval(() => {
setInclusionTime((prev) => prev + 10);
}, 10);
} else {
clearInterval(interval);
}

return () => clearInterval(interval);
}, [inclusionTimerActive]);

useEffect(() => {
let interval: any = null;

if (finalizationTimerActive) {
interval = setInterval(() => {
setFinalizationTime((prev) => prev + 30);
}, 30);
} else {
clearInterval(interval);
}

return () => clearInterval(interval);
}, [finalizationTimerActive]);

async function sendPreconfirmation() {
// Reset state
setEvents([]);
setPreconfSent(true);
setPreconfIncluded(false);
setPreconfFinalized(false);
setPreconfTime(0);
setInclusionTime(0);
setFinalizationTime(0);

try {
const { payload, txHash } = await createPreconfPayload(providerUrl);
Expand All @@ -120,16 +170,18 @@ export default function Home() {

// 1. POST preconfirmation.
// The preconfirmation is considered valid as soon as the server responds with a 200 status code.
setTime(0);
setTimerActive(true);
setPreconfTimerActive(true);
setInclusionTimerActive(true);
setFinalizationTimerActive(true);

const res = await fetch(`${SERVER_URL}/preconfirmation`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (res.status === 200) {
console.log("Preconfirmation successful");
setTimerActive(false);
console.log("Preconfirmation response was successful");
setPreconfTimerActive(false);
}
} catch (e) {
console.error(e);
Expand All @@ -140,6 +192,11 @@ export default function Home() {
setEvents((prev) => [event, ...prev]);
}

const getStatusClass = (status: boolean) => {
const base = "h-4 w-4 border border-gray-800 rounded-full ";
return base + (status ? "bg-green-500" : "bg-yellow-500");
};

return (
<main className="flex min-h-screen flex-col items-center p-24">
<div className="w-full max-w-6xl items-center justify-between lg:flex">
Expand Down Expand Up @@ -193,8 +250,46 @@ export default function Home() {
<div className="w-full max-w-6xl pt-4">
{beaconClientUrl && providerUrl ? (
<div className="w-full">
{preconfSent && (
<div className="grid gap-3 border p-4 border-gray-800 mb-4">
<p className="text-lg">Status</p>
<ul className="text-sm space-y-2">
<li className="flex items-center">
<span className="w-96">Transaction preconfirmed:</span>
<span
id="traffic-light-1"
className={getStatusClass(preconfSent)}
/>
<span className="pl-3">{preconfTime}ms</span>
</li>
<li className="flex items-center">
<span className="w-96">
Transaction confirmed (included in a block):
</span>
<span
id="traffic-light-2"
className={getStatusClass(preconfIncluded)}
/>
<span className="pl-3">{inclusionTime / 1000}s</span>
</li>
<li className="flex items-center">
<span className="w-96">
Transaction finalized (2 epochs after inclusion):
</span>
<span
id="traffic-light-3"
className={getStatusClass(preconfFinalized)}
/>
<span className="pl-3">{finalizationTime / 1000}s</span>
</li>
</ul>
</div>
)}

<div className="grid gap-3 border p-4 border-gray-800">
<p className="text-lg">Step 1: send a transactions eligible for pre-confirmation</p>
<p className="text-lg">
Step 1: Send a transaction eligible for preconfirmation
</p>
<small className="text-sm">
By clicking this button you will create a transaction and send
it as a preconfirmation request to the BOLT sidecar of the
Expand All @@ -214,52 +309,45 @@ export default function Home() {
</div>

{preconfSent && (
<div className="grid gap-3 border p-4 border-gray-800 mt-4">
<p className="text-lg">
Step 2: wait for proposers to issue the preconfirmation response
</p>
<small className="text-sm max-w-3xl">
The transaction will be processed by BOLT and you will
receive a preconfirmation for inclusion in the next block.
</small>

<div>
<p>
Waiting for preconfirmation. Time elapsed: <b>{time}</b>ms
<>
<div className="grid gap-3 border p-4 border-gray-800 mt-4">
<p className="text-lg">
Step 2: Wait for proposers to issue the preconfirmation
response
</p>
<small className="text-sm max-w-3xl">
The transaction will be processed by BOLT and you will
receive a preconfirmation for inclusion in the next block.
</small>
</div>
</div>
)}

<div className="grid gap-3 border p-4 border-gray-800 mt-4">
<p className="text-lg">Event logs</p>
<small className="text-sm max-w-3xl">
This is the list of events received from the server.
</small>

<ScrollArea className="max-h-80">
<ul className="font-mono text-sm">
{events.map((message, index) => (
<li key={index}>
<span>{parseDateToMs(message.timestamp)}</span>
{" | "}
{message.message.toString()}
{message.link && (
<a
href={message.link}
target="_blank"
rel="noreferrer"
className="text-blue-500"
>
{" "}
[link]
</a>
)}
</li>
))}
</ul>
</ScrollArea>
</div>
<div className="grid gap-3 border p-4 border-gray-800 mt-4">
<p className="text-lg">Event logs</p>
<ScrollArea className="max-h-80">
<ul className="font-mono" style={{ fontSize: "0.8rem" }}>
{[...events].reverse().map((message, index) => (
<li key={index}>
<span>{parseDateToMs(message.timestamp)}</span>
{" | "}
{message.message.toString()}
{message.link && (
<a
href={message.link}
target="_blank"
rel="noreferrer"
className="text-blue-500"
>
{" "}
[link]
</a>
)}
</li>
))}
</ul>
</ScrollArea>
</div>
</>
)}
</div>
) : (
<div className="w-full max-w-6xl pt-4">
Expand Down
8 changes: 6 additions & 2 deletions builder/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ func (b *Builder) subscribeToRelayForConstraints(relayBaseEndpoint, authHeader s
continue
}

EmitBoltDemoEvent(fmt.Sprintf("Received constraint from relay for slot %d, stored in cache (path: %s)", constraint.Message.Slot, SubscribeConstraintsPath))

// For every constraint, we need to check if it has already been seen for the associated slot
slotConstraints, _ := b.constraintsCache.Get(constraint.Message.Slot)
if len(slotConstraints) == 0 {
Expand All @@ -405,7 +407,9 @@ func (b *Builder) subscribeToRelayForConstraints(relayBaseEndpoint, authHeader s

// Update the slot constraints in the cache
b.constraintsCache.Put(constraint.Message.Slot, slotConstraints)

}

}

return nil
Expand Down Expand Up @@ -456,6 +460,7 @@ func (b *Builder) onSealedBlock(opts SubmitBlockOpts, constraints types.HashToCo
var versionedBlockRequestWithPreconfsProofs *common.VersionedSubmitBlockRequestWithProofs

if len(constraints) > 0 {
EmitBoltDemoEvent(fmt.Sprintf("sealing block %d with %d constraints", opts.Block.Number(), len(constraints)))
log.Info(fmt.Sprintf("[BOLT]: Sealing block with %d preconfirmed transactions for block %d", len(constraints), opts.Block.Number()))
payloadTransactions := opts.Block.Transactions()

Expand Down Expand Up @@ -529,8 +534,7 @@ func (b *Builder) onSealedBlock(opts SubmitBlockOpts, constraints types.HashToCo
timeForProofs := time.Since(timeStart)

// BOLT: send event to web demo
message := fmt.Sprintf("created %d merkle proofs for block %d in %v", len(constraints), opts.Block.Number(), timeForProofs)
EmitBoltDemoEvent(message)
EmitBoltDemoEvent(fmt.Sprintf("created %d merkle proofs for block %d in %v", len(constraints), opts.Block.Number(), timeForProofs))

versionedBlockRequestWithPreconfsProofs = &common.VersionedSubmitBlockRequestWithProofs{
Inner: versionedBlockRequest,
Expand Down
Loading