Part II: Solving Real-World Problems with Blockchain

Part II Editor’s note: This is the second post in a three-part series that investigates how a distributed application using blockchain could be applied to a practical problem facing the payments space. You can read the first part of the series here.

Payment Directory Distributed Application

In my last post, I said we are going to build a distributed application (DApp) that will solve a real-world problem. In this post, I will describe what that problem is and some of the practical constraints we must consider while solving it. This problem happens to exist in the payments industry where Levvel is actively engaged with many clients.

Describing the Problem

All current Person-to-Person (P2P) payment offerings use a directory that links a customer and their bank account number to an easy-to-use identifier (or alias), such as a mobile phone number or email address. These directories are unique for each P2P solution like PayPal, Venmo, Zelle and PopMoney; they are not shared. The payment directory holy grail would be a centralized, shared, common directory that might include the following features:

  • Not hosted by a single organization
  • Independently accessible by each financial institution (FI) participating in the shared directory, with shared maintenance and upkeep
  • Used for all directory-based payments including P2P, business-to-business (B2B), and other permutations
  • Will always be available to active participants, even if other participants are offline
  • Accessible with read-only access for non-bank payment companies for identification and verification (ID&V) purposes

A decentralized application that leverages a smart contract(s) might offer the perfect solution for the ubiquitous payment directory described above. The smart contract would allow authorized participants to add, delete, and modify alias data that is stored in the blockchain, verified by the blockchain miners, and transmitted to all participating blockchain nodes.

Payment Directory Practical Considerations

For real-world payment directories, each FI is required to verify the customer’s ownership of the alias prior to linking it to their deposit account. Since the FI is responsible for knowing their customer and hosting customer accounts, it is only natural that they are the only authorized participants who can add or delete aliases in the directory. Companies like PayPal and Venmo could use the directory to discover what FI account number the customer has registered with the alias so they can transfer money from their internal account to the customer’s FI account when asked to do so.

For this application, the following practical considerations are assumed:

  • Only FIs hosting customer accounts are authorized to add or delete aliases linked to that account
  • The FI registering an alias must verify that the customer owns the alias and linked account
  • For alias transfers (i.e. transfer to another FI), the owning FI must delete the alias before it can be added by the new FI. This makes the contract simpler.
  • Directory participants can view the directory contents to see if an alias is registered, and what FI has registered it, but the associated account numbers are encrypted.
  • All FI public keys will be available to clearing organizations to decode the encrypted account numbers.
  • A trusted administrator or committee will authorize (e.g. add or delete) the blockchain node address for participating FIs. This might seem counter-intuitive for a public blockchain, but is reasonable for a private or consortium blockchain network.

Blockchains are classified as public, private, or a consortium, which is a hybrid of the two. The actual blockchain used to implement our DApp is irrelevant from a security perspective since we will build security into the contract itself. Practically, a distributed payment alias directory is best suited for a consortium blockchain network since it is not intended for a wide audience of participants.

Payment Directory Smart Contract

The term “smart contract” makes a lot of sense in the context of writing applications that verify certain conditions exist prior to initiating a payment based upon those conditions. However, blockchain can be used for much more than that. In fact, blockchain software now has an integrated virtual computer built in that can execute instructions that store and manipulate data as well as send and receive payments. Blockchain can be thought of as a distributed operating system (OS), and smart contracts are just a computer program that runs on each blockchain node. The Ethereum Virtual Machine (EVM) is the “operating system” that facilitates this magic, and the Solidity computer language is the software we will use to create our payment directory application smart contract.

Tools of the Trade

Let’s get started writing our DApp. We could open our favorite editor and start writing code but as we all know; smart developers are lazy! Why make life harder than it already is? This is where DApp development tools like Truffle and Embark come to the rescue. I haven’t tried Embark or other related tools yet, but I used Truffle for this application, and it is awesome!

Like it or not, if you are going to develop DApps for Ethereum, you better warm up to Node.js and Javascript programming—Truffle is no exception. It installs using the Node Package Manager (npm) and leverages the Node module ecosystem for creating, building, deploying, and debugging DApp applications. Moreover, Truffle uses the popular Mocha testing framework for automated Javascript testing and Chai for testing assertions, both tools making it easier to write contract unit tests. You can create DApps using other programming languages too, but a search of the Internet for blockchain develop tools will yield significantly more Javascript-based tools than any other language.

Once you run the truffle init command, you will have a project structure with the following directories complete with sample files:

Table

The following shows the project directory structure for my simple Solidity contract complete with the contracts, deployment files, and a unit test file:

Directory Data Structure

