Skip to content

Commit

Permalink
Merge pull request #79 from chainbound/chore/demo/status
Browse files Browse the repository at this point in the history
chore(demo): add status section
  • Loading branch information
merklefruit authored Jun 9, 2024
2 parents bcbc18b + 067b668 commit abf9f46
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 88 deletions.
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

0 comments on commit abf9f46

Please sign in to comment.