/ preconfirmations-old.html
preconfirmations-old.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Ethereum Transaction Lifecycle Visualizer</title> 7 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script> 8 <style> 9 body, html { 10 margin: 0; 11 padding: 0; 12 width: 100%; 13 height: 100%; 14 overflow: hidden; 15 font-family: Arial, sans-serif; 16 } 17 #visualization { 18 width: 100%; 19 height: 100%; 20 } 21 #controls { 22 position: fixed; 23 bottom: 20px; 24 right: 20px; 25 width: 150px; 26 height: 150px; 27 } 28 .control-button { 29 position: absolute; 30 width: 60px; 31 height: 30px; 32 background-color: rgba(255, 255, 255, 0.7); 33 border: 1px solid #999; 34 border-radius: 5px; 35 } 36 #upBtn { top: 0; left: 45px; } 37 #leftBtn { top: 60px; left: 0; } 38 #rightBtn { top: 60px; right: 0; } 39 #downBtn { bottom: 0; left: 45px; } 40 #metadata, #addNodeForm { 41 position: fixed; 42 background-color: rgba(255, 255, 255, 0.9); 43 padding: 10px; 44 border-radius: 5px; 45 max-width: 300px; 46 max-height: 80%; 47 overflow-y: auto; 48 } 49 #metadata { 50 bottom: 20px; 51 left: 20px; 52 } 53 #addNodeForm { 54 top: 20px; 55 left: 20px; 56 display: none; /* Initially hidden */ 57 } 58 #metadata input, #metadata textarea, #addNodeForm input, #addNodeForm select { 59 width: 100%; 60 margin-bottom: 10px; 61 } 62 #metadata button, #addNodeForm button { 63 margin-right: 10px; 64 } 65 #editToggle { 66 position: fixed; 67 top: 20px; 68 right: 20px; 69 } 70 </style> 71 </head> 72 <body> 73 <div id="Title"> 74 <h1>Preconfirmations</h1> 75 </div> 76 <div id="Subtitle"> 77 <h2>The Setting</h2> 78 </div> 79 <div id="Content"> 80 <p>When a user submits a transaction to Ethereum, they have an expectation that there will be an interval of time before they can be sure that the transaction has been included in the blockchain.</p> 81 82 <p>In Ethereum, a block is produced every 12 seconds (<a href="https://ethos.dev/beacon-chain">one slot</a>), and so the shortest time interval for transaction inclusion is 12 seconds.</p> 83 84 <p>Beyond inclusion, there is the notion of finalization, which is the point at which the transaction is considered irreversible. In Ethereum, this is considered to occur after 2 epochs, where each 85 <a href="https://ethos.dev/beacon-chain">epoch</a> contains 32 slots of 12 seconds each. </p> 86 87 <p>So from a user's perspective, a transaction goes through the following stages:</p> 88 89 <p><strong>Submission > Inclusion > Finalization </strong></p> 90 </div> 91 <div id="visualization"></div> 92 <div id="controls"> 93 <button id="upBtn" class="control-button">Up</button> 94 <button id="leftBtn" class="control-button">Previous</button> 95 <button id="rightBtn" class="control-button">Next</button> 96 <button id="downBtn" class="control-button">Down</button> 97 </div> 98 <div id="metadata"></div> 99 <div id="addNodeForm"> 100 <h3>Add New Node</h3> 101 <input type="text" id="newNodeName" placeholder="Node Name"> 102 <select id="newNodeDirection"> 103 <option value="next">Next</option> 104 <option value="up">Up</option> 105 <option value="down">Down</option> 106 </select> 107 <button onclick="addNewNode()">Add Node</button> 108 </div> 109 <button id="editToggle" onclick="toggleEditMode()">Edit Mode</button> 110 111 <script> 112 let nodeIds = ["User"]; 113 let connectionMatrix = { 114 "User": {} 115 }; 116 let nodeMetadata = { 117 "User": { 118 "content": "Initiates and signs the transaction", 119 "url": "https://ethereum.org/en/developers/docs/transactions/" 120 } 121 }; 122 123 const directionToGridDelta = { 124 "next": [1, 0], 125 "previous": [-1, 0], 126 "up": [0, -1], 127 "down": [0, 1] 128 }; 129 130 let isEditMode = false; 131 132 function assignGridPositions(startNode) { 133 const gridPositions = {}; 134 const queue = [[startNode, 0, 0]]; 135 const visited = new Set(); 136 137 while (queue.length > 0) { 138 const [node, x, y] = queue.shift(); 139 if (visited.has(node)) continue; 140 visited.add(node); 141 gridPositions[node] = [x, y]; 142 143 for (let neighbor in connectionMatrix[node]) { 144 const connection = connectionMatrix[node][neighbor]; 145 if (connection) { 146 const [dx, dy] = directionToGridDelta[connection[0]]; 147 queue.push([neighbor, x + dx, y + dy]); 148 } 149 } 150 } 151 152 return gridPositions; 153 } 154 155 function createVisualization() { 156 const width = window.innerWidth; 157 const height = window.innerHeight; 158 const nodeRadius = Math.min(width, height) * 0.05; 159 const transactionRadius = nodeRadius * 0.3; 160 const gridSize = Math.min(width, height) * 0.2; 161 162 d3.select("#visualization").selectAll("*").remove(); 163 164 const gridPositions = assignGridPositions(nodeIds[0]); 165 166 let nodes = nodeIds.map(id => ({ 167 id: id, 168 x: (gridPositions[id][0] + 2) * gridSize, 169 y: (gridPositions[id][1] + 2) * gridSize 170 })); 171 172 const links = []; 173 for (let source in connectionMatrix) { 174 for (let target in connectionMatrix[source]) { 175 if (connectionMatrix[source][target]) { 176 links.push({ 177 source: nodes.find(n => n.id === source), 178 target: nodes.find(n => n.id === target), 179 type: connectionMatrix[source][target][0] 180 }); 181 } 182 } 183 } 184 185 const svg = d3.select("#visualization") 186 .append("svg") 187 .attr("width", width) 188 .attr("height", height); 189 190 const link = svg.append("g") 191 .selectAll("line") 192 .data(links) 193 .join("line") 194 .attr("stroke", "#999") 195 .attr("stroke-opacity", 0.6) 196 .attr("x1", d => d.source.x) 197 .attr("y1", d => d.source.y) 198 .attr("x2", d => d.target.x) 199 .attr("y2", d => d.target.y); 200 201 const node = svg.append("g") 202 .selectAll("circle") 203 .data(nodes) 204 .join("circle") 205 .attr("r", nodeRadius) 206 .attr("fill", "lightblue") 207 .attr("cx", d => d.x) 208 .attr("cy", d => d.y); 209 210 const label = svg.append("g") 211 .selectAll("text") 212 .data(nodes) 213 .join("text") 214 .text(d => d.id) 215 .attr("font-size", nodeRadius * 0.3) 216 .attr("text-anchor", "middle") 217 .attr("dominant-baseline", "central") 218 .attr("x", d => d.x) 219 .attr("y", d => d.y); 220 221 const transaction = svg.append("circle") 222 .attr("r", transactionRadius) 223 .attr("fill", "red"); 224 225 return { nodes, transaction }; 226 } 227 228 let { nodes, transaction } = createVisualization(); 229 let currentNode = nodes[0]; 230 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 231 232 const directionMapping = { 233 "up": "up", 234 "down": "down", 235 "next": "right", 236 "previous": "left" 237 }; 238 239 function updateMetadataDisplay(nodeId) { 240 const metadata = nodeMetadata[nodeId]; 241 let html = `<h3><a href="${metadata.url}" target="_blank">${nodeId}</a></h3>`; 242 if (isEditMode) { 243 html += ` 244 <form id="metadataForm"> 245 <input type="text" id="nodeNameInput" value="${nodeId}"> 246 <textarea id="content" name="content" placeholder="Content">${metadata.content}</textarea> 247 <input type="url" id="url" name="url" value="${metadata.url}" placeholder="URL"> 248 <button type="button" onclick="saveMetadata('${nodeId}')">Save</button> 249 <button type="button" onclick="deleteNode('${nodeId}')">Delete</button> 250 <button type="button" onclick="uploadNodeData()">Upload</button> 251 <button type="button" onclick="downloadNodeData()">Download</button> 252 </form>`; 253 } else { 254 html += `<p>${metadata.content}</p>`; 255 } 256 document.getElementById("metadata").innerHTML = html; 257 } 258 259 function saveMetadata(oldNodeId) { 260 const newNodeId = document.getElementById("nodeNameInput").value; 261 const content = document.getElementById("content").value; 262 const url = document.getElementById("url").value; 263 264 const newMetadata = { 265 content: content, 266 url: url 267 }; 268 269 if (oldNodeId !== newNodeId) { 270 nodeMetadata[newNodeId] = newMetadata; 271 delete nodeMetadata[oldNodeId]; 272 nodeIds = nodeIds.map(id => id === oldNodeId ? newNodeId : id); 273 274 // Update connectionMatrix 275 connectionMatrix[newNodeId] = connectionMatrix[oldNodeId]; 276 delete connectionMatrix[oldNodeId]; 277 for (let node in connectionMatrix) { 278 if (connectionMatrix[node][oldNodeId]) { 279 connectionMatrix[node][newNodeId] = connectionMatrix[node][oldNodeId]; 280 delete connectionMatrix[node][oldNodeId]; 281 } 282 } 283 284 currentNode = { id: newNodeId, x: currentNode.x, y: currentNode.y }; 285 //Update the nodes object 286 nodes = nodes.map(n => n.id === oldNodeId ? currentNode : n); 287 console.log(nodes); 288 } else { 289 nodeMetadata[newNodeId] = newMetadata; 290 } 291 292 ({ nodes, transaction } = createVisualization()); 293 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 294 updateButtons(); 295 updateMetadataDisplay(newNodeId); 296 } 297 298 function resetMetadata(nodeId) { 299 nodeMetadata[nodeId] = { 300 "content": "", 301 "url": "" 302 }; 303 updateMetadataDisplay(nodeId); 304 } 305 306 function moveTransaction(direction) { 307 for (let targetId in connectionMatrix[currentNode.id]) { 308 const connection = connectionMatrix[currentNode.id][targetId]; 309 if (connection && connection[0] === direction) { 310 const targetNode = nodes.find(n => n.id === targetId); 311 if (targetNode) { 312 currentNode = targetNode; 313 transaction.transition() 314 .duration(500) 315 .attr("cx", currentNode.x) 316 .attr("cy", currentNode.y); 317 updateButtons(); 318 updateMetadataDisplay(currentNode.id); 319 break; 320 } 321 } 322 } 323 } 324 325 function updateButtons() { 326 for (let direction in directionMapping) { 327 const buttonId = `#${directionMapping[direction]}Btn`; 328 let isEnabled = false; 329 for (let targetId in connectionMatrix[currentNode.id]) { 330 const connection = connectionMatrix[currentNode.id][targetId]; 331 if (connection && connection[0] === direction) { 332 isEnabled = true; 333 break; 334 } 335 } 336 d3.select(buttonId).property("disabled", !isEnabled); 337 } 338 } 339 340 function toggleEditMode() { 341 isEditMode = !isEditMode; 342 updateMetadataDisplay(currentNode.id); 343 document.getElementById("editToggle").textContent = isEditMode ? "View Mode" : "Edit Mode"; 344 document.getElementById("addNodeForm").style.display = isEditMode ? "block" : "none"; 345 } 346 347 function addNewNode() { 348 const newNodeName = document.getElementById("newNodeName").value; 349 const direction = document.getElementById("newNodeDirection").value; 350 351 if (!newNodeName || nodeIds.includes(newNodeName)) { 352 alert("Please enter a unique node name."); 353 return; 354 } 355 356 nodeIds.push(newNodeName); 357 connectionMatrix[newNodeName] = {}; 358 nodeMetadata[newNodeName] = { 359 "content": "", 360 "url": "" 361 }; 362 363 connectionMatrix[currentNode.id][newNodeName] = [direction, directionToGridDelta[direction]]; 364 const oppositeDirection = direction === "next" ? "previous" : (direction === "up" ? "down" : "up"); 365 connectionMatrix[newNodeName][currentNode.id] = [oppositeDirection, directionToGridDelta[oppositeDirection]]; 366 367 ({ nodes, transaction } = createVisualization()); 368 369 // Move the transaction to the new node 370 currentNode = nodes.find(n => n.id === newNodeName); 371 transaction.transition() 372 .duration(500) 373 .attr("cx", currentNode.x) 374 .attr("cy", currentNode.y); 375 376 updateButtons(); 377 updateMetadataDisplay(currentNode.id); 378 379 // Clear the input field 380 document.getElementById("newNodeName").value = ""; 381 } 382 383 d3.select("#upBtn").on("click", () => moveTransaction("up")); 384 d3.select("#downBtn").on("click", () => moveTransaction("down")); 385 d3.select("#leftBtn").on("click", () => moveTransaction("previous")); 386 d3.select("#rightBtn").on("click", () => moveTransaction("next")); 387 388 updateButtons(); 389 updateMetadataDisplay(currentNode.id); 390 391 window.addEventListener('resize', () => { 392 ({ nodes, transaction } = createVisualization()); 393 currentNode = nodes.find(n => n.id === currentNode.id); 394 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 395 updateButtons(); 396 updateMetadataDisplay(currentNode.id); 397 }); 398 399 function deleteNode(nodeId) { 400 if (nodeId === "User") { 401 alert("Cannot delete the initial node."); 402 return; 403 } 404 405 delete nodeMetadata[nodeId]; 406 delete connectionMatrix[nodeId]; 407 nodeIds = nodeIds.filter(id => id !== nodeId); 408 for (let node in connectionMatrix) { 409 delete connectionMatrix[node][nodeId]; 410 } 411 412 ({ nodes, transaction } = createVisualization()); 413 currentNode = nodes[0]; 414 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 415 updateButtons(); 416 updateMetadataDisplay(currentNode.id); 417 } 418 419 function downloadNodeData() { 420 const data = { 421 nodeIds: nodeIds, 422 connectionMatrix: connectionMatrix, 423 nodeMetadata: nodeMetadata 424 }; 425 const blob = new Blob([JSON.stringify(data)], { type: "application/json" }); 426 const url = URL.createObjectURL(blob); 427 const a = document.createElement("a"); 428 a.href = url; 429 a.download = "preconfirmations.json"; 430 a.click(); 431 } 432 433 function uploadNodeData() { 434 const input = document.createElement("input"); 435 input.type = "file"; 436 input.accept = ".json"; 437 input.onchange = e => { 438 const file = e.target.files[0]; 439 const reader = new FileReader(); 440 reader.onload = e => { 441 const data = JSON.parse(e.target.result); 442 nodeIds = data.nodeIds; 443 connectionMatrix = data.connectionMatrix; 444 nodeMetadata = data.nodeMetadata; 445 ({ nodes, transaction } = createVisualization()); 446 currentNode = nodes[0]; 447 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 448 updateButtons(); 449 updateMetadataDisplay(currentNode.id); 450 }; 451 reader.readAsText(file); 452 }; 453 input.click(); 454 } 455 456 function downloadNodeData() { 457 const data = { 458 nodeIds: nodeIds, 459 connectionMatrix: connectionMatrix, 460 nodeMetadata: nodeMetadata 461 }; 462 const blob = new Blob([JSON.stringify(data)], { type: "application/json" }); 463 const url = URL.createObjectURL(blob); 464 const a = document.createElement("a"); 465 a.href = url; 466 a.download = "preconfirmations.json"; 467 a.click(); 468 } 469 470 function onLoadPopulateNodesFromURl() { 471 //Fetch json file from relative URL, check if it exists, if so populate the nodes 472 fetch('preconfirmations.json') 473 .then(response => response.json()) 474 .then(data => { 475 nodeIds = data.nodeIds; 476 connectionMatrix = data.connectionMatrix; 477 nodeMetadata = data.nodeMetadata; 478 ({ nodes, transaction } = createVisualization()); 479 currentNode = nodes[0]; 480 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 481 updateButtons(); 482 updateMetadataDisplay(currentNode.id); 483 }) 484 .catch((error) => { 485 console.error('Error:', error); 486 }); 487 } 488 489 </script> 490 </body> 491 </html>