Before we write the contract, we must design the directory data structure for our simple DApp. The Solidity language (and EVM) will impact your data structure design because the language data types are limited (i.e. integers, bytes, address, arrays, maps, and structs). Forget using your favorite relational database tool—these limitations were my first challenge to writing the app. EVM data management and storage is one concept that a DApp developer will need to master. I suggest reading the Ethereum Application Binary Interface (ABI) document for starters.

Working within the given EVM constraints, the data stored on the blockchain is logically structured like the following JSON representation:

{
    "Banks": {
        "0xdbfc61a3657fc9c70356bc93d54e4a23081b0d24": {
            "name": "National Bank",
            "routing": "012345678"
        },
        "0x808e096baf29663de55fb8f7b2c23b3cf8f176dd": {
            "name": "Pine Tree Bank",
            "routing": "876543210"
        }
    },
    "Owners": {
        "id": "123456789",
        "name": "Jane Doe",
        "aliases": {
            "jane.doe@gmail.com": {
                "type": "email",
                "bankAddress": "0xdbfc61a3657fc9c70356bc93d54e4a23081b0d24",
                "account": "123456789"
            },
            "7044519990": {
                "type": "phone",
                "bankAddress": "0x808e096baf29663de55fb8f7b2c23b3cf8f176dd",
                "account": "987654321"
            }
        }
    }
}

Show Me the Code

Now it’s time to write some code. I found that Microsoft Visual Studio Code (VS Code) makes a great editor to use for all the right reasons: it’s free, available for your favorite operating system, and provides great Javascript development support. As a bonus, a Solidity extension is available that syntax highlights your code and compiles from within the editor. Life is good!

The following is a listing of the Solidity smart contract that was created to support the new distributed payment directory:

pragma solidity ^0.4.2;

/// @title Simple Alias Directory
/// @author Jim Boone
/*
  Demonstration payment alias directory distributed application 
  solidity contract

    jim.boone@levvel.io
    Copyright: Levvel, LLC
    http://levvel.io
*/
contract AliasDirectoryContract {

    // Data structure that represents a bank or financial institution 
    struct Bank {        
        string name;
        string routingNumber;
        address nodeAddress;
    }

    // Data structure that represenets an alias
    struct Alias {
        uint ownerId;
        string alias;
        bool email;
        string accountNumber;
        address owningFI;
    }

    // Data structure that represents the owner of an alias
    struct Owner {
        uint id;
        string name;
        mapping (string => Alias) aliases;
    }

    /*
     *  Node address that is responsible for managing the banks
     *  that are permitted to add aliases to the directory
     */ 
    address public adminstrator;

    // State variable map that stores authorized network Banks
    mapping(address => Bank) public banks;

    // State variable map that stores alias owners
    mapping(uint => Owner) public gOwners;

    // State variable map that stores alias owners
    // Note: the alias string value is convered to a hash prior to storing
    mapping(bytes32 => uint) public gHashedOwnerIds;

    // State variable map that stores customer's aliases
    mapping (string => Alias) gAliases;

    // State variable the is used to generate owner ID numbers
    uint ownerNumber = 100;

    // Event that is broadcast whenever an alias is created
    event AliasCreated (string alias, bool email, string accountNumber,
         string name, address owningFI);

    // Event that is broadcast whenever an alias is deleted
    event AliasDeleted (string alias, bool email, string accountNumber,
         string name, address owningFI);

    // Event that is broadcast whenever an owner is created
    event OwnerCreated(string name, uint id);

    // Event that is broadcast whenever a bank is authorized
    event AuthorizedBankAdded(string bankName, string routingNumber, address nodeAddress);

    // Event that is broadcast whenever an authorized bank is deleted
    event AuthorizedBankDeleted(string bankName, string routingNumber, address nodeAddress);

    // Function modifier that can be used to guard against unauthorized
    // users from participating in the directory
    modifier authorized {
        if (msg.sender == address(0x0))
            throw;
        _;
    }

    // Contract constructor function. The node creating the contract
    // will be the adminstrator
    function AliasDirectoryContract(){
        adminstrator = msg.sender;
        banks[adminstrator] = Bank("Adminstrator Bank","not_set",adminstrator );
    }

    // Add new authorized bank nodes
    function addAuthorizedBanks(string bankName, string routingNumber, address nodeAddress) authorized  {
        banks[nodeAddress] = Bank(bankName, routingNumber, nodeAddress); 
        AuthorizedBankAdded(bankName, routingNumber, nodeAddress);

    }

    // Remove authorized bank nodes
    function deleteAuthorizedBanks(address nodeAddress) authorized {
        delete banks[nodeAddress];
        Bank bank = banks[nodeAddress];
        AuthorizedBankDeleted(bank.name, bank.routingNumber, bank.nodeAddress);       
    }

    // Create new alias for the distributed directory 
    function newOwnerAndAlias(string name, string alias, bool email, string accountNumber ) authorized {
        uint ownerId = newOwner(name);                         
        newAlias( ownerId,  alias,  email,  accountNumber);
    }

    function getOwnerId(string name) constant returns (uint) {
         return gHashedOwnerIds[sha3(name)];
    }

    // Creates a new Owner
    function newOwner(string name) authorized returns (uint ownerId){
        ownerId = ownerNumber++;
        gHashedOwnerIds[sha3(name)] = ownerId;
        gOwners[ownerId] = Owner(ownerId, name);
        OwnerCreated(name, ownerId);
    }

    // Create new alias for the distributed directory 
    function newAlias(uint ownerId, string alias, bool email, string accountNumber ) authorized {
        // Ensure that the alias doesn't currently exist in the directory
        if (gAliases[alias].owningFI != address(0x0)){
            throw;
        }
        // Grab the owner
        Owner aliasOwner = gOwners[ownerId];

        // Create the new alias data structure
        gAliases[alias] = Alias(ownerId, alias, email, accountNumber, msg.sender);

        // Assign the alias to the owner alias map
        aliasOwner.aliases[alias] = gAliases[alias];

        // Broadcast the alias creation event
        AliasCreated(alias, email, accountNumber, aliasOwner.name, msg.sender);
    }

    // Delete the alias from the directory
    function deleteAlias(string value) authorized returns(bool) {

        Alias alias = gAliases[value];
        if(alias.owningFI != msg.sender){
            throw;
        }

        // Grab the owner
        Owner aliasOwner = gOwners[alias.ownerId];

        // Broadcast the alias deletion event
        AliasDeleted(value, alias.email, alias.accountNumber, aliasOwner.name, alias.owningFI);
        delete gAliases[value];
        return true;
    }

    // Returns details for the alias
    function getAlias(string alias) constant returns (bool email, 
                            string accountNumber, string name, uint ownerId, address owningFI) {
        Alias aliasData = gAliases[alias];
        email = aliasData.email;
        accountNumber = aliasData.accountNumber;
        name = gOwners[aliasData.ownerId].name;
        owningFI = owningFI;    
        ownerId = aliasData.ownerId;    
    }

    // Remove the contract from the blockchain and send it's ether to the contract owner
    // This will destroy the directory
    function destroyDirectory(){
        if(msg.sender == adminstrator){
            selfdestruct(adminstrator);
        }
    }
}

