This article is a practical application of Wei-Meng Lee's May/June 2018 article: Understanding Blockchain: A Beginners Guide to Ethereum Smart Contract Programming (https://bit.ly/3i2fu2C). Of all the most talked about and hyped topics in technology today, blockchain is at the top of the list. Almost without exception, the terms blockchain and crypto currency are used interchangeably for the basic reason that crypto currencies are based on blockchain. But not all blockchains are crypto currencies. It follows that we can discuss and implement blockchain independently of crypto currencies and the peer-to-peer networks within which the blockchains that are central to crypto currencies reside.

None of that resolves the basic question: What are blocks and block chains? What does one look like? Assuming there's some value with them, does blockchain, as a concept, apply to your applications? If so, which problems can be solved with blockchain and how would you implement those solutions in your applications? To answer those questions, you need a tangible proof of concept that makes concrete what has previously been largely an abstract concept. In this article, I tackle these questions with a tangible blockchain proof-of-concept.

Although this article endorses blockchain and despite referring to the crypto currency context, it's not an endorsement of crypto currencies. If there's one thing to take away from this article, it's that although blockchain and crypto currencies are related, they are in fact, quite distinct things. Crypto currencies are still in their infancy and have had their fair share of problems. Whether crypto currencies succeed or fail, that has no bearing on blockchain's efficacy. Editorially speaking, despite the hype, I don't see crypto currencies as a viable long-term thing. That conclusion is based on several factors:

  • Acquisition difficulty (at least in the USA)
  • Association with nefarious activity
  • Uncertain recourse when something goes wrong
  • Not immune to theft
  • General uncertainty and risk concerns

With a few strokes of the legislative pen, crypto currencies as a form of anonymous payment could very well be outlawed. Who are the only people that place a premium on anonymity in financial transactions? Terrorists, money launders, drug dealers, and sex traffickers, to name a few. No legitimate activity requires crypto currency. Despite the issues with crypto currency, those issues should not and do not detract from the benefits of blockchain, the primary enabler of crypto currency.

The code for the proof-of-concept discussed in this article can be found here: https://github.com/johnvpetersen/BlockChain. Note: because this is an active repository, although conceptually consistent, there are some deviations with the implementation details. It's baked enough to discuss, but this project is very much a work in progress. The goal is for you to take what I've started and make it useful for your specific use cases.

A Quick Word on Encryption

For the purposes of this proof-of-concept and article, data at rest isn't encrypted, but it could be. Encryption, however, is a separate concern. If data within a block is encrypted, it's the responsibility of another facility to decrypt and read the block data. Perhaps that would call for an IBlockReader interface? Perhaps that interface has a generic method that accepts a JSON string that was generated from the block's ToString() method?

What Is a Blockchain?

Blockchain isn't a technology or a third-party service for which you need to purchase a seat. You may have heard the phrases “distributed digital ledger” and “smart contracts” in the context of decentralization and crypto currencies like Bitcoin. Blockchain is the foundation of distributed digital ledgers and smart contracts. Distributed digital ledgers have been compared to a spreadsheet that's replicated and synced via nodes that are connected via a peer-to-peer network. A smart contract is a program that manages the protocol coordinating actions that are incident to some legal obligation. An example of such an obligation is the requirement to pay a sum of money in return for a product or service. Blockchain is at the core of it all. The specific programs that implement blockchain and the network those programs reside on, those are things I'm going to put to the side so that I can focus on blockchain itself as a thing you can implement in your applications. To do that, you need to understand what a blockchain is and what problems it's well suited to solve.

As glib as it may sound, it's nevertheless accurate to say that a blockchain is a chain of blocks. That necessarily raises two questions: What are blocks and how are blocks chained?

As Figure 1 illustrates, a block is a data container. A block includes at least two things:

  • Data
  • Data Hash
Figure 1: A block is a data container that also includes the hash of its data.
Figure 1: A block is a data container that also includes the hash of its data.

