/ 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>