X

Faster, Easier Development of Smart Contracts is Here Today with Blockchain App Builder for Oracle Blockchain Platform - Part 2

Todd Little
Oracle Blockchain Platform Chief Architect

In my last post I introduced the Oracle Blockchain App Builder and some of its features. In this post I’ll dig into more detail about the specification file that is used to drive the development of the smart contract and how the resulting chaincode can be tested in minutes.


The specification file is the heart of the application to be built on the Oracle Blockchain Platform. It defines the assets/objects to be managed by the smart contract and the behaviors/functions associated with each of those assets. While the example used only shows a single asset, a car, a specification file can specify multiple assets and each asset can have a different set of associated behaviors or APIs. Thus, the specification file could define cars, dealers, owners, manufacturers, etc. with each asset type can have its own set properties and behaviors.


While the starting point is a specification file, it is likely that the team’s understanding of the problem space matures and with that changes may be needed in the specification file. This could include adding new properties, changing the type of properties, adding new asset types, or adding new methods. The Oracle Blockchain App Builder provides a mechanism to allow changes in the specification file to be merged into the generated files that the user may have already modified. Thus, changes in scope or understanding of the problem don’t invalidate the work that has already been done.

Let’s see how a simple specification file can be turned into a usable Oracle Blockchain Platform smart contract.

Scaffolding a chaincode project

Starting with the last specification file I posted I’ll add the CRUD operations that are to be generated:

 

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 - additional validation possible
        - name: make        # Manufacturer, limited to one of: Cheverolet,Ford,General Motors,Toyota,Hyundai,Tesla,Tata,Fiat,Volkswagen,Peugeot
          type: string
          validate: /^\s*(cheverolet|ford|general\smotors|toyota|hyundai|tesla|tata|fiat|volkswagen|peugeot)\s*$/i
        - name: model       # Model name
          type: string
        - 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
    methods:
      crud: [create, getById, update, delete]
      others: []

By adding the “methods” section, App Builder will automatically generate the specified chaincode functions. In this case createCar, getCarById, updateCar, and deleteCar. These chaincode functions can be used to create, read, update, and delete cars, although without any logic to verify that the operations make business sense.

Let’s see how without even adding any custom business logic we can test the generated smart contract. The first step is to scaffold the chaincode project. Assuming the specification file is called fabcar.yml, we'll use the ochain init command to scaffold the project and generate the initial chaincode:

obptools@ochain:~$ ochain init --cc fabcar --lang ts --conf fabcar.yml

The result will be a subdirectory of the current directory named fabcar that contains:

dist/
.gitignore*
lib/
main.ts*
node_modules/
.npmignore*
.ochain.json*
package.json*
package-lock.json
README.md*
src/
src/fabcar.model.ts
src/fabcar.controller.ts
tests/
tsconfig.json
.vscode/
The generated model fabcar.model.ts and controller fabcar.controller.ts files will be in the src/ directory.

 

Testing the chaincode

Now that the project has been scaffolded, it’s time to run and test it locally using the ochain run and ochain invoke commands:

obptools@ochain:~$ cd fabcar
obptools@ochain:~/fabcar$ obptools@ochain ochain run
This command will start up a local Fabric network with single instances of the Fabric orderer, the Fabric peer, and the Fabric-CA. It will then install and instantiate (deploy) the chaincode to the peer.

Next, in another terminal window, let’s invoke one of the CRUD operations with the ochain invoke command:

obptools@ochain:~/fabcar$ ochain invoke createCar '{"vin":"vinandserialnum01","make":"ford","model":"hybrid","year":2015,"color":"black","price":15000,"lastSold":"2020-09-01"}'
obptools@ochain:~/fabcar$
This command takes the passed in parameter string and invokes the createCar CRUD method to add a 2015 Black Ford Hybrid that last sold for $15,000 on Sept 1, 2020. Let’s retrieve the car using the getCarById chaincode function by using the ochain query command. We could use the ochain invoke command as well if we wanted the request recorded in the ledger as query operations are not recorded in the ledger. We pass in the vin as the ID as the specification file indicated vin is the ID for the asset.

 

obptools@ochain:~/fabcar$ ochain query getCarById 'vinandserialnum01'
[2020-10-25T11:39:55.646] [INFO] default - 

============ Started Query Chaincode ============

[2020-10-25T11:39:55.696] [INFO] default - Successfully queried peer[0] on channel "mychannel" using chaincode "fabcar"
[2020-10-25T11:39:55.697] [INFO] default - 

============ Finished Query Chaincode ============

[2020-10-25T11:39:55.697] [INFO] default - {"assetType":"fabcar.car","vin":"vinandserialnum01","make":"ford","model":"hybrid","year":2015,"color":"black","owner":"joe@oracle.com","price":12000,"lastSold":"2020-10-20T06:00:00.000Z"}
obptools@ochain:~/fabcar$ 
Note that a readonly property called assetType has been added to the asset to allow the Blockchain App Builder to know the asset is a car and not some other type of asset in the smart contract.


If we try to add a car with parameters that violate the validations specified in the specification file, we’ll get an error:

obptools@ochain:~/fabcar$ ochain invoke createCar '{"vin":"vinandserialnum02","make":"studebaker","model":"Starlight","year":1947,"color":"black","price":1000,"lastSold":"1949-04-20"}'
[2020-10-25T11:41:21.534] [INFO] default - 

============ Started Invoke Chaincode ============

[2020-10-25T11:41:21.591] [ERROR] default - Error Invoking chaincode "fabcar:" method "createCar" on Channel "mychannel". Detailed Error: transaction returned with failure:    Validation of arguments failed: Error: Error in field 'make' with value '"studebaker"': this must match the following: "/^\s*(cheverolet|ford|general\smotors|toyota|hyundai|tesla|tata|fiat|volkswagen|peugeot)\s*$/i"
[2020-10-25T11:41:21.591] [INFO] default - 

