Loupe: The open source C# ASP.NET Core blockchain explorer

Github

Loupe is an open source C# ASP.NET Core blockchain explorer for Ethereum based crypto coins made using Razor Pages and DotNet Core 2.1. It’s designed to be highly portable and will also eventually power the free hosted blockchain explorers for coins made with the CoinPress cryptocurrency generator. Installation is very simple and requires only downloading and setting execute permissions. By default it will attempt to connect to the RPC port of a local instance of a CoinPress wallet (https://blog.coinpress.cc/coinpress-guide/) so the easiest way is to just run the wallet on Windows first, but if you’re connecting to a CoinPress dedicated server or to the MainNet (or another coin entirely) you’ll need to update Web3Client.cs with the address and RPC port. I used http://gasprice.poa.network:8545 to test it against the MainNet and confirmed it working. Plans are in the works to have all the free CoinPress hosted nodes enable their RPC ports for public use and will probably dovetail with the release of a later version of this software.

This is a very alpha product and is being released early with the aim of showing developers how to read data from the blockchain. For the technical purposes of this project, Nethereum is used to talk to Geth and abstracts much of the heavy lifting involved in querying the blockchain.  Fortunately, Nethereum was just recently updated to support ASP.NET Core 2.1 which really opens up the options on where the program can be hosted. I’ve tested on both Ubuntu 16.04 and Windows Server 2012 without any issues. Nethereum abstracts the Web3/RPC connection in a way that’s very close to the way the Ethereum web3.js works, so if you’re planning on using another language or library that implements it in a similarly faithful way it should be very simple to port the code.

Much of components involving CSS or HTML are out of the scope of this article and I will be covering only important code chunks and information that can be directly extrapolated to other blockchain projects. The explorer is built using Razor Pages which allows for the easy embedding of data, but I won’t really be concentrating on the plumbing of the pages.

Getting started

If you’re rolling your own solution to access the blockchain, it may be helpful to know that I started by creating a new ASP.NET Core project on .net 2.1 SDK. I selected “Web Application” and unchecked the box “configure for https” to avoid certificate errors on wider-scale deployments as this program is going to end up on a lot of servers identified only by IP. For individual deployment, it’s up to your personal preference. It is important, however, to get the version of DotNet right for the Nethereum library. I tried running the DotNet 3.0 preview but it broke Nethereum for me when I tried it.

Install the Nethereum.web3 package into your project if you’re rolling your own solution. A few things I make regular use of with Nethereum:

  • Nethereum.web3: Handles the connection to Geth. It allows you to grab most all information from the blockchain in just a few commands or less.
  • Namespace Nethereum.Hex.HexTypes: A ton of the Nethereum data comes back using the custom type “HexBigInteger”. This return type has a built-in conversion to more manageable units. f you get a returned variable of, say, “returnedData”, you can call “returnedData.Value” to get an automatic conversion to the type “BigInteger” from the System.Numerics namespace which then has an easy conversion to int.
  • Namespace Nethereum.RPC.Eth.DTOs: This will give you the types for the blocks returned from Ethereum, and keeps you from having to define your own classes for the data.

Watch out: as it’s easy to overflow a 32-bit integer with data returned from the blockchain. Keep this in mind when doing type conversions.

Particular parts of code

A lot of this will change as this codebase matures into more production-ready, but for right now there’s a few relatively simple commands that do most of the work. Much of the rest of the code is doing data type changes, handling exception cases, and manipulating the data that comes out of these commands.

//Ethereum MainNet RPC server

//Web3 web3Connect = new Web3("http://gasprice.poa.network:8545"); 

//Local instance of a CoinPress coin node set to default RPC port 3907
//Note that you must change your ServerConfig.json file to allow RPC. I used the following (but beware this opens up most everything)
//{"AdditionalCommandLineArgs":"--shh --rpc --rpcaddr \"0.0.0.0\" --rpcapi \"db, eth, net, web3, personal\" --rpccorsdomain \"*\"","RPCPort":3907,"CacheSize":128,"DataDirectory":"","Port":3906}

//Web3 web3Connect = new Web3("http://localhost:3907"); 

//Note that you must change your ServerConfig.json file to allow RPC. I used the following (but beware this opens up most everything)
//{"AdditionalCommandLineArgs":"--shh --rpc --rpcaddr \"0.0.0.0\" --rpcapi \"db, eth, net, web3, personal\" --rpccorsdomain \"*\"","RPCPort":3907,"CacheSize":128,"DataDirectory":"","Port":3906}

//Local instance of a CoinPress wallet set to default port

Web3 web3Connect = new Web3("http://localhost:8545"); 

There are 3 different connections here, and the first 2 are commented out but there to show how the various connections would be handled. The comments next to each line of code identify where that line of code would connect. This hard-coding of the address is temporary just to get the software working.

The rest of the file is basically wrapping a few lines of code that are doing most of the work.

await web3Connect.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(blockNo);

This command uses the web3Connect object we just created and returns a block of the Nethereum library type BlockWithTransactions. This command can get you most of the data you want off the actual written blockchain itself if you know the block number. It’s going to want that block number field in its own HexBigInteger format, however, so if you’re looking for a way to convert that you could use:

await web3Connect.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new HexBigInteger(new BigInteger(blockNumberAsInteger)));

