Compare smart contracts on ParallelChain™ and Hyperledger

ParallelChain Lab
ParallelChain Lab Blog
8 min readFeb 23, 2021

In this piece, we are comparing the implementation of two identical smart contracts in ParallelChain™ and Hyperledger Fabric. Scroll down to see code snippets, or you can also view these on our ParallelChain webpage.

We’re about to get ultra-technical with this one! You have been warned.

The Scenario: Commercial Paper

Commercial paper is a type of security issued by large corporations to obtain funds to meet short-term needs. The buyer of a commercial paper can redeem it on the paper’s maturity date. On redeem, the issuer of the paper pays the buyer the face amount specified on the note.

In our scenario, MagnetoCorp (in the person of staff member Isabella) issues commercial papers through PaperNet, a private blockchain network.

Another organization in PaperNet, DigiBank (in the person of staff member Balaji), seeks to buy the commercial paper.

On the maturity date, DigiBank wants to redeem the commercial paper (return ownership of the paper to MagnetoCorp) in exchange for its monetary value (+added interest).

Our first order of business, then is to write a smart contract that implements issue, buy, and redeem actions.

Commercial Paper Smart Contract — High-level anatomy:
As you already know if you have read ‘Writing Smart Contracts,’ a smart contract to the ParallelCore Smart Contract SDK is simply any Go language struct that implements an Initialize and a Handle method, each with a well-defined signature.

The couple main features to note, right out of the gate are:

  • Initialize is called once every time a smart contract is deployed. This is useful, for instance, if (in your business needs) a deployment of a smart contract is an event that has effects on the world state or triggers the invocation of another smart contract. However, in this scenario (and in most real-life scenarios, we have found) this functionality is unnecessary. Hyperledger Fabric’s counterpart to Initializeis Instantiate
  • Handle is called every time a smart contract is invoked. The business logic of your smart contract should enter the control flow through here. You could technically write a ParallelChain smart contract that is nothing but an empty Initialize and a massive Handle, but good coding practices will compel you to write something like what we have above, where Handle serves mostly as a wrapper around a switch…case: construct that multiplexes between multiple kinds of actions; in our case: issue, buy, and redeem.

