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