The block data is composed of the following items:

  • Business data (order, customer, transaction, or anything else)
  • The previous block's hash. If this is the first block, the value is null
  • A nonce value (random integer)

A block's validity is determined by calculating the hash and then comparing that value to the stored hash. If those values are equal, the block is valid. In a blockchain, illustrated in Figure 2, a block's previous hash value must equal the preceding block's hash value. The only exception to the rule is the first block, or what is often referred to as the “Genesis block.” The first block's previous hash value will always be null. Carrying the previous hash forward to the next block is how the blocks are chained together.

Figure 2: Blocks are chained together by carrying forward a block's hash to the next block.
Figure 2: Blocks are chained together by carrying forward a block's hash to the next block.

What Is the Nonce Value, What Is It Used For, and How Does It Relate to Proof-of-Work?

In cryptography, a nonce value is an arbitrary value that's used to generate a hash. In a block, the business data and previous hash values are fixed. The generated hash value for those two things will always be the same. What if the generated hash needs to conform to some specification such as the hash needing to begin with a set prefix? The only way to generate a different hash value is to update the nonce value.

A common way to generate nonce values is via a random number generator. This implies a process that continually loops through a process that generates a new random number, generates the hash, and then compares the generated hash value to a required prefix specification. This process is the proof-of-work concept that's also a core concept in the crypto currency context. Proof of work is the way nodes on a network achieve consensus on whether a block can be added to the chain. Another phrase you may have heard of is “block mining.” The more complex the specification for the hash, the more computing power you need to find the first combination of elements that yields a hash that conforms to the specification so that the block can be added to the chain.

For an internal application, do you need to implement the proof-of-work concept? The answer is no. Nevertheless, I've implemented the concept here because if there's at least some rule for hash complexity and that rule is secret and embedded in your program, it follows that your program - and only your program - can create blocks. In such a case, a block's provenance is certain, which in turn, enhances the blockchain's data trustworthiness. In other words, a rogue process can't create or update blocks in a blockchain.

A related concept to proof-of-work is proof-of-stake. With proof-of-work (PoW), any miner that expends the necessary effort (including energy consumption) can verify a new block. Because of the computing resources required, the energy consumption mentioned is just that: electrical power. Miners get rewards for mining blocks. Those rewards fund the sizeable electrical bills that are incurred with block mining! Proof-of-stake (PoS), on the other hand, goes toward how a subset of miners acquire the right to verify new blocks. This dichotomy between PoW and PoS is one example that illustrates the open questions related to crypto currencies.

Blockchain Benefits

Because a block's data contains the hash for the previous block, this arrangement has at least two profound ramifications:

  • The validity of any one block can be verified. Does the generated hash == the stored hash? We can easily detect whether a block's data has been altered since the hash was generated.
  • The validity of the blockchain can be verified. Does a block's data contain the hash for the previous block?

You often have the need to quickly verify whether a data series is “Valid”. Valid, in this context, means that the data hasn't been altered since hashes were generated. Altering any one block in the chain not only invalidates that block, but it also invalidates every subsequent block and, as a result, the entire chain is invalidated. If, for a given data set, you were called upon to prove whether the dataset has changed, how would you go about handling that task? Is it possible? Would you need to ping off another data source? If you implement blockchain, the task is trivial because a blockchain is capable of expressing whether or not data has been altered since hashes were generated and it can do so within its boundaries without the need for external resources. This can have profound positive performance ramifications for your applications! Therefore, in the crypto currency context, blockchain supports the decentralized digital ledger concept.

Blockchain Use Case: Read-Only Sequential Data

The world isn't a nail and blockchain isn't a hammer! For blockchain, a good use case appears to be one that involves read-only sequential data. Think about bank account transactions. Once a transaction has been logged, it should never be altered. Once an event occurs and has been recorded, there's no going back. In other words, there is no Wayback Machine that allows you to go back in time to change history. If a correction needs to be made, you simply append a new transaction to record the correction. Or put another way, add another block to the chain.