If you don’t know the block number, you can query it by the hash as well:

await web3Connect.Eth.Blocks.GetBlockWithTransactionsByHash.SendRequestAsync(hash);

There’s another command that allows you to get the blocks with just the transaction hashes and not all of the individual transaction data:

await web3Connect.Eth.Blocks.GetBlockWithTransactionsHashesByNumber.SendRequestAsync(new HexBigInteger(new BigInteger(blockNumberAsInteger)));

You’ll want to use this in situations like the Top 50 page where you’re querying a multitude of blocks at once as otherwise the memory usage of the Web3 objects can skyrocket. This isn’t as noticeable on private blockchains but there’s a huge performance difference against the MainNet.

await web3Connect.Eth.Transactions.GetTransactionByHash.SendRequestAsync(hash);

This grabs a transaction by hash. This is nice as it keeps you from having to iterate through blocks looking at all the transactions in them to find the match.

This next snippet gets you an account balance:

var account = await web3Connect.Eth.GetBalance.SendRequestAsync(hash);
return Nethereum.Web3.Web3.Convert.FromWei(account.Value);

Take note of the Convert.FromWei.(account.Value) part. “account.Value” converts it from the Nethereum type HexBigInteger to just the System.Numeric BigInteger, but if you send this value back you’ll see a much larger number than you were expecting. Convert.FromWei() converts the value that’s returned as the Ethereum order of magnitude moniker “Wei” to the more manageable “Ether” you were probably expecting. This is preferred over the alternative of simply dividing the returned number by 1000000000000000000.

Reminder: Watch your data types as they’re easy to overflow unexpectedly with the data you’re going to get back from the blockchain.

If it wasn’t obvious, these are all async/await commands which means you’ll either need to have your methods setup as async, or you could always force them to work in single-threaded mode by attaching the .GetAwaiter().GetResult() commands to the method call:

web3Connect.Eth.Transactions.GetTransactionByHash.SendRequestAsync(hash).GetAwaiter().GetResult();

If you don’t do either of these, you’ll get a green squiggly line under the code and a fun race condition.

One last bit of code I’d like to point out relates to the search functionality. To be able to have a single search bar up top where you can search on transaction hash, block number, block hash, or address, you need to be able to identify what it is they’re searching for. It’s fairly easy to suss out most of the data:

  • Block numbers are distinguished as being integers.
  • Addresses are hashes that can be distinguished as being exactly 42 bytes.
  • Block and transaction hashes are both 66 bytes, which distinguishes them as hashes but amongst the two of them I’m not aware of a good way to differentiate them just by looking at them.

This chunk of code handles the search cases based on parsing and length, and then hands it off in the case of it being a transaction or block hash:

public static string Decipher(string text)
{
     int blockNumber;
     if (int.TryParse(text, out blockNumber))
     {
             return $"/Block?{text}";
     }

     Utilities.Web3Client w3client = new Utilities.Web3Client();

     switch (text.Length)
          {
             case 42:
                return $"/Address?{text}";
             case 66:
                return $"/{w3client.DeciperTypeOfHash(text).GetAwaiter().GetResult()}?{text}";
             default:
                return "/Index";
            }
        }

To differentiate between transaction hashes and block hashes, I check and see if the 66 byte hash is a valid transaction and then default to it being a block if it’s not. There may be a better way to do this, the exception isn’t handled properly, and it’s possible the order could affect performance as well.

public async Task<string> DeciperTypeOfHash(string hash)
        {
            var trans = await GetTransaction(hash);

            try
            {
                if (!string.IsNullOrEmpty(trans.From))
                {
                    return "Transaction";
                }
            }
            catch (Exception)
            {
                
            }
            return "Block";
        }

Making it work with your CoinPress node

At the time of writing, CoinPress downloadable server nodes come with RPC disabled. Although the public CoinPress servers will soon support RPC connections, the downloadable ones will likely remain opt-in for security reasons.

I edited the ServerConfig.json in the node directory to:

{"AdditionalCommandLineArgs":"--shh --rpc --rpcaddr \"0.0.0.0\" --rpcapi \"db, eth, net, web3, personal\" --rpccorsdomain \"*\"","RPCPort":3907,"CacheSize":128,"DataDirectory":"","Port":3906}

NOTE! This opens your RPC server to anybody. There are a bunch of options (https://github.com/ethereum/wiki/wiki/JSON-RPC) you can configure. It’s not recommended to use this exact configuration in production, but makes it much easier to work with in testing.

Known shortcomings:

  • There’s no caching of results. This likely will not scale well.
  • The Top 50 page is currently hard coded to “top 10” due to the lack of caching.
  • The Top 50 page also appears to have some sort of memory leak. Requesting the page a bunch of times causes the memory use to rise and not release. It might be related to garbage collection.
  • Error handling isn’t perfect. There’s still an occasional exception on a page when it’s having trouble hitting the RPC host.
  • A lot of the formatting can use some work.
  • You may run into an issue where you need to expose IIS Express to more than just localhost if you’re just running it straight out of IIS Express/Visual Studio. If you don’t do this, you may only be able to get to the blockchain explorer from the host it’s running on.

Leave a Reply

Your email address will not be published. Required fields are marked *