There are several key concepts that a DApp developer must be aware of when interacting with a contract. Since the Ethereum web3.js Javascript library is typically used by clients to call contract functions, responding to these concepts will be reflected in the client code.

Call Functions

A call is a local invocation of a contract function that doesn’t broadcast anything to the other blockchain nodes. It is essentially a read-only operation that doesn’t consume any Ether and discards all state changes when complete. From a client’s perspective, function calls are synchronous and return immediately. The Solidity constant keyword is used to denote read-only functions, as seen in the code above. The web3.eth.call API is used to call functions.

Transactions Functions

Transaction calls can change the state of the blockchain. They are broadcast to the network, processed by the miners, and the operation results are published to the blockchain if they are valid. Because they require work by the miners and run the risk of rejection or running out of gas, they are asynchronous calls that will only return a transaction ID. The transaction will roll back the state of the blockchain if an exception is thrown by the contract during execution. The web3.eth.sendTransaction API is used to make function calls that result in a transaction.

Events

Because transactions are asynchronous, the client must register a callback that listens for events, and the contract writer must generate an event within the function to broadcast the result. Events are also written as part of the transaction receipts and are stored alongside the blockchain as historical data. Events (or Logs) are not part of the blockchain and are not included in the consensus process, but they are written to each node and retrievable by clients.

Testing the Contract

The easiest way I found to test a Solidity contract is using the browser-solidity web site. This is a web-based application that allows you to upload your contract, compile it, and manually interact with it by executing contract functions. It also displays all the events that are generated by the contract. You can also see detailed EVM runtime bytecode and the assembly language generated by compiling the contract. Looking at these details gave me flashbacks of the 8080-assembly language class that I took and reminded me of how thankful I am for higher-level programming languages!

The browser-solidity screenshot below shows the AliasDirectoryContract Solidity contract loaded and compiled along with the results of several function calls. This is one tool I highly recommend playing with to get a feel for what is going on under the hood.

Browser Solidarity

Contract Unit Tests

Debugging your smart contract using browser-solidity is great, but as a professional software engineer, I expect developers to write repeatable unit tests, and I am not exempt. We will write a unit test that exercises the major functionality of the contract. The following is a listing of AliasDirectoryTest.js, a Mocha-based unit test file with Chai assertions.