Blockchain applies to any scenario that involves the recordation of an event. Blocks are always added to the end of the chain. In the crypto currency world, this gets a bit more complicated because multiple copies of the blockchain exist on nodes in the network. This is what the distributed digital ledger is all about. Synching up the nodes is the job of a network service like Ethereum. For the purpose of this article and this use case, you don't need to be concerned with a third-party network or the need to synch-up distributed nodes. All you need to be concerned with is with blockchain itself.

With that background, let's review some code!

Implementing a Blockchain in .NET Core

You can find the project code in the following GitHub Repository: https://github.com/johnvpetersen/BlockChain. To fully understand the code, I strongly encourage you to run and step through the tests. Although I used VS Code and .NET Core 3.1, you can easily offload the code to Visual Studio and leverage .NET 4.x if you're more familiar with that environment. The solution, illustrated in Figure 3 is divided between two projects:

  • App: hosts the core blockchain classes
  • Tests: hosts the test fixtures that exercise the blockchain classes hosted in the app
Figure 3: The blockchain implementation is very simple in that it only consists of three classes and one interface.
Figure 3: The blockchain implementation is very simple in that it only consists of three classes and one interface.

As Figure 4 illustrates, a block chain is a simple structure. The way the blocks are chained is also quite simple. The link is a block's hash that's carried forward to the next block's data.

Figure 4: A block chain is a chain of blocks where each block contains data and the data hash. The data is an object that contains a nonce, business data, and, if applicable, the previous block's hash.
Figure 4: A block chain is a chain of blocks where each block contains data and the data hash. The data is an object that contains a nonce, business data, and, if applicable, the previous block's hash.

To implement what's depicted in Figure 4, you'll rely on the following objects:

  • Data: An object to hold a block's data. The data structure includes the nonce, business data, and if applicable, the previous block's hash.
  • Proof of work: An object that uses a specified algorithm to calculate the required prefix for a valid hash.
  • Block: An object to host data. The block object is also responsible for calculating its data hash.
  • Chain: An object to host a collection of blocks. The chain object is responsible for adding a new block to the collection. Part of that operation includes applying the previous block's hash to the new block's data.

Data Class

Listing 1 illustrates the data class implementation. Because the class is generic, it can host your custom class. In addition to your business data, this class also contains the nonce value and the previous block's hash. The following illustrates how to instantiate and verify the data class:

[Fact]
public void CanInstantiateData() 
{
    var sut = new Data<MyData>(0,new MyData(10,"Amount is $10.00"));
    Assert.Equal(10,sut.Value.Amount);
    Assert.Equal("Amount is $10.00", sut.Value.Message);
    Assert.Equal(0,sut.Nonce);
    Assert.True(string.IsNullOrEmpty(sut.PreviousHash));
}

Listing 1: Data Class

using static Newtonsoft.Json.JsonConvert;

namespace BlockChain
{
    public class Data<T> 
    {
        public Data(int nonce, T value, string previousHash = "")
        {
            Nonce = nonce;
            Value = value;
            PreviousHash = previousHash;
        }
        
        public int Nonce { get; private set; }
        public T Value { get; private set; }
        public string PreviousHash { get; private set; }
        
        public override string ToString() 
        {
            return SerializeObject(this);
        }
    }
}

Overriding the ToString() Virtual Method

For each class, take note of how the ToString() method is always overridden. In each case, a class can emit itself as JSON when its ToString() method is invoked. The purpose is to facilitate serialization and de-serialization. All of the classes in this solution are immutable once they've been instantiated. During its lifetime, a blockchain will move from active to inactive states. When inactive, the blockchain only exists as JSON. When active, that JSON string is used to instantiate the blockchain class. In the examples that follow, you will see how the overridden ToString() method is useful.

IProofOfWork Interface

The solution defines an interface, not an implementation for proof-of-work. The reason for the interface is that you don't know at design time what specific rule will be employed to define the required prefix for a hash. The following illustrates this simple interface with one method:

public interface IProofOfWork {
    string GetPrefix();
}

Listing 2 illustrates the implementation of the IProofOfWork interface. The class can be instantiated with a set prefix or with a GUID and an array of integers that specifies which characters in the GUID to use for the prefix.

Note: the longer the prefix, the longer it will take the generate a hash. The following test is an example of how to implement the proof of work class that implements the IProofOfWork interface.

[Fact]
public void CanVerifyPrefixWithParameters() 
{
    var guid = Guid.NewGuid();
    int[] charsToTake = { 0, 5, 10 };
    var expected = string.Empty;
    var sut = new ProofOfWork (guid, charsToTake);
    Array.ForEach (charsToTake, element => 
    {
        expected += guid.ToString().Substring (element, 1);
    });
    Assert.Equal (expected, sut.GetPrefix());
}

Listing 2: Proof Of Work Class

public class ProofOfWork : IProofOfWork
{
    private string _prefix;
    
    [JsonConstructor]
    public ProofOfWork(string prefix) 
    {
        _prefix = prefix;
    }
    
    public ProofOfWork() : this(Guid.NewGuid(), new int[] { 0 }) {}
    
    public ProofOfWork(Guid guid, int[] charsToTake) 
    {
        _prefix = string.Empty;
        if (charsToTake == null || charsToTake.Length == 0)
            return;
            
        Array.ForEach(charsToTake, element =>
        {
            _prefix += guid.ToString().Substring(element, 1);
        });
    }
    
    public string GetPrefix() => _prefix;
}

The implementation above is completely arbitrary. The class's only purpose is to encapsulate code that generates a prefix. That code can be as simple or as complicated as you need it to be. It's also worth noting that IProofOfWork implicates all the SOLID principles:

  • S: Single Responsibility: IProofOfWork supports concrete implementations doing one thing and one thing only. That one thing is to generate the required hash prefix.
  • O: Open-Closed: Because the dependency is on an abstract contract, the entity relying on the abstract contract is open for extension. Extending capability is achieved by way of the concrete class that conforms to the IProofOfWork Interface.
  • L: Liskov Substitution: An entity relying on the IProofOfWork Interface can work with any implementation of that interface.
  • I: Interface Segregation: In this context, you can alter how a required hash prefix is calculated without affecting any other aspect of the solution.
  • D: Dependency Inversion: In this context, the chain and block objects depend on the IProofOfWork interface, not on any specific concrete implementation. At runtime, the chosen concrete implementation that conforms to the interface can be injected into the chain and block classes.

Block Class

The block class joins the data and proof-of-work concepts. To review, the block is a data container and is illustrated back in Figure 1. The block class code is illustrated in Listing 3. Like the other classes, the block class overrides the ToString() method such that the ToString() method leverages JSON.NET's object serialization capability. The serialized block object, as is illustrated in Figure 4, corresponds to the block constructor that's decorated with the JsonConstructor attribute. This is how you can serialize and de-serialize a block. It's important to emphasize that all classes in this solution are immutable. You need a way to hydrate an object while it's in an active state. When inactive and at rest, you need a way to persist state.

Listing 3: Block Class

using System;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using static Newtonsoft.Json.JsonConvert;

namespace BlockChain
{
    public class Block<T> 
    {
        private string _hash = string.Empty;
        private Data<T> _data;
        private string _prefix;
        
        [JsonConstructor]
        public Block(string hash, Data<T> data, string prefix) 
        {
            _hash = hash;
            _data = data;
            _prefix = prefix;
        }
        
        public Block(T data, string previousHash = "", IProofOfWork proofOfWork = null) 
        {
            _prefix = proofOfWork == null ? string.Empty : proofOfWork.GetPrefix();
            var rnd = new Random();
            while (true) 
            {
                var blockData = new Data<T>(rnd.Next(), data, previousHash);
                var result = computeHash(blockData);
                
                if (string.IsNullOrEmpty(_prefix) || result.Substring(0, _prefix.Length) == _prefix) 
                {
                    _hash = result;
                    _data = blockData;
                    break;
                }
            }
        }
        
