October 2019

Volume 34 Number 10

[Blockchain]

DevOps for Blockchain Smart Contracts

By Stefano Tempesta | October 2019

Blockchain has emerged from the shadow of its cryptocurrency origins to be seen as a transformative data technology that can power the next generation of software for multi-party enterprise and consumer scenarios. With the introduction of blockchain technology in enterprise software development, organizations are asking for guidance on how to deliver DevOps for blockchain projects. This article looks at core aspects of DevOps with a focus on best practices and tools to incorporate continuous delivery, continuous improvement and infrastructure as code into the development of smart contracts for blockchain solutions.

Smart Contract Design

Blockchain applications are often designed to handle financial transactions, track mission-critical business processes, and maintain the confidentiality of their consortium members and the customers they serve. Software faults in these areas might, in extreme cases, represent a significant risk to an organization. As a result, blockchain applications usually demand more rigorous risk management and testing strategies than traditional software applications.

A popular approach is to look at smart contract design much as you’d look at microservice design: Decompose the solution into its core entities and the processes that act on those entities, then develop discrete, composable smart contracts for each entity and process so they can evolve independently over time. A good starting point is my September 2018 article, “Architect Blockchain Applications as Microservices” (msdn.com/magazine/mt829754).

Common practices for writing robust smart contracts include:

  • Fail as early and visibly as possible. A simple yet powerful programming good practice is to make your code check for preconditions and throw exceptions when they’re not met.
  • Structure your function code. As an extension of the fail-early principle, a good practice is to structure all your functions as follows: first, check all the pre-conditions; then, make changes to your contract’s state; and, finally, interact with other contracts.
  • Favor requests for payments (pull) over using the send function (push). In Ethereum, every ether transfer implies potential code execution. The receiving address can implement a fallback function that can throw an error. Thus, you should never trust that a send call will execute without error.
  • Write simple, modular code. Security comes from a match between your intention and what your code actually allows to occur. This is very hard to verify, especially if the code is huge and messy. This practice is applicable to almost all software systems, but it’s particularly important for blockchain.
  • Write tests. Writing tests is a lot of work, but will save you from regression problems.

Let’s look at a few examples of code smells in smart contracts—and how best to deal with them. I’ll use Solidity, a C-family language designed for use with Ethereum contract systems. The SupplyChain smart contract in Figure 1 shows a function that, given the name of a party in a supply chain managed on blockchain, returns its state as a number. States for all parties involved are stored in the partyStates map.

Figure 1 SupplyChain Smart Contract with Silent Failing Example

contract SupplyChain {
  uint constant DEFAULT_STATE = 1;
  mapping(string => uint) partyStates;
  function getPartyState(string memory name) public view returns (uint) {
    if (bytes(name).length != 0 && partyStates[name] != 0) {
      return partyStates[name];
    }
    else {
      return DEFAULT_STATE;
    }
  }
}

The getPartyState function checks the input for a valid length of the name variable, and that a party with that name exists, before returning the party’s state. Otherwise, it returns the default state, defined as a global constant. This code works perfectly and it never fails. Except, it does. The problem is, if those conditions aren’t met, a default value is returned. This could hide an error from the caller. If errors are hidden, they can propagate to other parts of the code and cause inconsistencies that are difficult to trace. Here’s a more correct approach:

function getPartyState(string memory name) public view returns (uint) {
  if (bytes(name).length == 0) revert("Invalid name.");
  if (partyStates[name] == 0) revert("Name not found.");
  return partyStates[name];
}

This version of getPartyState also checks for conditions before returning the desired value, but it shows a desirable programming pattern—it separates precondition checks and deals with each fail individually. Note that some of these checks, especially those depending on internal state, can be implemented via function modifiers. Modifiers can be used in Solidity to change the behavior of functions, and, for example, they can automatically check a condition prior to executing the function. Using modifiers, the getPartyState function would be implemented as in Figure 2.

Figure 2 Using Function Modifiers in Solidity to Check for a Contract’s Preconditions

function getPartyState(string memory name)
public view nameIsValid(name) nameExists(name) returns (uint) {
  return partyStates[name];
}
modifier nameIsValid(string memory name) {
  require(bytes(name).length == 0, "Invalid name.");
  _;
}
modifier nameExists(string memory name) {
  require(partyStates[name] == 0, "Name not found.");
  _;
}

