• Pricing
  • Enterprise
  • Customers
  • Blog

EVM speed tester: Measure how fast nodes respond to transactions 


Ethereum is the most popular network among all blockchain protocols—it is adopted in a wide range of digital asset applications, including NFT and decentralized finance industries. Currently, it has over one million transactions every day and there are more than 6000 nodes running all over the world.

One of the most frequently asked questions from developers is how fast can a node catch a transaction in the mempool.

The mempool of Ethereum, or any EVM-based network, also called transaction pool or txpool, is the dynamic in-memory area where pending transactions reside before they are included in a block and thus become static. See also Exploring the methods of looking into Ethereum’s transaction pool.

The speed of a node receiving a transactions in its txpool is important in many ways: for a DeFi service provider, it means a better user experience; for NFT traders, it means being able to sell and buy tokens quicker.

Would it be useful to have a tool to measure the txpool performance of a node?

EVM speed tester is the tool that does exactly that. It measures how fast a node can get a transaction in its txpool sent from a different node. It is installation free and easy to use.


An EVM address and a private key. Make sure there are some funds on the address to pay for the transaction. For testnets, use a faucet.

Any EVM-based node—for example, Ethereum, Polygon, Binance Smart Chain, Avalanche C-Chain, Fantom.

Some funds in the wallet, testnet Faucet is perfectly fine.

At least two different nodes on the same network with a WebSocket endpoint:

To get nodes:

  1. Sign up with Chainstack.
  2. Deploy a node.
  3. Get the deployed node’s WSS endpoint.

About the speed tester

The EVM speed tester is a serverless web app. You can simply open it on this JSFiddle. The code is explained in the second half of this article, it is recommended to any reader who wants to customize it or dive into the mechanics.

Two WebSocket endpoints and one address are required to run the app. After the program start, the transaction is initiated on both endpoints one after another. It is a simple transaction. The address (identified using the private key) sends the network’s base currency (e.g., ether) to itself over and over again. So the user can see how fast both nodes received the transaction.

This is a sample output when the code is successfully run:

Imagine playing fetch with two dogs, this code is similar to that. The difference is now both dogs take turns throwing the ball to keep the game going.

This app was tested with the Ethereum Ropsten testnet but it should be easily configured to use on any other EVM networks, mainnet, or testnet.

Using the speed tester

The tool needs an address and a private key to send transactions. Every transaction burns a small amount of gas fee, so please try it on testnets before running it on the mainnet.

The code is explained in detail in the next section. If further assurance is needed, feel free to jump on our Discord.

The live code:

  1. Enter the private key and your address.
  2. Enter the amount of the base network currency (e.g., ether) to transfer in each transaction. The default value is 0.1 ether, the minimum value is 1 wei.
  3. Enter the address of the WebSocket endpoints, it usually starts with ws:// or wss://.
  4. Click start;
  5. Wait until both connections are established, the speed test starts after that:

Now the code is up and running. Your address is sending the currency to itself over and over again. It is a meaningless transaction but it helps to leverage the performance of nodes behind these endpoints.

Inner workings

The only external source referenced in this app is web3.js, it was hosted on cdn.jsdelivr.net and it is also used in this tutorial on Ethereum’s official website.


The initialization function is bare. Mainly getting user inputs and passing it to another function initNode():

function init(){
    walletPrivateKey = document.getElementById("walletPrivateKey").value
    walletPublicKey = document.getElementById("walletPublicKey").value
    wsURL1 = document.getElementById("wsURL1").value
    wsURL2 = document.getElementById("wsURL2").value
    node1IsReady = false
    node2IsReady = false
    transValue = Web3.utils.toWei(document.getElementById("transValue").value,"ether")
    initNode ("node 1",wsURL1,"web3_1","subscribe_1","timer_1","node1IsReady")
    initNode ("node 2",wsURL2,"web3_2","subscribe_2","timer_2","node2IsReady")


InitNode function initializes a node with the given parameters.

function initNode(node_name, wsURL, web3_name, subscribe_name, timer_name,readyFlag_name)
  • node_namenode 1 or node 2.
  • wsURL — the WebSocket URL the user filled in.
  • web3_name — reference to the web3 global instance. The web3 object is from web3.js and is declared at the beginning of code block. In functions, it is referenced as window[“web3_1”] or window[“web3_2”], therefore the parameter is a string. Same for subscirbe_name, timer_name, and readyFlag_name.
  • subscribe_name — reference to the subscription instance. It keeps a WebScoket connection and receives feedback when events are triggered.
  • timer_name — reference to the variables record time.
  • readyFlag_name — reference to a boolean object for the nodes’ readiness for a new transaction.

Below is the pseudocode of initNode():

Create a new web3 instance. 
Connect to the wallet using private key. 
Get the balance of the wallet to test if everything is successfully setup. If yes: 
  Update connection success message. 
    Start Ethereum subscription on two events: 
      If both node1 and node2 are ready, initiate the first transaction 
      “data” :
      Waiting for pending transactions. When it comes in, calculate the      difference between its initiation and receiving timestampe. This event triggers for all transactions, so unrelated transactions are filtered.


Transaction is crafted and sent out in the pitch function.

function pitch(){
    var web3_pitcher
    updateInfo(pitcher + " is the sender now")
    if(pitcher == "node 1")
        web3_pitcher = web3_1
        web3_pitcher = web3_2
    node1IsReady = false
    node2IsReady = false
    pitch_time = new Date();
        from: walletPublicKey,
        to: walletPublicKey,
        value: transValue,
    }).on('sent', function(payload){
    .on('sending', function(payload){
    .on('transactionHash', function(hash){
    .on('receipt', function(receipt){

When the transaction is finalized, the listening event on.receipt is called. The pitcher is changed and a new transaction is initiated by calling the pitch() function again.

.on('receipt', function(receipt){
        updateInfo("transaction finalised")
        if (!node1IsReady)
            updateInfo("node 1 misses")
        if (!node2IsReady)
            updateInfo("node 2 misses")
        if(pitcher == "node 1")
            pitcher = "node 2"
            pitcher = "node 1"


Read more about the web3.js subscription and transaction methods. Feel free to tweak around with the code and try it out on other networks. Have fun playing.

Happy coding!

One more thing

There is an upgradeable version of the app. It is called: The node-lympics! It supports an unlimited number of nodes and a user-defined number of rounds. It is in GitHub repo too, enjoy!

Power-boost your project on Chainstack

Have you already explored what you can achieve with Chainstack? Get started for free today.


How to buidl your own Web3 dashboard

The tutorial shows you how to build a simple Web3 dashboard using web3.py, google sheets and the data from EVM-based blockchains.

Sethu Raman Omanakuttan
Sep 14

zkEVM and zkRollups Explained

zkEVMs are an attempt to overcome the complexity of implementing an EVM-compatible zk-rollup to ease the integration with current EVM tools and opcodes along with the knowledge derived from it.

Bastian Simpertigue
Nov 3