        string computeHash() => computeHash(_data);
        
        string computeHash(Data<T> data)
        {
            using (var sha256 = SHA256.Create())
            {
                return Convert.ToBase64String(sha256.ComputeHash(Encoding.UTF8.GetBytes(SerializeObject(data))));
                
            }
        }
        
        public override string ToString() 
        {
            return SerializeObject(this);
        }
        
        public string Hash => _hash;
        public Data<T> Data => _data;

        public bool IsValid() 
        {
            var retVal = true;
            retVal = string.IsNullOrEmpty(_prefix) ? retVal : this.Hash.Substring(0, _prefix.Length) == _prefix;
            return retVal && computeHash(_data) == this.Hash;
        }
    }
}

JSON serialization and deserialization is how to achieve that capability and the following test illustrates how to create, serialize, and deserialize a block. The MyData class referenced in the following test is the business object that hosts the business data that's ultimately hosted in the block. The block class has a private method to compute the data hash. Could that functionality be offloaded to its own class and then have the class instance (object) injected into the block? The answer is an unqualified “yes” and would be a worthwhile re-factoring exercise, one that I've left outstanding for you to undertake if you wish. The same is true for how a block generates a hash. As built, that specific functionality is burned into the block class definition. If you want to be able to extend that functionality, that requires the code to be refactored to its own class and then you'd rely on dependency injection to make that functionality available with the block class.

public void BlockCanBeSerializedAndDeSerialized () 
{
    var sut = new BlockChain.Block<MyData>(new MyData(108, "Amount is 108"), null, new ProofOfWork());
    var blockJSON = sut.ToString();
    sut = null;
    sut = DeserializeObject<BlockChain.Block<MyData>>(blockJSON);
    Assert.True(sut.IsValid());
}

Chain Class

The chain class code is illustrated in Listing 4. The two public members to focus on are the Blocks Property and the AddBlock() Method:

Listing 4: Chain Class

using System.Collections.Generic;
using System.Collections.Immutable;
using Newtonsoft.Json;
using static Newtonsoft.Json.JsonConvert;
using System.Linq;

namespace BlockChain 
{
    public class Chain<T>
    {
        private Dictionary<int, Block<T>> _blockChain = new Dictionary<int, Block<T>>();
        private IProofOfWork _proofOfWork = null;\
        public ImmutableDictionary<int, Block<T>> Blocks => _blockChain.ToImmutableDictionary();
        
