Bitshares Astro UI Dev Blog - Observing the live blockchain! Recent average statistics, transaction data, earnings, charts, etc!

in BitShares3 months ago (edited)

image.png

September continues to bring plenty of updates to the Astro UI!

Thank you for exploring the latest Bitshares Astro UI development blog!

The Bitshares Astro UI aims to be an ongoing, innovative project, enhancing Bitshares front-end user interfaces with cutting-edge technology for 2024 and beyond.

So, what’s in today’s update?

Creating an Astro UI implementation of the reference UI's blockchain explore page!

Live blockchain explore page!

For an initial reference, check out the reference UI's blockchain data explorer overview page:

image.png

There's some interesting data here which I felt was missing from the Astro UI, namely:

  • Stats summarizing the recent blocks & recent transactions
  • The recent history
  • Recent blocks
  • Charts showing off the trx count/block

Now let's see what I've thrown together:

image.png

Most of what's on the reference page is present in the Astro UI's blockchain overview page, providing you a real time insight into the speed, reliability and status of the Bitshares blockchain, however there are a few notable differences introduced in the Astro UI implementation, notably:

  • We're no longer displaying the time since last block because it can easily be manually deduced by the end user from looking at the most recent block timestamp.
  • Calculating the transactions per second & per block to 4 decimal places, not 2.
  • Not showing the block time chart, because it's always 3 seconds, so it doesn't give any extra info than that.
  • The BTS fees collected from the recent blocks is prominently shown, to highlight to you in real time the utilization of the core token on the Bitshares blockchain.
  • The current witness is shown, this changes every block.
  • Switched to a 24 hour timestamp for the recent block timestamp table cell
  • The operations in each transaction (row) within the recent activity card doesn't fully translate the contents of the operation to a localized string, instead a badge containing the operation's numbers within each transaction in the recent blocks. When you click on an operation button in the recent activity table row then the user is shown the JSON contents of the operation
  • External links are provided to the Blocksights Bitshares blockchain explorer for the witness IDs and blockchain numbers - providing users a shortcut to more in depth analytics of the Bitshares blockchain witnesses/blocks.

Live blockchain code implementation

If you're interested in how this is achieved, I'll provide some of the key chunks of code which make the above possible!

React code for initiating the live block data & parsing the block response from the main thread:

  let [recentBlocks, setRecentBlocks] = useState([]);
  useEffect(() => {
    if (!currentNode || !currentNode.url) return;
    // on ipc render process
    window.electron.requestBlocks({url: currentNode.url});
    window.electron.onBlockResponse((data) => {
      if (recentBlocks.find((x) => x.block === data.block)) return;
      setRecentBlocks((prevBlocks) => {
        return [...prevBlocks, data];
      });
      window.electron.stillAlive({});
    });
  }, [currentNode]);

.
For the renderer to communicate with the electron background main thread, we need to establish some preloaded code:

    // WS queries
    requestBlocks: async (args) => ipcRenderer.send('requestBlocks', args),
    onBlockResponse: (func) => {
        ipcRenderer.on('blockResponse', (event, data) => {
            func(data);
        });
    },
  stopBlocks: async () => ipcRenderer.send("stopBlocks", args),

