Hyperledger Composer architecture
Welcome, to part two of the “Choosing private blockchain tech” series (part1 is here). In each article we implement a simple use case to give an overview of what an existing technology does and how it can be useful in businesses.
Originally, this article was meant to be about Hyperledger Fabric, but after learning about Hyperledger Composer, it became clear that it would be beneficial to write about it first. This will allow us to get a higher-level view of what type of business applications can be built on top of Hyperledger Fabric blockchain and make it easier to understand the purpose of Fabric itself.
Let’s start with the artificially simple use case from the finance industry defined in part1. Here are the parties in this scenario:
Let’s see how we can use Hyperledger Composer to define all participants in this scenario and enable them to share appropriate information.
Hyperledger Composer is a higher-level toolset and framework made to quickly build and run applications on top of Hyperledger Fabric blockchain. If Fabric is network-level, Composer is application-level. It allows us to define the data model, business logic and access control lists for an application that can be deployed and executed inside of a Fabric channel. Users of such applications don’t have to run a local node and can interact with a remote node through an RPC or HTTP REST if needed.
Composer comes with a nice web playground which allows users to prototype applications in browser without setting up a local network. We’ll use the playground to implement and test our use case. In a future article, we will export the resulting ‘business network definition’ and deploy to a real Hyperledger Fabric blockchain.
The full code of the solution can be found here. Bellow we’ll see how to configure each piece step-by-step using the Composer Playground.
Select ‘Deploy a new business network’ and fill out the required info. In my case, the name of the network is ‘hedge-fund-network’ and admin card name is admin@hedge-fund-network. Select the ‘empty-business-network’ template and hit Deploy. This will create an empty business network and an admin identity that has full control over it. After the business network is created we can connect to it by pressing ‘Connect Now’.
Composer has its own object modeling language that is very straightforward and easy to use. There are 4 types of resources that can be defined:
Now, let’s create a model file named ‘org.acme.mynetwork.cto’ and add the following code. The file defines 3 participant types (Trader, Client, Custodian), Lot asset, Trade transaction and NewTrade event.
/*** My commodity trading network*/namespace org.acme.mynetwork
asset Lot identified by lotId {o String lotIdo String securityNameo Double quantityo Double price--> Client owner}
participant Client identified by clientId {o String clientIdo String description}
participant Custodian identified by custodianId {o String custodianIdo String description}
participant Trader identified by traderId {o String traderIdo String name}
transaction Trade {--> Trader trader--> Client client--> Lot lot}
event NewTradeEvent {--> Lot lot}
Next, we’ll implement a custom logic that gets executed every time a Trade transaction is sent. Create a new script file called script.js with the code below. Composer knows to execute this code for each Trade transaction based on the @param annotation in the comment. This code does 2 things: changes the owner of a Lot and emits a NewTrade event on success.
/*** Track the trade* @param {org.acme.mynetwork.Trade} trade — the trade to be processed* @transaction*/function tradeCommodity(trade) {var factory = getFactory();trade.lot.owner = trade.client;var result = getAssetRegistry(‘org.acme.mynetwork.Lot’).then(function (assetRegistry) {return assetRegistry.update(trade.lot);});if (result) {var newTradeEvent = factory.newEvent(‘org.acme.mynetwork’, ‘NewTradeEvent’);newTradeEvent.lot = trade.lot;emit(newTradeEvent);}return result;}
Lastly, we need to define an access control list governing what each participant type can do and see. Here we specify that traders can execute trades, clients can view their own trades, and custodians can view all trades.
/* Admin */
rule NetworkAdminUser {description: "Grant business network administrators full access to user resources"participant: "org.hyperledger.composer.system.NetworkAdmin"operation: ALLresource: "**"action: ALLOW}
rule NetworkAdminSystem {description: "Grant business network administrators full access to system resources"participant: "org.hyperledger.composer.system.NetworkAdmin"operation: ALLresource: "org.hyperledger.composer.system.**"action: ALLOW}
/* Common */
rule CommonReadTransactionRegistry {description: "Allow all participants to read transaction registry"participant: "org.hyperledger.composer.system.Participant"operation: READresource: "org.hyperledger.composer.system.TransactionRegistry"action: ALLOW}
rule CommonReadParticipantRegistry {description: "Allow all participants to read participant registry"participant: "org.hyperledger.composer.system.Participant"operation: READresource: "org.hyperledger.composer.system.ParticipantRegistry"action: ALLOW}
rule CommonReadAssetRegistry {description: "Allow all participants to read asset registry"participant: "org.hyperledger.composer.system.Participant"operation: READresource: "org.hyperledger.composer.system.AssetRegistry"action: ALLOW}
rule CommonReadNetwork {description: "Allow all participants to read network"participant: "org.hyperledger.composer.system.Participant"operation: READresource: "org.hyperledger.composer.system.Network"action: ALLOW}
/* Trader */
rule TraderManageClient {description: "Allow traders to read all clients"participant: "org.acme.mynetwork.Trader"operation: ALLresource: "org.acme.mynetwork.Client"action: ALLOW}
rule TraderManageOwnTrades {description: "Allow traders to manage their trades"participant(t): "org.acme.mynetwork.Trader"operation: ALLresource(tt): "org.acme.mynetwork.Trade"condition: (tt.trader.getIdentifier() == t.getIdentifier())action: ALLOW}
rule TraderManageLots {description: "Allow traders to read and create lots"participant: "org.acme.mynetwork.Trader"operation: READ, CREATEresource: "org.acme.mynetwork.Lot"action: ALLOW}
rule TraderUpdateLots {description: "Allow traders to update lots via Trade transaction"participant: "org.acme.mynetwork.Trader"operation: UPDATEresource: "org.acme.mynetwork.Lot"transaction: "org.acme.mynetwork.Trade"action: ALLOW}
rule TraderReadOwnTrader {description: "Allow traders to read their own info"participant(t): "org.acme.mynetwork.Trader"operation: READresource(tt): "org.acme.mynetwork.Trader"condition: (tt.getIdentifier() == t.getIdentifier())action: ALLOW}
rule TraderAddAsset {description: "Allow traders to add assets to registry"participant: "org.acme.mynetwork.Trader"operation: CREATEresource: "org.hyperledger.composer.system.AddAsset"action: ALLOW}
rule TraderCreateHistorianRecord {description: "Allow traders to create historian record"participant: "org.acme.mynetwork.Trader"operation: CREATEresource: "org.hyperledger.composer.system.HistorianRecord"action: ALLOW}
rule TraderReadOwnHistorianRecord {description: "Allow traders to read their own historian record"participant(t): "org.acme.mynetwork.Trader"operation: READresource(hr): "org.hyperledger.composer.system.HistorianRecord"condition: (hr.transactionType == "org.acme.mynetwork.Trade" && hr.participantInvoking.getIdentifier() == t.getIdentifier())action: ALLOW}
/* Client */
rule ClientReadOwnTrades {description: "Allow clients to view their trades"participant(c): "org.acme.mynetwork.Client"operation: READresource(t): "org.acme.mynetwork.Trade"condition: (t.client.getIdentifier() == c.getIdentifier())action: ALLOW}
rule ClientReadOwnEvents {description: "Allow clients to subscribe to NewTrade events"participant(c): "org.acme.mynetwork.Client"operation: READresource(e): "org.acme.mynetwork.NewTradeEvent"condition: (e.lot.owner.getIdentifier() == c.getIdentifier())action: ALLOW}
rule ClientReadOwnLots {description: "Allow clients to view lots they own"participant(c): "org.acme.mynetwork.Client"operation: READresource(s): "org.acme.mynetwork.Lot"condition: (s.owner.getIdentifier() == c.getIdentifier())action: ALLOW}
rule ClientReadOwnClient {description: "Allow clients to view their info"participant(c): "org.acme.mynetwork.Client"operation: READresource(cc): "org.acme.mynetwork.Client"condition: (cc.getIdentifier() == c.getIdentifier())action: ALLOW}
rule ClientReadTraders {description: "Allow clients to view traders"participant: "org.acme.mynetwork.Client"operation: READresource: "org.acme.mynetwork.Trader"action: ALLOW}
rule ClientReadHistorianRecord {description: "Allow clients to view their historian records"participant(c): "org.acme.mynetwork.Client"operation: READresource(hr): "org.hyperledger.composer.system.HistorianRecord"condition: (hr.transactionType == "org.acme.mynetwork.Trade" && hr.transactionInvoked.client.getIdentifier() == c.getIdentifier())action: ALLOW}
/* Custodian */
rule CustodianReadAllTrades {description: "Allow custodian to view all trades"participant: "org.acme.mynetwork.Custodian"operation: READresource: "org.acme.mynetwork.Trade"action: ALLOW}
rule CustodianReadAllLots {description: "Allow custodian to view all lots"participant: "org.acme.mynetwork.Custodian"operation: READresource: "org.acme.mynetwork.Lot"action: ALLOW}
rule CustodianReadAllClients {description: "Allow custodian to view all clients"participant: "org.acme.mynetwork.Custodian"operation: READresource: "org.acme.mynetwork.Client"action: ALLOW}
rule CustodianReadAllEvents {description: "Allow custodian to subscribe to NewTrade events"participant: "org.acme.mynetwork.Custodian"operation: READresource: "org.acme.mynetwork.NewTradeEvent"action: ALLOW}
rule CustodianReadAllTraders {description: "Allow custodian to view all traders"participant: "org.acme.mynetwork.Custodian"operation: READresource: "org.acme.mynetwork.Trader"action: ALLOW}
rule CustodianReadOwnCustodian {description: "Allow custodian to view their info"participant(c): "org.acme.mynetwork.Custodian"operation: READresource(cc): "org.acme.mynetwork.Custodian"condition: (cc.getIdentifier() == c.getIdentifier())action: ALLOW}
rule CustodianReadHistorianRecord {description: "Allow custodian to view all trade historian records"participant(t): "org.acme.mynetwork.Custodian"operation: READresource(hr): "org.hyperledger.composer.system.HistorianRecord"condition: (hr.transactionType == "org.acme.mynetwork.Trade")action: ALLOW}
After all steps are completed, we can deploy the application by clicking Update on the left.
Now that we have the complete definition of our application, we can test it using the Composer Playground Test module (click Test at the top).
First, let’s seed our application with some participants. We can create 2 clients: a custodian and a trader.
In order to be able to interact with the business network a participant needs an associated identity. Identities can be created in the ID registry (open the user dropdown on the top right). We’ll create an identity for each participant by specifying its name and pointing to an instance of the participant created in the previous step. At a minimum, we need 4 identities: 2 clients, 1 trader, and 1 custodian.
We can now login using the newly created identities by clicking “Use now” next to each identity in the list. First, we login as a trader and create a new Lot that doesn’t belong to any client (client ID is null).
Then, we assign the ownership of this lot to client 1 via a Trade transaction.
Now, let’s create one more lot and assign it to client 2 via another Trade.
Then, we’ll login as client 1 and client 2 to check if we can see our Lots.
Client 1 lots
Client 2 lots
As you can see, each client sees only lots that they have ownership of and they are not aware of trades for other clients.
Let’s login as a custodian to check that we can view all trades. Indeed, both trades are visible, as you can see below.
Custodian can also view history of all trades:
Keep in mind that when a transaction is broadcasted to the Hyperledger Fabric network, each configured peer is independently executing the custom transaction processing logic which ensures the correctness of the blockchain update. That’s a major architectural difference and — in many cases — an advantage over the traditional centralized, client-server approach. In the traditional approach a single organization would be responsible for managing the data and exposing it to other participants, forcing others to trust it and making it a potential single point of failure.
In the blockchain approach, each peer is responsible for managing and reconciling their own copy of the data. This makes the entire network more resilient and decentralized. However, it is not completely simple to configure. There are additional privacy implications to consider since the data is now hosted in many places which creates a potentially larger threat surface.
Hopefully this has given you a better understanding of what Hyperledger Composer is and how it can be useful in sharing information and keeping track of assets and other concepts among multiple participants. Feel free to export the .bna file of your network and follow the tutorial here to deploy the application to a local Hyperledger Fabric network. We’d love to hear your thoughts on Hyperledger Composer. Please, leave any questions or comments below.