        [JsonConstructor
        public Chain(Dictionary<int, Block<T>> blockChain, IProofOfWork proofOfWork)
        {
            _proofOfWork = proofOfWork;
            _blockChain = blockChain;
        }
        
        public Chain(IProofOfWork proofOfWork = null)
        {
            _proofOfWork = proofOfWork;
        }
        
        public bool ? IsValid()
        {
            if (Blocks == null || Blocks.Count == 0)
                return null;
                
            if (Blocks.Count(x => ! x.Value.IsValid()) > 0)
                return false;
                
            for (int i = 1; i < Blocks.Count; i++)
            {
                if (Blocks[i].Data.PreviousHash != Blocks[i - 1].Hash)
                    return false;
            }
            
            return true;
        }
        
        public override string ToString()
        {
            var prefix = _proofOfWork == null ? string.Empty : _proofOfWork.GetPrefix();
            return SerializeObject(new
            {
                BlockChain = _blockChain,
                ProofOfWork = prefix
            });
        }
        
        public void AddBlock(T data)
        {
            var previousHash = _blockChain.Count == 0 ? null : _blockChain[_blockChain.Count - 1].Hash;
            _blockChain.Add(_blockChain.Count, new Block<T>(data, previousHash, _proofOfWork));
        }
        
        public Block<T> this[int i] => _blockChain[i];
    }
}
  • Blocks Property: This property is the public version of the internal blocks dictionary. The System Collections Immutable NuGet Package is leveraged so that you can create an immutable version of the internal blocks dictionary. An external entity needs to read the block data in the chain. At the same time, the data needs to be read-only. Although the underlying block objects and the data it contains is read-only, the outer dictionary is mutable. It isn't a concern because the variable that holds that mutable dictionary (_blockChain) is private. The internal dictionary needs to be mutable because blocks may be added. When you refer to the Blocks property, a copy of the _blockChain variable is made. That copy is, however, an immutable dictionary.
  • AddBlock() Method: The AddBlock() method is responsible for taking the block data, taking the block data and computing a hash, and checking to see if the computed hash conforms to the prefix if a required prefix applies. That's why the While loop is in place. The process continues to leverage the block's ability to generate hashes until one is generated that conforms to the prefix. To achieve that capability, the nonce value is updated with another random number. If that didn't happen and given that the other data doesn't change, the same hash value is generated and unless the hash generated conforms to the prefix, you'd never exit the loop. In this context, much of this operation occurs in the block's constructor. In other words, when a block is created, it necessarily means the block's hash conforms to the required prefix if one was specified.

The following is a test that puts everything together. There are two broad scenarios tested: with a proof of work prefix requirement and without. The test, under both scenarios:

  • Creates a new chain
  • Adds three blocks
  • Serializes the chain
  • Nulls the blocks variable
  • Deserializes the chain from JSON
  • Adds a fourth block
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ChainCanBeSerializedAndDeSerialized (bool proofOfWork) 
{
    var blocks = new Chain<MyData>(proofOfWork ? new ProofOfWork() : null);
    blocks.AddBlock(new MyData(108, "Amount is 108"));
    blocks.AddBlock(new MyData(109, "Amount is 109"));
    blocks.AddBlock(new MyData(110, "Amount is 110"));
    
    Assert.Equal(3,blocks.Blocks.Count);
    Assert.True(blocks.IsValid());
    
    var blockJSON = blocks.ToString();
    blocks = null;
    blocks = DeserializeObject<Chain<MyData>>(blockJSON, new ProofOfWorkConverter());
    
    Assert.True(blocks.IsValid());
    Assert.Equal(blockJSON, blocks.ToString());
    
    blocks.AddBlock(new MyData(111, "Amount is 111"));
    
    Assert.Equal(4, blocks.Blocks.Count);
}

Conclusion

Blockchain, independent of the crypto currency context, is a simple and powerful concept. A prime use case for blockchain in a business application is one where there are multiple sequentially created records. The blockchain itself can express whether it's in a valid state. A valid state is one where the computed hash equals the newly generated hash for the data. Assuming nothing changed with the data, the computed and stored hashes are identical. In the crypto currency world, there's the concept of block mining, which involves significant computing power to create a block, Block mining entails the proof-of-work concept. The way the proof of work concept is implemented in this article is through the proof of work class that defines a required prefix. When a block is created with a proof of work requirement, the process of looping and determining whether the new hash conforms to the prefix, which is conceptually the same as block mining in the crypto currency world.

Note: if you require a generated hash to conform to a prefix length that is greater than or equal to 3, you will likely see significant delays when generating blocks. It may very well be that in a business application, there is no need for proof-of-work. That's why the proof-of-work was separated to its own interface.

Finally, the chain class ties the blocks together. Like a block class, the chain class can express its valid state. If any one block is invalid, the entire chain is also invalid. Being able to determine at a glance and without having to ping off another resource like a database whether or not a dataset is valid is a performance enhancement. Although you can't prevent data tampering with 100% success, you can, at the very least, with blockchain, detect when data has been altered. In my opinion, that ability makes blockchain a powerful approach that affords your applications benefits that would otherwise be more difficult to realize. It's important to note that hashing as a means to detect data modifications isn't a new or novel concept, and neither is blockchain!