Two modifiers are defined: nameIsValid and nameExists. The getPartyState function applies these two modifiers as part of its declaration. The underscore symbol in the modifier is where the body of the function is inserted. This means that if the condition is met—by passing the require clause—the function is executed, otherwise an exception is thrown.

Another typical code smell happens when payments are involved. Figure 3 shows a bid function that calls the send function to transfer value to another address. But the call to the send function happens in the middle of a function, which is unsafe because send can trigger the execution of code in another contract. Now, imagine someone bids from an address that simply throws an error every time someone sends money to it. What happens when someone else tries to outbid that? The send call will always fail, bubbling up and making bid throw an exception. A function call that ends in error leaves the state unchanged as any changes made are rolled back. That means nobody else can bid, and the contract is broken.

Figure 3 Unsafe Payment Calling the Send Function Directly

address payable highestBidder;
uint highestBid;
function bid() public payable {
  if (msg.value < highestBid) revert("Value lower than current bid.");
  // Return bid to previous winner
  if (highestBidder != address(0)) {
    if (!highestBidder.send(highestBid)) revert("Payment failed.");
  }
  highestBidder = msg.sender;
  highestBid = msg.value;
}

A better pattern is to separate payments into a different function and have users request funds independently of the rest of the contract logic. The code refactoring in Figure 4 shows how to implement a transfer request (pull) that has to be initiated by the requestor.

Figure 4 Implementation of a Pull Payment Using the Transfer Function

mapping(address => uint) refunds;
function bid() public payable {
  if (msg.value < highestBid) revert("Value lower than current bid.");
  if (highestBidder != address(0)) {
    refunds[highestBidder] += highestBid;
  }
  highestBidder = msg.sender;
  highestBid = msg.value;
}
function withdrawBid() external
{
  uint refund = refunds[msg.sender];
  refunds[msg.sender] = 0;
  msg.sender.transfer(refund);
  refunds[msg.sender] = refund;
}

This time, I use a map to store refund values for each outbid bidder, and provide a function to withdraw their funds. If there’s a problem in the send call, only that bidder is affected. This is a simple pattern to implement, so remember, when sending ether tokens, favor pull over push payments.

These are just a few of smart contract design patterns you should keep in mind to write more robust code. How can you make this easier? I frequently use OpenZeppelin (openzeppelin.org) to build simple and robust blockchain code. OpenZeppelin is a library for secure smart contract development on Ethereum and other blockchains. It provides implementations of standards like ERC20 and ERC721, which you can deploy as is or extend to suit your needs, as well as Solidity components to build custom contracts and more complex decentralized systems.

OpenZeppelin features a stable API, which means your contracts won’t break unexpectedly when upgrading to a newer minor version. The OpenZeppelin SDK documentation (docs.openzeppelin.com/sdk) explains how to build a project using this framework, how to upgrade contracts, how to share packages for other projects to reuse, how to vouch for the quality of a package, and how to use the JavaScript libraries to operate the project in a CI/CD pipeline. If you’re writing a smart contract that implements a token-based payment system, OpenZeppelin provides all the necessary base contracts you need to get started, and the framework for automatic deployment on the Ethereum testnet. All you need to do is to import the OpenZeppelin base contracts and extend them through inheritance:

pragma solidity ^0.5.0;
import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol';
import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Mintable.sol';
contract MyTokenContract is ERC721Full, ERC721Mintable {
  constructor() ERC721Full("MyTokenContract", "MYTKN") public {
  }
}

Generating Test Data

The central concepts and practices to develop for blockchain solutions are the same for DevOps as for any other type of software project. Engineers work on a local branch of the source code of smart contracts and decentralized applications (dapps), develop new capabilities or apply fixes to that code, test those changes (hopefully!), and submit increments of work into a source control system and build of the solution. The request to combine code into a common branch may require gated endorsements, depending on the success of different types of tests.

