The Blockchain App Builder for the Oracle Blockchain Platform is a new component that extends our vision of making enterprise blockchains easy and quick. Since the initial release of the platform in 2018, our focus before has been on simplifying the infrastructure, configuration, integration, and operations tasks. Now we aim to bring the same ease and simplicity to building smart contracts that are at the heart of any enterprise blockchain.
The Blockchain App Builder (BAP) is designed to simplify and speed up the development of smart contracts for the Oracle Blockchain Platform, a production-ready, pre-assembled enterprise blockchain platform based on a Linux Foundation open source project Hyperledger Fabric. This series of blog posts will introduce the features provided and show how to use the tools.
With the deprecation of the Hyperledger Composer project, developers lacked blockchain development tooling to assist them in creating blockchain-based applications, particularly the actual chaincode (as smart contracts are known in the Oracle Blockchain Platform) that maps the ledger’s data model and handles the business logic. While Composer made it easy to quickly develop and test smart contracts, it had some significant limitations. The goal of our new tools is to help accelerate the development of blockchain applications while still providing the flexibility to use the Oracle Blockchain Platform to its fullest.
Developing smart contracts for use in a blockchain application can be complicated and difficult. A smart contract executes in its own Docker container, making deploying, debugging, and testing more challenging. It needs to execute the same way across multiple nodes to ensure consistent results, and it relies on specific shim APIs to retrieve and persist the data. To simplify the development of smart contracts, the Blockchain App Builder includes a set of tools that aid in:
The tools currently provide two user interfaces:
The basic flow for developing a smart contract with these tools is shown on the diagram below. First, you create a specification file that describes the assets or objects being maintained on the blockchain ledger. This specification file is then used to scaffold a smart contract project and generate source code for models and controllers for the assets specified in the file. The specification file allows defining the properties of the assets and any validation requirements for the properties.
This results in a Model file, which contains the property definitions of all the assets defined in the specification file, and a Controller file, which defines all the behavior or methods for those assets. A specification file allows defining additional custom methods that the user needs to implement in order to provide the business logic of the smart contract. Even before those custom methods are implemented, though, the user can deploy and test the generated chaincode with the CRUD methods that were generated automatically.
Both the CLI and the VS Code extension allow scaffolding, deploying, and testing the smart contracts. Before we delve into the details, you naturally want to know how to get the Blockchain App Builder. Provision a new Blockchain Platform instance in Oracle Cloud using OCI Menu, and once the instance is ready, bring up the Console and navigate to the Developer Tools tab. Under this tab, the Blockchain App Builder page describes its features and provides download links for the CLI and VS Code extension (for which you need to installing VS Code itself) as well as two sample specification files (which are also included in the tool downloads.) Your existing gen2 Blockchain instances in OCI will be updated to include this page and the download links in a few weeks. For OBP Enterprise Edition users, we'll be providing a patch with this update as well.
The starting point for creating a smart contract is to define the information that will be stored in the ledger. This is done via a YAML or JSON file, as in this simple example:
|
assets:
- name: car # Define the properties for a car properties: - name: vin # Vehicle identification number type: string mandatory: true id: true # Use the VIN as the ID for the car - name: make # Manufacturer type: string - name: model # Model name type: string - name: year # Model year type: number - name: color # Color type: string |
This specification file defines the properties for an asset we call “car” which includes the Vehicle Identification Number (or VIN), the make, model, year, and color of the car. For each property, the type of the property is given. Notice that for vin, two additional attributes are defined for the property: mandatory: true indicating that the vin must be specified and can’t be left empty, and id: true indicating that the vin is used as the key or ID for the car.
A specification file can define multiple assets, and additional attributes of the properties are available to define things like validation rules to apply when the value of the property is provided. A simple example is a validation rule on year to indicate it must be a positive number or even that it must be greater than 1900. The vin could have a validator that indicates it must be no more and no less than 17 characters. The objective is to declare these sorts of constraints or requirements in the specification file, so they can be checked in the generated code and don’t have to be checked by the user’s own application code.
|
assets:
- name: car # Define the properties for a car properties: - name: vin # Vehicle identification number type: string mandatory: true id: true validate: min(17),max(17) # Always 17 characters ❶ - name: make # Manufacturer type: string mandatory: true validate: /^\s*(cheverolet|ford|general\smotors|toyota|hyundai|tesla|tata|fiat|volkswagen|peugeot)\s*$/I ❷ - name: model # Model name type: string mandatory: true - name: year # Model year type: number mandatory: true validate: min(1910),max(2020) ❸ - name: color # Color - no validation as color names are innumerable type: string mandatory: true - name: owner # Owners's email address type: string validate: email() # Validate it as a valid email address ❹ - name: price # Current price of the vehicle type: number validate: positive() ❺ - name: lastSold # Date of the last sale of this car type: date ❻ |
Here is a slightly more complete version of the car asset definition. In this updated specification file, we added:
❶ A validator to make sure the vin is exactly 17 characters
❷ A validator on make limiting the property to one of a set of values, effectively an enum, using a regular expression
❸ A validator on year to make sure the date is between 1910 and 2020
❹ A new string property owner that must conform to a valid email address
❺ A new property price that must always be a positive number
❻ A new date property lastSold
These validators and type specifications eliminate the need for the chaincode developer to perform all these validations themselves in their implementation. If the validations don’t pass or the type of data doesn’t match the specified type, the requested operation fails with a validation failure.
Given the above specification file, we can use the “ochain init” command to scaffold a smart contract project. For the examples in this blog post, I’ll focus on TypeScript-based chaincode, even though the tools currently support both TypeScript and Golang. Once the chaincode project has been initialized (scaffolded), the project folder will include the following directories and files:
/home/obptools/mycar2/lib
/home/obptools/mycar2/main.ts
/home/obptools/mycar2/node_modules
/home/obptools/mycar2/package.json
/home/obptools/mycar2/package-lock.json
/home/obptools/mycar2/README.md
/home/obptools/mycar2/src
/home/obptools/mycar2/src/mycar2.controller.ts
/home/obptools/mycar2/src/mycar2.mode.ts
/home/obptools/mycar2/tests
/home/obptools/mycar2/tsconfig.json
Let’s examine the generated model file from the above specification file:
|
import * as yup from 'yup';
import { Id, Mandatory, Validate, Default } from ';../lib/decorators' import { OchainModel } from '../lib/ochain-model'; @Id('vin') export class Car extends OchainModel<Car> { public readonly assetType = 'mycar2.car'; @Mandatory() @Validate(yup.string().min(17).max(17)) public vin: string; @Validate(yup.string().matches(/^\s*(cheverolet|ford|general\smotors|toyota|hyundai|tesla|tata|fiat|volkswagen|peugeot)\s*$/i)) public make: string; @Validate(yup.string()) public model: string; @Mandatory() @Validate(yup.number().min(1910).max(2020)) public year: number; @Mandatory() @Validate(yup.string()) public color: string; @Validate(yup.string().email()) public owner: string; @Validate(yup.number().positive()) public price: number; @Validate(yup.date()) public lastSold: Date; |
This generated file provides the definition of the Car asset. It uses annotations to define specific requirements for any given property. For example, the:
@Validate(yup.string().min(17).max(17))
on the vin property indicates that it must be at least 17 characters in length and no more than 17 characters in length. Likewise, the:
@Validate(yup.number().positive())
on the price property indicates that it must be a positive number, as was specified in the specification file. Type validation occurs automatically without any additional information required in the specification file. So, the yup.number() annotation indicates that the property must be a number. The positive() annotation on price indicates the number must be a positive number.
If we examine the generated controller file, we can see the CRUD method implementations:
|
import * as yup from 'yup';
import { Validator } from '../lib/decorators'; import { OchainController } from '../lib/ochain-controller'; import { Car } from './fabcar.model'; export class FabcarController extends OchainController { public async init(params: any) { return; } //----------------------------------------------------------------------------- // Car //----------------------------------------------------------------------------- @Validator(Car) public async createCar(asset: Car) { return await asset.save(); } public async getCarById(id: string) { const asset = await Car.get(id); return asset; } @Validator(Car) public async updateCar(asset: Car) { return await asset.update(); } public async deleteCar(id: string) { const result = await Car.delete(id); return result; } } |
At this point, we have a ready-to-run, ready-to-test smart contract.
Now that we’ve seen how to model an asset, in the next post, I’ll describe how to use the Oracle Blockchain App Builder to run and test the smart contract and how to extend it to add custom functions.
To learn more about blockchain and Oracle Blockchain Platform, visit the Oracle Blockchain page.