|
| 1 | +Writing Your First Application |
| 2 | +============================== |
| 3 | + |
| 4 | +The goal of this document is to show the tasks and provide a baseline for writing |
| 5 | +your first application against a Hyperledger Fabric network (a.k.a. Fabric). |
| 6 | + |
| 7 | +At the most basic level, applications on a blockchain network are what enable |
| 8 | +users to **query** a ledger (asking for specific records it contains), or to |
| 9 | +**update** it (adding records to it). |
| 10 | + |
| 11 | +Our application, composed in Javascript, leverages the node.js SDK to interact |
| 12 | +with the network (where our ledger exists). This tutorial will guide you through |
| 13 | +the three steps involved in writing your first application. |
| 14 | + |
| 15 | + **1. Starting a test fabric blockchain network.** We need some basic components |
| 16 | + in our Fabric in order to query and update the ledger. These components -- |
| 17 | + a peer node, ordering node and Certificate Authority -- serve as the backbone of |
| 18 | + our network; we'll also have a CLI container used for a few administrative commands. |
| 19 | + A single script will download and launch this test network. |
| 20 | + |
| 21 | + **2. Learning the parameters of the sample smart contract our app will use.** Our |
| 22 | + smart contracts contain various functions that allow us to interact with the ledger |
| 23 | + in different ways. For example, we can read data holistically or on a more granular |
| 24 | + level. |
| 25 | + |
| 26 | + **3. Developing the application to be able to query and update fabric records.** |
| 27 | + We provide two sample applications -- one for querying the ledger and another for |
| 28 | + updating it. Our app will use the SDK APIs to interact with the network and |
| 29 | + ultimately call these functions. |
| 30 | + |
| 31 | +After completing this tutorial, you should have a basic understanding of how |
| 32 | +an application, using the Fabric SDK for Node.js, is programmed in conjunction with a smart contract |
| 33 | +to interact with the ledger on a Fabric network. |
| 34 | + |
| 35 | +First, let's launch our test network... |
| 36 | + |
| 37 | +Getting a Test Network |
| 38 | +---------------------- |
| 39 | + |
| 40 | +Visit the :doc:`prereqs` page and ensure you have the necessary dependencies installed |
| 41 | +on your machine. |
| 42 | + |
| 43 | +Now determine a working directory where you want to clone the fabric-samples repo. Issue |
| 44 | +the clone command and change into the ``fabcar`` subdirectory |
| 45 | + |
| 46 | +.. code:: bash |
| 47 | +
|
| 48 | + git clone https://github.com/hyperledger/fabric-samples.git |
| 49 | + cd fabric-samples/fabcar |
| 50 | +
|
| 51 | +This subdirectory -- ``fabcar`` -- contains the scripts |
| 52 | +and application code to run the sample app. Issue an ``ls`` from |
| 53 | +this directory. You should see the following: |
| 54 | + |
| 55 | +.. code:: bash |
| 56 | +
|
| 57 | + chaincode invoke.js network package.json query.js startFabric.sh |
| 58 | +
|
| 59 | +Now use the ``startFabric.sh`` script to launch the network. |
| 60 | + |
| 61 | +.. note:: The following command downloads and extracts the Fabric docker images, so it |
| 62 | + will take a few minutes to complete. |
| 63 | + |
| 64 | +.. code:: bash |
| 65 | +
|
| 66 | + ./startFabric.sh |
| 67 | +
|
| 68 | +For the sake of brevity, we won't delve into the details of what's happening with |
| 69 | +this command. Here's a quick synopsis: |
| 70 | + |
| 71 | +* launches a peer node, ordering node, Certificate Authority and CLI container |
| 72 | +* creates a channel and joins the peer to the channel |
| 73 | +* installs smart contract (i.e. chaincode) onto the peer's file system and instantiates said chaincode on the channel; instantiate starts a chaincdoe container |
| 74 | +* calls the ``initLedger`` function to populate the channel ledger with 10 unique cars |
| 75 | + |
| 76 | +.. note:: These operations will typically be done by an organizational or peer admin. The script uses the |
| 77 | + CLI to execute these commands, however there is support in the SDK as well. |
| 78 | + Refer to the `Hyperledger Fabric Node SDK repo <https://github.com/hyperledger/fabric-sdk-node>`__ |
| 79 | + for example scripts. |
| 80 | + |
| 81 | +Issue a ``docker ps`` command to reveal the processes started by the ``startFabric.sh`` script. |
| 82 | +You can learn more about the details and mechanics of these operations in the |
| 83 | +:doc:`build_network` section. Here we'll just focus on the application. The following picture |
| 84 | +provides a simplistic representation of how the application interacts with the Fabric network. |
| 85 | + |
| 86 | +.. image:: images/AppConceptsOverview.png |
| 87 | + |
| 88 | +Alright, now that you’ve got a sample network and some code, let’s take a |
| 89 | +look at how the different pieces fit together. |
| 90 | + |
| 91 | +How Applications Interact with the Network |
| 92 | +------------------------------------------ |
| 93 | + |
| 94 | +Applications use **APIs** to invoke smart contracts (referred to in Fabric as "chaincode"). |
| 95 | +These smart contracts are hosted in the network and identified by name and version. |
| 96 | +For example, our chaincode container is titled - ``dev-peer0.org1.example.com-fabcar-1.0`` - where |
| 97 | +the name is ``fabcar``, the version is ``1.0`` and the peer it is running against is ``dev-peer0.org1.example.com``. |
| 98 | + |
| 99 | +APIs are accessible with a software development kit (SDK). For purposes of this |
| 100 | +exercise, we'll be using the `Hyperledger Fabric Node SDK |
| 101 | +<https://fabric-sdk-node.github.io/>`__ though there is also a Java SDK and |
| 102 | +CLI that can be used to develop applications. |
| 103 | + |
| 104 | +Querying the Ledger |
| 105 | +------------------- |
| 106 | +Queries are how you read data from the ledger. You can query for the value |
| 107 | +of a single key, multiple keys, or -- if the ledger is written in a rich data storage |
| 108 | +format like JSON -- perform complex searches against it (looking for all |
| 109 | +assets that contain certain keywords, for example). |
| 110 | + |
| 111 | +.. image:: images/QueryingtheLedger.png |
| 112 | + |
| 113 | +As we said earlier, our sample network has an active chaincode container and |
| 114 | +a ledger that has been primed with 10 different cars. We also have some |
| 115 | +sample Javascript code - ``query.js`` - in the ``fabcar`` directory that |
| 116 | +can be used to query the ledger for details on the cars. |
| 117 | + |
| 118 | +Before we take a look at how that app works, we need to install the SDK node |
| 119 | +modules in order for our program to function. From your ``fabcar`` directory, |
| 120 | +issue the following: |
| 121 | + |
| 122 | +.. code:: bash |
| 123 | +
|
| 124 | + npm install |
| 125 | +
|
| 126 | +.. note:: You will issue all subsequent commands from the ``fabcar`` directory. |
| 127 | + |
| 128 | +Now we can run our javascript programs. First, let's run our ``query.js`` |
| 129 | +program to return a listing of all the cars on the ledger. A function that |
| 130 | +will query all the cars, ``queryAllCars``, is pre-loaded in the app, |
| 131 | +so we can simply run the program as is: |
| 132 | + |
| 133 | +.. code:: bash |
| 134 | +
|
| 135 | + node query.js |
| 136 | +
|
| 137 | +It should return something like this: |
| 138 | + |
| 139 | +.. code:: json |
| 140 | +
|
| 141 | + Query result count = 1 |
| 142 | + Response is [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}}, |
| 143 | + {"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}}, |
| 144 | + {"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}}, |
| 145 | + {"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}}, |
| 146 | + {"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}}, |
| 147 | + {"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}}, |
| 148 | + {"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}}, |
| 149 | + {"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}}, |
| 150 | + {"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}}, |
| 151 | + {"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}] |
| 152 | +
|
| 153 | +These are the 10 cars. A black Tesla Model S owned by Adriana, a red Ford Mustang |
| 154 | +owned by Brad, a violet Fiat Punto owned by someone named Pari, and so on. The ledger |
| 155 | +is key/value based and in our implementation the key is ``CAR0`` through ``CAR9``. |
| 156 | +This will become particularly important in a moment. |
| 157 | + |
| 158 | +Now let's see what it looks like under the hood (if you'll forgive the pun). |
| 159 | +Use an editor (e.g. atom or visual studio) and open the ``query.js`` program. |
| 160 | + |
| 161 | +The inital section of the application defines certain variables such as chaincode ID, channel name |
| 162 | +and network endpoints: |
| 163 | + |
| 164 | +.. code:: bash |
| 165 | +
|
| 166 | + var options = { |
| 167 | + wallet_path : path.join(__dirname, './network/creds'), |
| 168 | + user_id: 'PeerAdmin', |
| 169 | + channel_id: 'mychannel', |
| 170 | + chaincode_id: 'fabcar', |
| 171 | + network_url: 'grpc://localhost:7051', |
| 172 | +
|
| 173 | +This is the chunk where we construct our query: |
| 174 | +
|
| 175 | +.. code:: bash |
| 176 | +
|
| 177 | + // queryCar - requires 1 argument, ex: args: ['CAR4'], |
| 178 | + // queryAllCars - requires no arguments , ex: args: [''], |
| 179 | + const request = { |
| 180 | + chaincodeId: options.chaincode_id, |
| 181 | + txId: transaction_id, |
| 182 | + fcn: 'queryAllCars', |
| 183 | + args: [''] |
| 184 | +
|
| 185 | +We define the ``chaincode_id`` variable as ``fabcar`` -- allowing us to target this specific chaincode -- and |
| 186 | +then call the ``queryAllCars`` function defined within that chaincode. |
| 187 | +
|
| 188 | +When we issued the ``node query.js`` command earlier, this specific function was |
| 189 | +called to query the ledger. However, this isn't the only function that we can pass. |
| 190 | +
|
| 191 | +To take a look at the others, navigate to the ``chaincode`` subdirectory and open |
| 192 | +``fabcar.go`` in your editor. You'll see that we have the following functions available |
| 193 | +to call - ``initLedger``, ``queryCar``, ``queryAllCars``, ``createCar`` and ``changeCarOwner``. |
| 194 | +Let's take a closer look at the ``queryAllCars`` function to see how it interacts with the |
| 195 | +ledger. |
| 196 | +
|
| 197 | +.. code:: bash |
| 198 | +
|
| 199 | + func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { |
| 200 | +
|
| 201 | + startKey := "CAR0" |
| 202 | + endKey := "CAR999" |
| 203 | +
|
| 204 | + resultsIterator, err := APIstub.GetStateByRange(startKey, endKey) |
| 205 | +
|
| 206 | +The function uses the Fabric's shim interface ``GetStateByRange`` to return |
| 207 | +ledger data between the args of ``startKey`` and ``endKey``. Those keys are |
| 208 | +defined as ``CAR0`` and ``CAR999`` respectively. Therefore, we could theoretically |
| 209 | +create 1,000 cars (assuming the keys are tagged properly) and a ``queryAllCars`` would |
| 210 | +reveal every one. |
| 211 | +
|
| 212 | +Below is a representation of how an app would call different functions in chaincode. |
| 213 | +
|
| 214 | +.. image:: images/RunningtheSample.png |
| 215 | +
|
| 216 | +We can see our ``queryAllCars`` function up there, as well as one called ``createCar`` that |
| 217 | +will allow us to update the ledger and ultimately append a new block to the chain. |
| 218 | +But first, let's do another query. |
| 219 | +
|
| 220 | +Go back to the ``query.js`` program and edit the constructor request to query |
| 221 | +a specific car. We'll do this by changing the function from ``queryAllCars`` |
| 222 | +to ``queryCar`` and passing a specific "Key" to the args parameter. Let's use |
| 223 | +``CAR4`` here. So our edited ``query.js`` program should now contain the |
| 224 | +following: |
| 225 | +
|
| 226 | +.. code:: bash |
| 227 | +
|
| 228 | + const request = { |
| 229 | + chaincodeId: options.chaincode_id, |
| 230 | + txId: transaction_id, |
| 231 | + fcn: 'queryCar', |
| 232 | + args: ['CAR4'] |
| 233 | +
|
| 234 | +Save the program and navigate back to your ``fabcar`` directory. Now run the |
| 235 | +program again: |
| 236 | +
|
| 237 | +.. code:: bash |
| 238 | +
|
| 239 | + node query.js |
| 240 | +
|
| 241 | +You should see the following: |
| 242 | +
|
| 243 | +.. code:: json |
| 244 | +
|
| 245 | + {"colour":"black","make":"Tesla","model":"S","owner":"Adriana"} |
| 246 | +
|
| 247 | +So we've gone from querying all cars to querying just one, Adriana's black Tesla |
| 248 | +Model S. Using the ``queryCar`` function, we can query against any key (e.g. ``CAR0``) and |
| 249 | +get whatever make, model, color, and owner correspond to that car. |
| 250 | +
|
| 251 | +Great. Now you should be comfortable with the basic query functions in the chaincode, |
| 252 | +and the handful of parameters in the query program. Time to update the ledger... |
| 253 | +
|
| 254 | +Updating the Ledger |
| 255 | +------------------- |
| 256 | +
|
| 257 | +Now that we’ve done a few ledger queries and added a bit of code, we’re ready to |
| 258 | +update the ledger. There are a lot of potential updates we could |
| 259 | +make, but let's just create a new car for starters. |
| 260 | +
|
| 261 | +Ledger updates start with an application generating a transaction proposal. |
| 262 | +Just like query, a request is constructed to identify the channel ID, |
| 263 | +function, and specific smart contract to target for the transaction. The program |
| 264 | +then calls the ``channel.SendTransactionProposal`` API to send the transaction proposal to the peer(s) |
| 265 | +for endorsement. |
| 266 | +
|
| 267 | +The network (i.e. endorsing peer) returns a proposal response, which the application uses |
| 268 | +to build and sign a transaction request. This request is sent to the ordering service by |
| 269 | +calling the ``channel.sendTransaction`` API. The ordering service will bundle the transaction |
| 270 | +into a block and then "deliver" the block to all peers on a channel for validation. (In our |
| 271 | +case we have only the single endorsing peer.) |
| 272 | +
|
| 273 | +Finally the application uses the ``eh.setPeerAddr`` API to connect to the peer's |
| 274 | +event listener port, and calls ``eh.registerTxEvent`` to register events associated |
| 275 | +with a specific transaction ID. This API allows the application to know the fate of |
| 276 | +a transaction (i.e. successfully committed or unsuccessful). Think of it as a notification mechanism. |
| 277 | +
|
| 278 | +.. note:: We don't go into depth here on a transaction's lifecycle. Consult the |
| 279 | + :doc:`txflow` documentation for lower level details on how a transaction |
| 280 | + is ultimately committed to the ledger. |
| 281 | +
|
| 282 | +The goal with our initial invoke is to simply create a new asset (car in this case). We |
| 283 | +have a separate javascript program - ``invoke.js`` - that we will use for these transactions. |
| 284 | +Just like query, use an editor to open the program and navigate to the codeblock where we |
| 285 | +construct our invocation: |
| 286 | +
|
| 287 | +.. code:: bash |
| 288 | +
|
| 289 | + // createCar - requires 5 args, ex: args: ['CAR11', 'Honda', 'Accord', 'Black', 'Tom'], |
| 290 | + // changeCarOwner - requires 2 args , ex: args: ['CAR10', 'Barry'], |
| 291 | + // send proposal to endorser |
| 292 | + var request = { |
| 293 | + targets: targets, |
| 294 | + chaincodeId: options.chaincode_id, |
| 295 | + fcn: '', |
| 296 | + args: [''], |
| 297 | + chainId: options.channel_id, |
| 298 | + txId: tx_id |
| 299 | +
|
| 300 | +You'll see that we can call one of two functions - ``createCar`` or ``changeCarOwner``. |
| 301 | +Let's create a red Chevy Volt and give it to an owner named Nick. We're up to ``CAR9`` |
| 302 | +on our ledger, so we'll use ``CAR10`` as the identifying key here. The updated codeblock |
| 303 | +should look like this: |
| 304 | +
|
| 305 | +.. code:: bash |
| 306 | +
|
| 307 | + var request = { |
| 308 | + targets: targets, |
| 309 | + chaincodeId: options.chaincode_id, |
| 310 | + fcn: 'createCar', |
| 311 | + args: ['CAR10', 'Chevy', 'Volt', 'Red', 'Nick'], |
| 312 | + chainId: options.channel_id, |
| 313 | + txId: tx_id |
| 314 | +
|
| 315 | +Save it and run the program: |
| 316 | +
|
| 317 | +.. code:: bash |
| 318 | +
|
| 319 | + node invoke.js |
| 320 | +
|
| 321 | +There will be some output in the terminal about Proposal Response and Transaction ID. However, |
| 322 | +all we're concerned with is this message: |
| 323 | +
|
| 324 | +.. code:: bash |
| 325 | +
|
| 326 | + The transaction has been committed on peer localhost:7053 |
| 327 | +
|
| 328 | +The peer emits this event notification, and our application receives it thanks to our |
| 329 | +``eh.registerTxEvent`` API. So now if we go back to our ``query.js`` program and call |
| 330 | +the ``queryCar`` function against an arg of ``CAR10``, we should see the following: |
| 331 | +
|
| 332 | +.. code:: bash |
| 333 | +
|
| 334 | + Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Nick"} |
| 335 | +
|
| 336 | +Finally, let's call our last function - ``changeCarOwner``. Nick is feeling generous and |
| 337 | +he wants to give his Chevy Volt to a man named Barry. So, we simply edit ``invoke.js`` |
| 338 | +to reflect the following: |
| 339 | +
|
| 340 | +.. code:: bash |
| 341 | +
|
| 342 | + var request = { |
| 343 | + targets: targets, |
| 344 | + chaincodeId: options.chaincode_id, |
| 345 | + fcn: 'changeCarOwner', |
| 346 | + args: ['CAR10', 'Barry'], |
| 347 | + chainId: options.channel_id, |
| 348 | + txId: tx_id |
| 349 | +
|
| 350 | +Execute the program again - ``node invoke.js`` - and then run the query app one final time. |
| 351 | +We are still querying against ``CAR10``, so we should see: |
| 352 | +
|
| 353 | +.. code:: bash |
| 354 | +
|
| 355 | + Response is {"colour":"Red","make":"Chevy","model":"Volt","owner":"Barry"} |
| 356 | +
|
| 357 | +Additional Resources |
| 358 | +-------------------- |
| 359 | +
|
| 360 | +The `Hyperledger Fabric Node SDK repo <https://github.com/hyperledger/fabric-sdk-node>`__ |
| 361 | +is an excellent resource for deeper documentation and sample code. You can also consult |
| 362 | +the Fabric community and component experts on `Hyperledger Rocket Chat <https://chat.hyperledger.org/home>`__. |
| 363 | +
|
| 364 | +.. Licensed under Creative Commons Attribution 4.0 International License |
| 365 | + https://creativecommons.org/licenses/by/4.0/ |
0 commit comments