A proper release pipeline would then deploy the combination of smart contracts or dapp code to one or more system environments. As with non-blockchain projects, these environments can be provisioned with an infrastructure as code (IaC) approach. IaC automates the deployment of system environments to ensure consistency of components, topology and configuration, and to avoid variations that may occur by applying manual changes directly to a system. Although the core concepts are the same, the decentralized and distributed nature of blockchain applications and the immutability of the digital ledger create a set of blockchain-specific considerations that should be assessed when defining a DevOps approach for blockchain solutions.

One of the first challenges encountered when testing smart contracts is the generation of sample data. Populating test data in a blockchain is more complex than in other types of data stores. In a relational database, for example, a script could create database tables and pre-populate them with INSERT statements written in SQL.

Populating blockchain with scripts, however, is more complicated. A blockchain is the result of blocks of transactions connected in a chronological sequence. Individual transactions are periodically grouped in blocks and appended to the last node in the sequence as a result of some work to validate a block among all nodes in the blockchain network. A valid block is then broadcast to the network; that is, it’s propagated to all active nodes for storing inside their own copy of the digital ledger.

Automatically populating a blockchain with scripts can be done, but it requires coordination and sequencing of transactions and should be done in the context of the multiple parties involved. An emerging trend to achieve this result is to create a snapshot, referred to as a “fork,” of an existing blockchain. This can often deliver the same result without the complexity of building a system to create synthetic transactions to populate the ledger.

Forking refers to any divergence in a blockchain network, temporary or permanent. Very simply, forking is when a blockchain splits into two branches. It can happen as a result of a change in a consensus algorithm or other software. Depending on the nature of the change, the fork can be categorized as either a hard fork or a soft fork.

Figure 5 shows how a hard fork works. A hard fork is a permanent divergence from the previous version of the blockchain, and nodes running previous versions will no longer be accepted by the newest version. A hard fork is a radical change to the protocol that makes previously valid blocks or transactions invalid. Any transaction on the forked (newer) chain will not be valid on the older chain. All nodes and miners will have to upgrade to the latest version of the protocol software if they wish to be on the new forked chain. This essentially creates a fork in the blockchain: one path that follows the new, upgraded blockchain, and one path that continues along the old blocks.

Hard Fork on Blockchain
Figure 5 Hard Fork on Blockchain

A soft fork is when a change to the software protocol keeps it backward compatible, as illustrated in Figure 6. The new forked chain will follow the new rules and will also honor the old rules. The original chain will continue to follow the old rules.

Soft Fork on Blockchain
Figure 6 Soft Fork on Blockchain

With this difference in mind, you can decide your forking strategy depending on requirements for introducing new data and new rules that aren’t compatible with the past blocks (a hard fork), or work with some sort of compatibility that may fit better in a soft fork.

Good DevOps processes attempt to eliminate or minimize the difference between test environments and production. In an enterprise that uses a private or consortium topology for its blockchain network, forking can be achieved with tools like Ganache CLI, a command-line utility that’s part of the Truffle suite of Ethereum development tools (bit.ly/2Yab0ld). You simply point Ganache at the RPC endpoint of the node you wish to fork, and it will do the work for you. For an Ethereum deployment on Azure, here’s an example command line that forks the Ethereum blockchain from a specified block number:

ganache-cli --fork https://<mynetwork>:8545@<block_no>

Once a fork is generated with Ganache, a local network is exposed on port 8545, which will refer back to the original chain for reads of state that existed prior to the fork. Irrespective of the length of the chain, there’s no apparent delay when the forking happens, as the new branch starts from the last block on the existing chain. Because this new chain is meant for testing purposes, you can make transactions on it without spending real ether or changing the existing chain’s state. Moreover, you can reset the chain back to its original state and dispose of all the test blocks.

Deploying a Smart Contract

An implementation of a DevOps strategy for a permissioned blockchain solution should include local development and testing, testing at the private network or consortium level, and testing by blockchain members for member-specific applications. Visual Studio Code with the Azure Blockchain Development Kit for Ethereum extension (bit.ly/2Yc1ZYX ) or the Truffle Suite (trufflesuite.com) are two tools that can help blockchain developers to automate testing of smart contracts at different levels.