contract('AliasDirectoryContract', function (accounts) {

    // Note: account[0] is the contract owner
    var authorizedBank = accounts[1];
    var unauthorizedBank = accounts[2];

    var directory;

    // Grap a reference to the directory contract and add an authorized user prior to the tests
    before("Add authorized bank and user", function () {
        directory = AliasDirectoryContract.deployed();

        return directory.addAuthorizedBanks("Authorized Bank", "053000196", authorizedBank).then(function (tx_id) {
            // Add a new user
            return directory.newOwner("Sam Smith", { from: authorizedBank }).then(function (tx_id) {
                return;
            })
        });
    });
    it("should add a new user and alias the directory", function () {
        directory.newOwnerAndAlias("John Doe", "7043869776", false, "bof12345").then(function (tx_id) {

            return directory.getAlias.call("7043869776").then(function (results) {
                assert.isFalse(results[0], "Should be false");
                assert.equal(results[1], "bof12345");
                assert.equal(results[2], "John Doe");
                assert.equal(results[3], 101);

                return directory.getOwnerId.call("John Doe").then(function (id) {
                    assert.equal(id, 101);
                    return directory.getOwnerId.call("Sam Smith").then(function (id) {
                        assert.equal(id, 100);
                    });
                });
            }).catch(function (err) {
                assert.fail(0, 1, err);
            });
        }).catch(function (err) {
            assert.fail(0, 1, err);
        });
    });
    it("should fail to let an unauthorized bank add any bank to authorized list", function () {
        directory.addAuthorizedBanks("Bogus Bank", accounts[4], { from: unauthorizedBank }).then(function (tx_id) {
            assert.fail(0, 1, "This should fail");
        }).catch(function () {
            // An exception was thrown as expected
        });
    });
    it("should ensure that only the bank that owns the alias can delete it", function () {
        directory.newAlias(100, "testuser@test.com", true, "bof12345", { from: authorizedBank }).then(function (tx_id) {

            return directory.deleteAlias("testuser@test.com", { from: accounts[0] }).then(function (tx_id) {
                assert.fail(0, 1, "This should fail");

            }).catch(function (err) {

                return directory.deleteAlias("testuser@test.com", { from: authorizedBank }).then(function (tx_id) {
                }).catch(function () {
                    assert.fail(0, 1, "This should not fail");
                });
            });
        });
    });
});

Now that we have a unit test, we use the truffle test command to run it. Here is the output from our first attempt:

Truffle test

D’oh! This is an exception. It looks like the unit test is attempting to connect to an Ethereum client. We didn’t have to do anything special when using browser-solidity. How do you run unit tests without deploying the contract to a blockchain EVM? Truffle will deploy the contract for you, but we need an Ethereum blockchain client to deploy it to.

Once again, the development community solved this problem for us by writing testrpc, a Node-based application that simulates a full Ethereum client. All you need to do is fire up testrpc in one terminal window and run the unit tests in the other window. When you start testrpc, it will generate 10 accounts and their associated private keys for testing. The following shows the results of testrpc waiting for interactions:

Now we can try to run the truffle test command again. The command will compile all contracts in the build tree, deploy them to the client, and run the tests. The testrpc screen will scroll with output while the test is running. Finally, the test is done! The output is shown below:

This is great—all the tests have finally passed! Writing the unit test was harder than I expected, because I had to wrestle with Javascript promises to know when all the contract transactions had completed. I’m sure this will be much easier for you Javascript developers out there.

Don’t Run Out of Gas

Previously, I casually threw out the concept of a transaction function “running out of gas”. What is that all about? I don’t intend to dive deeply into it here, but gas is essentially the amount of Ether—the Ethereum currency—that it costs to mine the result of the transaction. Because Ethereum uses a Proof-of-Work (POW) algorithm to reach consensus for each new block in the chain, you must pay the “miner” who finds the solution first for his hard work each time a transaction occurs. After all, the miner has bills to pay, too! The short of it is, each transaction submitted to the blockchain will cost you gas money (Ether). If you write sloppy, inefficient code, then it will cost you even more of your hard-earned gas money. We won’t worry about how you acquire Ether now—that will be covered in the next post.

If you want to learn more about POW and Proof-of-Stake (POS) mining and how Ethereum is planning two switch from POW to POS, this link is a great resource.

That’s a Wrap

A lot of mileage was covered in this blog post, so I will stop here. What we still need to do is write a client that interacts with the contract to build a simple directory, work to earn some gas money, and finally deploy the contract to a real Blockchain network. Until then, I will see if I can earn enough Ether to fund the journey.

Jim Boone

Jim Boone

Principal Consultant

Jim has over 25 years of experience in systems engineering, systems management, software design/development, and payment software systems across the power generation, health care, and banking industries.