============ Finished Invoke Chaincode ============

ERROR (CLI): Error invoking chaincode: 
obptools@ochain:~/fabcar$ 
Because Studebaker doesn’t match one of the allowed makes listed in the regex, the request fails.

Adding Custom Methods

Let’s add a custom method to buy a car. The business logic will be trivial, only check that the car exists and doesn’t have an owner. To add the custom method, we’ll update the specification file to add a buyCar function by including a new section in the specification file called customMethods to the end of the updated specification:

    methods:
      crud: [create, getById, update, delete]
      others: []
customMethods:
    - "buyCar(vin: string, buyer: string, price: number, date: Date)"
Since we’ve changed the specification file, we’ll want to sync that to the already generated files. To perform that we’ll use the ochain sync command. It takes the updated specification file and merges the resulting changes into the generated model and controller files.

 

obptools@ochain:~/fabcar$ ochain sync

If we examine the updated controller file fabcar.controller.ts, we’ll see at the end an empty function called buyCar.

@Validator(yup.string(), yup.string(), yup.number(), yup.date())
public async buyCar(vin: string, buyer: string, price: number, date: Date) {
}

The function is prefaced with the @Validator decorator to indicate how the parameters should be validated according to what was specified in the specification file. In this case, the only validations being perform are to ensure the parameters adhere to the appropriate type definitions. So vin and buyer are validated as strings, while price is validated as a number, and date validated as a date. Since the owner property of the car must be a valid email address due to the decorator given in the specification file, we’ll update the validator on this function to ensure it’s not only a string, but also a valid email address.

As this is a trivial example, we’ll just check to make sure the car is in the system and isn’t currently owned by adding an implementation of the buyCar function as shown here:

@Validator(yup.string(), yup.string().email(), yup.number(), yup.date())
public async buyCar(vin: string, buyer: string, price: number, date: Date) {
    try {
        const car = await this.getCarById(vin);
        if (!car) {
            throw new Error(`Car with vin '${vin}' is not in the system`);
        }
        if (car.owner) {
            throw new Error(`Car with vin '${vin}' already has an owner '${car.owner}'`);
        }
        car.owner = buyer;
        car.price = price;
        car.lastSold = date;
        await car.update();
        return `Car with vin '${vin}' has been bought by buyer '${buyer}'`;
    } catch(error) {
        throw new Error(error.message);
    }
}

In the implementation we first retrieve the car based up the vin. If the car isn’t in the system, throw an error. If the car already has an owner, throw an error. Finally update the properties on the car and update it in the system.

Let’s try our implementation by buying the first car we created. We’ll first need to save the updated fabcar.controller.ts file which will cause the chaincode to automatically be redeployed and then we can invoke buyCar.

obptools@ochain:~/fabcar$ ochain invoke buyCar 'vinandserialnum01' 'joe@oracle.com' '12000' '2020-10-20'
[2020-10-20T10:17:16.472] [INFO] default - 

============ Started Invoke Chaincode ============

[2020-10-20T10:17:16.531] [INFO] default - Successfully sent Proposal and received ProposalResponse
[2020-10-20T10:17:18.572] [INFO] default - The chaincode invoke transaction has been committed on peer localhost:7051
[2020-10-20T10:17:18.572] [INFO] default - The chaincode invoke transaction was valid.
[2020-10-20T10:17:18.573] [INFO] default - Successfully sent transaction to the orderer.
[2020-10-20T10:17:18.573] [INFO] default - Successfully invoked method "buyCar" on chaincode "fabcar" on channel "mychannel"
[2020-10-20T10:17:18.573] [INFO] default - 

============ Finished Invoke Chaincode ============

[2020-10-20T10:17:18.573] [INFO] default - Car with vin 'vinandserialnum01' has been bought by buyer 'joe@oracle.com'

We can check to see if the car now has an owner by querying the car again:

obptools@ochain:~/fabcar$ ochain query getCarById 'vinandserialnum01'
[2020-10-20T12:09:55.457] [INFO] default - 

============ Started Query Chaincode ============

[2020-10-20T12:09:55.490] [INFO] default - Successfully queried peer[0] on channel "mychannel" using chaincode "fabcar"
[2020-10-20T12:09:55.490] [INFO] default - 

============ Finished Query Chaincode ============

[2020-10-20T12:09:55.490] [INFO] default - {"assetType":"fabcar.car","vin":"vinandserialnum01","make":"ford","model":"hybrid","year":2015,"color":"black","owner":"joe@oracle.com","price":12000,"lastSold":"2020-10-20T06:00:00.000Z"}
obptools@ochain:~/fabcar$

And we see that joe@oracle.com is now the owner. If we try to buy the car again, we get an error:

obptools@ochain:~/fabcar$ ochain invoke buyCar 'vinandserialnum01' 'joe@oracle.com' '12000' '2020-10-20'
[2020-10-20T12:11:23.987] [INFO] default - 

============ Started Invoke Chaincode ============

[2020-10-20T12:11:24.028] [ERROR] default - Error Invoking chaincode "fabcar:" method "buyCar" on Channel "mychannel". Detailed Error: transaction returned with failure:    Car with vin 'vinandserialnum01' already has an owner 'joe@oracle.com'
[2020-10-20T12:11:24.028] [INFO] default - 

============ Finished Invoke Chaincode ============

ERROR (CLI): Error invoking chaincode: 
obptools@ochain:~/fabcar$

So far we’ve been testing our chaincode by running it locally. In my next post, I’ll show how we can run and test this same smart contract on the Oracle Blockchain Platform.

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.