.
And this is the electron code we can reach via the ipc renderer:

  let continueFetching = false;
  let latestBlockNumber = 0;
  let isFetching = false;
  let apisInstance = null;
  let fetchTimeout = null;

  const fetchBlocks = async () => {
    isFetching = true;
    while (continueFetching) {
      let currentBlock;
      try {
        currentBlock = await apisInstance.db_api().exec("get_block", [latestBlockNumber]);
      } catch (error) {
        console.log({ error });
        continueFetching = false;
        isFetching = false;
        break;
      }
      mainWindow.webContents.send("blockResponse", {
        ...currentBlock,
        block: latestBlockNumber,
      });
      latestBlockNumber += 1;

      await new Promise((resolve) => {
        fetchTimeout = setTimeout(resolve, 4200);
      });
    }

    if (!continueFetching) {
      apisInstance.close();
      apisInstance = null;
    }
    isFetching = false;
  };

  ipcMain.on("requestBlocks", async (event, arg) => {
    const { url } = arg;

    // Stop any ongoing fetching process
    if (isFetching) {
      continueFetching = false;
      clearTimeout(fetchTimeout); // Clear the timeout to stop the current fetching process immediately
      await new Promise((resolve) => setTimeout(resolve, 100)); // Short wait to ensure the current fetching process stops
    }

    // Reset state variables
    continueFetching = true;
    isFetching = false;

    // Create a new Apis instance
    try {
      apisInstance = Apis.instance(url, true);
    } catch (error) {
      console.log({ error, location: "Apis.instance", url });
      continueFetching = false;
      isFetching = false;
      return;
    }

    try {
      await apisInstance.init_promise;
      console.log("connected to:", apisInstance.chain_id);
    } catch (err) {
      console.log({ err });
      continueFetching = false;
      isFetching = false;
      if (apisInstance) {
        apisInstance.close();
        apisInstance = null;
      }
      return;
    }

    let globalProperties;
    try {
      globalProperties = await apisInstance.db_api().exec("get_dynamic_global_properties", []);
    } catch (error) {
      console.log({ error, location: "globalProperties", url });
      continueFetching = false;
      isFetching = false;
      return;
    }

    latestBlockNumber = globalProperties.head_block_number;

    const blockPromises = [];
    for (let i = latestBlockNumber - 1; i > latestBlockNumber - 31; i--) {
      blockPromises.push(apisInstance.db_api().exec("get_block", [i]));
    }

    let lastFewBlocks = [];
    try {
      lastFewBlocks = await Promise.all(blockPromises);
    } catch (error) {
      console.log({ error });
    }

    for (let i = lastFewBlocks.length - 1; i >= 0; i--) {
      mainWindow.webContents.send("blockResponse", {
        ...lastFewBlocks[i],
        block: latestBlockNumber - 1 - i,
      });
    }

    // Start fetching blocks continuously
    fetchBlocks();
  });

  // Handle the user navigating away from the page
  ipcMain.on("stopBlocks", () => {
    continueFetching = false;
    clearTimeout(fetchTimeout); // Clear the timeout to stop the current fetching process immediately
    if (apisInstance) {
      apisInstance.close();
      apisInstance = null;
    }
    isFetching = false;
  });

.
This code initially fetches the last 30 blocks, then begins fetching the latest block every 4.2 seconds - whilst the blockchain blocks are every 3 seconds we wait a little extra to reduce the load on the network and to avoid rate limits.

It seems this is the same method that the reference UI uses, regularly fetching the blockchain data rather than subscribing to every new block, as subscribing to global objects like that is disabled by some of the main nodes - that's something we could overcome via a localhost node perhaps in the future..

There's still a few improvements this page could do with in the future:

  • Going back more blocks for a greater initial recent blocks data set
  • Improving the rechart chart - currently it's a little choppy, perhaps disabling the animations entirely could make it smoother as more blocks are added to the recent blocks array.
  • Utilizing line charts - perhaps the recent transactions per block (or another stat) would look better as a line chart than a bar chart
  • Showing unique committee member count
  • Breakdown of who created the recent blocks - percentages and leaderboard data in a dialog
  • Breakdown of the recent operation types within the recent blcoks - percentage breakdown of each operation type out of the total items
  • Extrapolate fees earned per day/week/month/year based on the recently collected fees
    • This is likely to be a major underestimate as some operations can cost upwards of a million BTS to perform where as most other operations run about a single BTS... regardless it could be an interesting additional statistic to showcase.
  • Show a localized string instead of the operation number (still not a full description)
  • Improve tolerance for refreshing blockchain data page via the hidden dev menu

Check out the previous hive posts regarding the Astro UI:


Thanks for reading this far, I'm looking forwards to any comments you have!

Download the Bitshares Astro UI today! https://github.com/BTS-CM/astro-ui/releases

Sort:  


The rewards earned on this comment will go directly to the people sharing the post on Reddit as long as they are registered with @poshtoken. Sign up at https://hiveposh.com. Otherwise, rewards go to the author of the blog post.