The Azure Blockchain Development Kit extends to build and deploy smart contracts on Ethereum ledgers. With this dev kit, you can generate micro­services from smart contracts, publish smart contract event data to an off-chain database or service bus, and perform basic contract management such as scaffolding out a contract and compiling and deploying it to a local Azure Blockchain Service or public network. This extension for VS Code works seamlessly with Azure Blockchain Service. You can provision a new blockchain service in Azure directly from VS Code. Just select the Create Azure Blockchain Service item in the AZURE BLOCKCHAIN panel, as shown in Figure 7, and you’re ready to go. Without leaving VS Code, you can provision a new blockchain consortium in a few steps, or join an existing one.

Create an Azure Blockchain Service Consortium from Within VS Code
Figure 7 Create an Azure Blockchain Service Consortium from Within VS Code

Once a blockchain service is available, you can start developing a smart contract. The blockchain dev kit leverages Truffle Suite to scaffold, build and deploy contracts on Azure Blockchain Service. Start typing “Azure Blockchain: New Solidity Project” in the command palette, choose a folder and a name for your smart contract. Your newly scaffolded directory includes a simple contract and all the necessary files to build and deploy a fully working contract to the Azure Blockchain Service. Apply any necessary changes to the contract, and when ready, select your contract Solidity (.sol) file, then right-click and choose Build Contracts.

Once the contract is compiled, it, along with the contract metadata—such as the contract application binary interface (ABI) and the bytecode—will be available in the smart contract .json file in the “build” directory. At this point you can deploy your smart contract to the Azure Blockchain Service you provisioned. So again, from the VS Code Command Palette, type “Azure Blockchain: Deploy Contracts,” or right-click on your .sol file and choose Deploy Contracts, as shown in Figure 8.

Deploy Smart Contracts into Azure Blockchain Service Directly from Within VS Code
Figure 8 Deploy Smart Contracts into Azure Blockchain Service Directly from Within VS Code

The Azure Blockchain Development Kit extension for VS Code will soon provide capabilities for debugging and testing smart contracts without leaving the development environment. Please check the Dev Kit wiki page (bit.ly/2Zg2mOT) for when this capability will be available. Until then, a popular tool for testing smart contracts is Truffle.

Automating Tests with Truffle

Truffle Suite is a very popular open source suite that consists of three products: Truffle, Ganache and Drizzle. Truffle is a development environment, testing framework and asset pipeline for blockchains using the Ethereum Virtual Machine (EVM). Truffle capabilities include:

  • Built-in smart contract compilation, linking, deployment and binary management.
  • Automated contract testing for rapid development.
  • Debugging of smart contracts that can span multiple connected contracts.
  • Scriptable, extensible deployment and migrations framework.
  • Network management for deploying to any number of public and private networks.
  • Package management with EthPM and NPM, using the ERC190 standard.
  • Configurable build pipeline with support for tight integration.

Truffle provides an automated testing framework for writing simple, manageable tests in two different ways: in Solidity, for unit testing scenarios, and in JavaScript and TypeScript, for testing smart contracts from the outside world. For JavaScript or TypeScript tests, internally Truffle uses the Mocha testing framework (mochajs.org) and Chai (chaijs.com) for assertions to provide a solid framework from which to write your JavaScript tests. Writing test code in Truffle is similar to writing a JavaScript test in Mocha, except for the contract function: This function runs after the smart contract being tested is deployed to the running Ethereum client and it generates a clean contract state. Also, the contract function provides a list of accounts made available by the Ethereum client, which you can use to impersonate in your tests.

Contract abstractions are the basis for making contract interaction possible from JavaScript. Because Truffle has no way of detecting which contract you want to interact with your tests, you’ll need to ask for those contracts explicitly. You can use Truffle’s artifacts.require method to request a usable contract abstraction for a specific Solidity contract. The following code shows a test script for a smart contract that uses the contract function and the artifacts.require method to test the SupplyChain smart contract presented earlier:

const SupplyChain = artifacts.require("SupplyChain");
contract("SupplyChain", testParty => {
  it("Test Party should be in state 10 (Out of Compliance)", () =>
    SupplyChain.deployed()
      .then(instance => instance.getPartyState.call(testParty))
      .then(state => {
        assert.equal(state.valueOf(), 10,
        "The state of Test Party is " + state);
      }
  ));
});

The getPartyState action is called on a deployed instance of the SupplyChain smart contract, passing a test value as input (the name of a test party on the supply chain, whose state is expected to be 10, that is, out of compliance). The returned value is then tested with the assert object for the expected value.