Now, onto the main differences between the ParallelChain and Fabric code samples:

  1. Fabric smart contracts have a main method, ParallelChain smart contracts do not. To execute your contract, the ParallelCore Engine simply looks for the exported SmartContract variable’s (last line of ParallelChain sample Handle method and calls it.
  2. Fabric smart contracts do not have a Handle method. Behind the scenes, Fabric extracts the non-keyword names of methods attached to type Contract and registers them as invokable actions. In ParallelChain, this is done explicitly in Handle.
  3. Fabric smart contract ‘action functions’ are methods of type Contract, ParallelChain action functions are plain, well, functions.
  4. Fabric smart contract action functions can return any type they like, ParallelChain’s Handle returns a generic slice of bytes.

The two code samples have a few other differences — we’ve only listed ones that are mandatory — others, like how we are passing arguments to issue, buy, and redeem inside a struct, are simply a subjective matter of code style.

The `issue` action:
Let’s move on and implement issue: the action Isabella in MagnetoCorp will use to create new Commercial Papers and put them in circulation.

ParallelChain: ‘issue’ action
Hyperledger: ‘issue’ action

The key lines are:

  • We start by quering the key-value store to check if a paper with the same issuer and paperNumber (in the form of FullKey(), which we define in paper.go, already exists). If so, issue and by extension Handle returns with an error.
  • We ‘flatten’ the paper struct into a slice of bytes . This will become the ‘value’ in the key-value pair that represents our commercial paper in the PCoreDB key-value store. You can implement Serialize in any way you want. Typically, you would serialize your business entities into JSON-encoded strings for simplicity, but anything from YAML to Google’s Protocol Buffers works just as well.
  • We have defined a FullKey() method on paper that returns a string of the form `CP<issuer><paperNumber>`

Now that we have both key and value on hand, we can write the pair into the world state using Transaction.Set(). Transactions in ParallelChain are committed into the world state and the blockchain immediately, before a client’s smart contract invocation returns. In much slower, ‘public’ blockchains like Bitcoin and Ethereum, this makes interactive, ‘real-time’ applications impossible. ParallelChain handles these workloads perfectly.

Transaction is a struct provided to you by the Smart Contract SDK. If you’ve worked with Hyperledger Fabric before, you can see how Transaction provides an interface similar to that exposed by Fabric'sChaincodeStub, just much simpler. We’ll only be working with Transaction’s Get, Set, and GetByRange methods in this tutorial.

The `buy` and `redeem` actions
Now that you are familiar with the code for ‘issue’, we should be able to skim through both ‘buy’ and ‘redeem.’ Here is the code for buy, which Balaji from DigiBank will use to buy MagnetoCorp’s issued CP and transfer its ownership to DigiBank’s.

ParallelChain’s code for ‘buy’
Hyperledger’s code for ‘buy’

And here is the code for ‘redeem’, which Balaji will use to return the CP he bought to MagnetoCorp for profit!

ParallelChain’s code for ‘redeem’
Hyperledger’s code for ‘redeem’

Querying the database — the ‘get’ action
Keen-eyed readers should have noticed that we’ve also included in main.go three extra actions beyond issue, buy, and redeem. We will investigate the other two in a bit, but for now it’d do well for us if we look into get, an action which requires us to GetByRange.

This method of Transaction takes in three arguments: a start key (inclusive), an end key (exclusive), and a struct that defines a few further query parameters.

In the most precise terms, the ‘range’ in GetByRange is defined by lexicographic string ordering, not numerical or bitwise ordering. This is significant. If range meant bitwise range, then the key ‘AA’ will not be included in the results for GetByRange(‘A’, ‘Z’, …). In lexicographic string ordering, it is. The start-end key pairs we specified in the call in the code sample will return us every item with keys starting with the prefix ‘CP/’.

Scary bitwise string encoding talk: a single character in a PCoreDB key is encoded as a single byte (8-bits), that means that strictly speaking, you have 2⁸ = 255 (256–1, \xff is reserved) unique characters available to you to construct a key, which includes all characters easily accessible on a typical full-size keyboard. This is more than sufficient for virtually all use cases.

Transaction.GetByRange() returns an iterator struct which we can traverse through using the for construct above.

The `issue` application
If you have followed the tutorials closely so far, you should now be quite comfortable with the idea of smart contracts — executables ‘living’ in the blockchain network, executed by individual peer nodes. You should also have seen how we can invoke smart contracts through the ParallelCore CLI.

But it would of course be quite counterintuitive for blockchain network users to have to open a command line interface on their high-powered Linux machine running ParallelCore every time they’d like to make a bid for an asset, or approve a transfer of goods in a supply chain.

Ideally, we would like users’ interactions with the blockchain to be abstracted away as possible from scary things like ‘engines’ and ‘smart contracts’. We would like to develop ‘applications.’

This is where the ParallelCore Client Go SDK comes in (remember, we have been working with the smart contract SDK so far).

The single clientSDK.Client instantiated using OpenAny wraps around a connection to a single node in the ParallelChain network. All nodes in a ParallelChain network are logically identical, with access to the same world state, blockchain ledger, and able to execute any deployed smart contract, so it does not matter for our purposes which node we connect to.

In our example, we tell OpenAny function to try and connect to either the node with the endpoint ‘local.digital-transaction.net:5000’, or the one with the endpoint ‘local.digital-transaction.net:5001.’ The function returns a Client with a connection to the the first endpoint in endpointSpec (a space-delimited list of endpoints) that isn’t too busy (load balancing considerations).

We then Invoke the CommercialPaper smart contract, passing in arguments as a byte slice. The ‘-v*’ syntax appended to the end of first argument to Invoke specifies that we’d like to run the latest deployed version of the smart contract available. In most cases, this is the desired behavior, but ParallelChain can be configured to keep older versions of smart contracts executable too.

Key concept: ‘keyspaces’
Let’s say, 1–2 years down the line, another organization in our ParallelChain network creates their own series of ‘CP’ (Cute Pandas) smart contracts, and starts registering pandas into the shared PCoreDB key-value store with the same ‘CP/’ key prefixes we’ve used for ‘Commercial Paper.’ What happens to our key-value pairs?

The short and easy answer is: nothing. Just like in Hyperledger Fabric, smart contracts in ParallelChain only ‘see’ the part of the database that is ‘keyspaced’ to them. This is a core idea in our programming model, and avails you greater flexibility in choosing keys: collisions will not happen.

If your design calls for a set of smart contracts that frequently share data with each other, you should consider packaging them instead as separate actions within the same smart contract.

If this makes your SC packages too large, then you might want to pull out all DB-related functions and put them into a single ‘utility’ smart contract. You can then use Transaction.Invoke to invoke actions in this utility smart contract, from other smart contracts.

ParallelChain enforces development of light, performant smart contracts with clearly delineated roles. Smart contract invocations that take longer than 5-seconds automatically time out to prevent network bottleneck. If you find your smart contracts consistently hitting this 5-second limit, you should look into factoring out your business logic into multiple smart contracts.

--

--