Solidity test contracts live alongside JavaScript tests as .sol files. When Truffle tests are run, they’ll be included as a separate test suite for each test contract. These unit tests maintain all the benefits of the JavaScript tests, namely a clean-room environment per test suite, direct access to the deployed contracts and the ability to import any contract dependency. The structure of a unit test in Solidity requires the initialization of a new instance of the smart contract being tested, using the Truffle DeployedAddresses contract and the Assert library, as in Figure 9. The addresses of your deployed contracts are available through the DeployedAddresses library. This is a list in the form of DeployedAddresses.<contract name>, which is recompiled and relinked before each suite is run to provide your tests with a clean room environment. Keep in mind that all test contracts must start with Test, using an uppercase T, and all test functions must start with test, lowercase. Each test function is executed as a single transaction, in order of appearance in the test file.

Figure 9 Smart Contract Testing in Solidity with Truffle

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../SupplyChain.sol";
contract TestSupplyChain {
  function testGetPartyState() {
    SupplyChain supplyChain = SupplyChain(DeployedAddresses.SupplyChain());
    uint expected = 10;
    string testParty = "Test Party";
    Assert.equal(meta.getPartyState(testParty), expected,
      "Invalid Test Party state.");
  }
}

Once all test files are ready in your project’s “test” folder, you can run all tests with the command:

$ truffle test

Or you can specify a path to a specific file you want to run:

$ truffle test ./test/unit_tests.sol

For a test environment, Ganache, a personal blockchain for Ethereum development, can be used to deploy contracts, develop applications and run tests. It’s available as both a desktop application and a command-line tool (formerly known as the TestRPC). Ganache is available for Windows, Mac, and Linux, and is also being distributed as an embeddable lib for use in tools and applications that would benefit from an internal single-node blockchain. The ability to deploy locally or within a function reduces costs and management overhead typically associated with running a full blockchain node configuration. At the same time, it supports forking of an existing
chain, which lets you test against a blockchain that mirrors the target environment, consortium production, for example.

Wrapping Up

Testing enterprise blockchain solutions doesn’t end at smart contracts. At the consortium-member level, there will be custom code that interacts with the smart contract. This can be code running in compute (a virtual machine, Azure Logic Apps or Functions, Microsoft Flow, and so forth) or in a Web app communicating directly using the native blockchain API endpoints or client libraries, such as Nethereum, that wrap them. This is consortium-specific code that can manifest itself in the communication of data and events to and from the smart contract from back-end systems, Web or mobile clients for end users, and so on. The consortium member should determine what tests should be written to validate that smart contracts haven’t changed in a way that will break compatibility with their existing integration points. The contracts should then be incorporated into build and deployment pipelines as appropriate.

In some use cases, smart contracts represent processes that may span many days, weeks, months, even years. A key consideration for testing is understanding the requirements of releasing a new version of a contract to an immutable blockchain and incorporating those requirements into your testing. If a contract has two versions, a determination must be made as to how existing versions of the contract should be handled. In some cases, existing “in flight” smart contracts will continue to function and in other cases they’ll need to migrate to a newer version of the contract.

For example, if a bank were to introduce a new loan process that had new requirements, it might only want to apply that process to new loans or to loans with terms greater than 180 days. In other cases, legislation, industry regulation, or corporate policies might change how a process must be handled in every situation. Based on a scenario’s requirements, tests that include upgrading existing contracts, compatibility with applications across multiple versions of contracts, and the like should be considered and added as appropriate. Once testing is successfully completed, a release pipeline can copy files and publish build artifacts for use in a release pipeline.


Stefano Tempesta is a Microsoft Regional Director, MVP on Azure, AI and Business Applications, and a member of Blockchain Council. A regular speaker at international IT conferences, including Microsoft Ignite and Tech Summit, Tempesta’s interests extend to blockchain and AI-related technologies. He created Blogchain Space (blogchain.space), a blog about blockchain technologies, writes for MSDN Magazine and MS Dynamics World, and publishes machine learning experiments on the Azure AI Gallery (gallery.azure.ai).

Thanks to the following Microsoft technical expert for reviewing this article: James McCaffrey


Discuss this article in the MSDN Magazine forum