diff --git a/README.md b/README.md index cd2a282ec..f3978bd42 100644 --- a/README.md +++ b/README.md @@ -18,49 +18,63 @@ Chaincode is a piece of code that is deployed into a network of [Hyperledger fab # Implementing Your First Chaincode -#### Setting up the environment +## Setting Up Your Development Environment Before you get started, you should go [here](docs/setup.md) and build your chaincode development environment. When you come back, you'll have all the tools you need to get through this tutorial. -## Deploying chaincode to IBM Bluemix -1. Fork this repository to your github account (scroll up to the top and click **Fork**.) -![Register Example](imgs/fork.png) -2. Now clone your fork to your $GOPATH. +## Setting Up Your Development Pipeline - ``` +The following tasks take you through the process of building a pipeline that will allow you to build chaincode effectively. In short, your pipeline for iterating on chaincode will consist of the following steps: + - Make changes to the given chaincode on your local machine and check that the code compiles. + - Push your updates to Github. + - Deploy your updated chaincode to your Hyperledger network using fabric REST API. + - Test your chaincode using the fabric REST API + - Repeat + +1. Fork this repository to your github account. This can be accomplished quickly by scrolling up and clicking the 'Fork' button at the top of this repository. + + ![Fork Button Screenshot](imgs/fork.png) + + "Forking" the repository means creating a copy of this repository under your Github account. +2. Clone your fork into your $GOPATH. + + ```bash cd $GOPATH - mkdir -p src/github.com// - cd src/github.com// - git clone https://github.com//learn-chaincode.git + mkdir -p src/github.com// + cd src/github.com// + git clone https://github.com//learn-chaincode.git ``` -3. Notice that we have provided two different versions of the chaincode used in this tutorial: [Start](https://github.com/IBM-Blockchain/learn-chaincode/blob/master/start/chaincode_start.go) - the skeleton chaincode from which you will start developing, and [Finished](https://github.com/IBM-Blockchain/learn-chaincode/blob/master/finished/chaincode_finished.go) - the finished chaincode. + + Now, you have a copy of your fork on your machine. You will develop your chaincode by making changes to these local files, pushing them to your fork on Github, and then deploying the code on your blockchain network using the REST API on one of your peers. + +3. Notice that we have provided two different versions of the chaincode used in this tutorial: [Start](start/chaincode_start.go) - the skeleton chaincode from which you will start developing, and [Finished](finished/chaincode_finished.go) - the finished chaincode. 4. Make sure it builds in your local environment: - Open a terminal or command prompt - ``` - cd $GOPATH/src/github.com//learn-chaincode/start + ```bash + cd $GOPATH/src/github.com//learn-chaincode/start go build ./ ``` - - It should complete with no errors/text. If not, make sure that you have correctly followed all of the steps above, including [testing your install of Go](https://golang.org/doc/install#testing). + + - It should complete with no errors/text. If not, make sure that you have correctly installed Go per the [development environment setup instructions](docs/setup.md). -###Implementing the chaincode interface -The first thing you need to do is implement the chaincode shim interface in your golang code. -The three main functions are **Init**, **Invoke**, and **Query**. -All three functions have the same prototype; they take in a function name and an array of strings. +In order to turn a piece of Go code into chaincode, all you need to do is implement the chaincode shim interface. +The three functions you have to implement are **Init**, **Invoke**, and **Query**. +All three functions have the same prototype; they take in a 'stub', which is what you use to read from and write to the ledger, a function name and an array of strings. The main difference between the functions is when they will be called. -We will be building up to a working chaincode to create generic assets. +In this tutorial you will be building a chaincode to create generic assets. ###Dependencies The `import` statement lists a few dependencies that you will need for your chaincode to build successfully. - `fmt` - contains `Println` for debugging/logging. - `errors` - standard go error format. -- `github.com/hyperledger/fabric/core/chaincode/shim` - the code that interfaces your golang code with a peer. +- `github.com/hyperledger/fabric/core/chaincode/shim` - contains the definition for the chaincode interface and the chaincode stub, which you will need to interact with the ledger. ###Init() Init is called when you first deploy your chaincode. As the name implies, this function should be used to do any initialization your chaincode needs. -In our example, we use Init to configure the initial state of one variable on the ledger. +In our example, we use Init to configure the initial state of a single key value pair on the ledger. In your `chaincode_start.go` file, change the `Init` function so that it stores the first element in the `args` argument to the key "hello_world". @@ -79,15 +93,15 @@ func (t *SimpleChaincode) Init(stub *shim.ChaincodeStub, function string, args [ } ``` -This is done by using the shim function `stub.PutState`. -The first argument is the key as a string, and the second argument is the value as an array of bytes. -This function may return an error which our code inspects and returns if present. +This is done by using the stub function `stub.PutState`. +The function interprets the first argument sent in the deployment request as the value to be stored under the key 'hello_world' in the ledger. Where did this argument come from, and what is a deploy request? All will be explained after we finish implementing the chaincode interface. +If an error occurs because the wrong number of arguments was passed in or because something went wrong when writing to the ledger, then this function will return an error. Otherwise, it exits cleanly, returning nothing. ###Invoke() `Invoke` is called when you want to call chaincode functions to do real work. -Invocation transactions will be captured as blocks on the chain. +Invocations will be captured as a transactions, which get grouped into blocks on the chain. When you need to update the ledger, you will do so by invoking your chaincode. The structure of `Invoke` is simple. -It receives a `function` argument and based on this argument calls Go functions in the chaincode. +It receives a `function` and an array of arguments. Based on what function was passed in through the `function` parameter in the invoke request, `Invoke` will either call a helper function or return an error. In your `chaincode_start.go` file, change the `Invoke` function so that it calls a generic write function. @@ -103,7 +117,7 @@ func (t *SimpleChaincode) Invoke(stub *shim.ChaincodeStub, function string, args } fmt.Println("invoke did not find func: " + function) - return nil, errors.New("Received unknown function invocation") + return nil, errors.New("Received unknown function invocation: " + function) } ``` @@ -129,16 +143,16 @@ func (t *SimpleChaincode) write(stub *shim.ChaincodeStub, args []string) ([]byte } ``` -This `write` function should look similar to the `Init` change you just did. -One major difference is that you can now set the key and value for `PutState`. -This function allows you to store any key/value pair you want into the blockchain ledger. +You're probably thinking that this `write` function looks similar to `Init`. It is very similar. Both function check for a certain number of arguments and then write a key value pair to the ledger. +However, you'll notice that `write` uses two arguments, allowing you to pass in both the key and the value for the call to `PutState`. +Basically, this function allows you to store any key/value pair you want into the blockchain ledger. ###Query() -As the name implies, `Query` is called whenever you query your chaincode state. -Queries do not result in blocks being added to the chain. +As the name implies, `Query` is called whenever you query your chaincode's state. +Queries do not result in blocks being added to the chain, and you cannot use functions like `PutState` inside of `Query` or any helper functions it calls. You will use `Query` to read the value of your chaincode state's key/value pairs. -In your `chaincode_start.go` file, change the `Query` function so that it calls a generic read function. +In your `chaincode_start.go` file, change the `Query` function so that it calls a generic read function, similar to what you did in `Invoke`. ```go func (t *SimpleChaincode) Query(stub *shim.ChaincodeStub, function string, args []string) ([]byte, error) { @@ -150,11 +164,11 @@ func (t *SimpleChaincode) Query(stub *shim.ChaincodeStub, function string, args } fmt.Println("query did not find func: " + function) - return nil, errors.New("Received unknown function query") + return nil, errors.New("Received unknown function query: " + function) } ``` -Now that it’s looking for `read`, make that function somewhere in your `chaincode_start.go` file. +Now that it’s looking for `read`, let's create that helper function somewhere in your `chaincode_start.go` file. ```go func (t *SimpleChaincode) read(stub *shim.ChaincodeStub, args []string) ([]byte, error) { @@ -176,15 +190,14 @@ func (t *SimpleChaincode) read(stub *shim.ChaincodeStub, args []string) ([]byte, } ``` -This `read` function is using the complement to `PutState` called `GetState`. -This shim function just takes one string argument. -The argument is the name of the key to retrieve. +This `read` function is using the complement to `PutState` called `GetState`. While `PutState` allows you to set a key value pair, `GetState` lets you read the value for a previously written key. +You can see that the single argument used by this function is taken as the key for the value that should be retrieved. Next, this function returns the value as an array of bytes back to `Query`, who in turn sends it back to the REST handler. ### Main() Finally, you need to create a short `main` function that will execute when each peer deploys their instance of the chaincode. -It just starts the chaincode and registers it with the peer. -You don’t need to add any code for this function. Both chaincode_start.go and chaincode_finished.go have a `main` function that lives at the top of the file. The function looks like this: +It just calls `shim.Start()`, which sets up the communication between this chaincode and the peer that deployed it. +You don’t need to add any code for this function. Both `chaincode_start.go` and `chaincode_finished.go` have a `main` function that lives at the top of the file. The function looks like this: ```go func main() { @@ -196,160 +209,177 @@ func main() { ``` ### Need Help? -If you're stuck or confused at any point, just go check out the chaincode_finished.go file. Use this file to validate that the code snippets you're building into chaincode_start.go are correct. +If you're stuck or confused at any point, just go check out the `chaincode_finished.go` file. Use this file to validate that the code snippets you're building into chaincode_start.go are correct. #Interacting with Your First Chaincode The fastest way to test your chaincode is to use the rest interface on your peers. -We’ve included a Swagger UI in the dashboard for your service instance that allows you to experiment with deploying chaincode without needing to write any additional code. - -###Swagger API -The first step is to find the api swagger page. - -1. Login to [IBM Bluemix](https://console.ng.bluemix.net/login) -1. You probably landed on the Dashboard, but double check the top nav bar. Click the "Dashboard" tab if you are not already there. -1. Also make sure you are in the same Bluemix "space" that contains your IBM Blockchain service. The space navigation is on the left. -1. There is a "Services" panel on this Bluemix dashboard near the bottom. Look through your services and click your IBM Blockchain service square. -1. Now you should see a white page with the words "Welcome to the IBM Blockchain..." and there should be a teal "LAUNCH" button on the right, click it. -1. You are on the monitor page and you should see two tables, though the bottom one may be empty. - - Noteworthy information on the network tab: - - **Peer Logs** will be found in the top table. Find the row for peer 1 and then click the file-like icon in the last row. - - It should have opened a new window. Congratulations you found your peer logs! - - In addition to this static view there are live-streaming peer logs in the **View Logs** tab near the top of the page. - - **ChainCode Logs** will be found in the bottom table. There is one row for every chaincode, and they are labeled using the same chaincode hash that was returned to you when it was deployed. Find the chaincode ID you want, and then select the peer. Finally click the file-like icon. - - It should have opened a new window. Congratulations you've found your peer's chaincode logs! - - **Swagger Tab** is the one labeled **APIs**. Click it to see the API interactive documentation. - - You are now on your Swagger API page. - -###Secure Enrollment -Calls to the `/chaincode` endpoint of the rest interface require a secure context ID. -This means that you must pass in a registered enrollID from the service credentials list in order for most REST calls to be accepted. -- Click the link **+ Network's Enroll IDs** to expand a list of enrollIDs and their secrets for your network. -- Open up a notepad and copy one set of credentials. You will need them later. -- Expand the "Registrar" API section by clicking it -- Expand the `POST /registrar` section by clicking it -- Set the body's text field. It should be JSON that contains an enrollID and secret from your list above. Example: +If you're using the blockchain service on Bluemix, you should follow the steps described [here](https://new-console.ng.bluemix.net/docs/services/blockchain/ibmblockchain_tutorials.html). Otherwise, we recommend using a tool like Postman, as described in the [environment setup documentation](docs/setup.md). +There are two rest endpoints we will be interacting with: `/chaincode` and `/registrar`. + - `/chaincode` is the endpoint used for deploying, invoking, and querying chaincode. Which operation you perform is controlled by the body of the request that you send. + - `/registrar` allows you to enroll users. Why does this matter? Read on! + +### Secure Enrollment +Calls to the `/chaincode` endpoint of the rest interface require a secure context ID to be included in the body of the request. +This means that you must first enroll a user from the user list in the membership service for your network. +- Find an available user to enroll on one of your peers. This will most likely require you to grab a user from the `membersrvc.yaml` file for your network. Look for the section that has a list of users like this: + + ``` + ... + test_user0: 1 MS9qrN8hFjlE bank_a 00001 + test_user1: 1 jGlNl6ImkuDo institution_a 00007 + test_user2: 1 zMflqOKezFiA bank_c 00008 + ... + ``` - -![Register Example](https://raw.githubusercontent.com/IBM-Blockchain/learn-chaincode/master/imgs/registrar.PNG) - -![Register Example](https://raw.githubusercontent.com/IBM-Blockchain/learn-chaincode/master/imgs/register_response.PNG) - - -If you didn't receive a "Login successful" response, go back and make sure you properly copied your enrollID and secret. Now that you have enrollID set up, you can use this ID when deploying, invoking, and querying chaincode in the subsequent steps. - -###Deploying the chaincode -In order to deploy chaincode through the rest interface, you will need to have the chaincode stored in a public git repository. +- Open up a notepad and copy one set of credentials. You will need them later. + + ``` + test_user0 MS9qrN8hFjlE + ``` + +- Create a POST request like the example below. + + ![/registrar POST](imgs/registrar_post.png) + + The url indicates that the REST port for one of my peers is accessible at `b88037dd5b6d423caf5258c6b7b15f5a-vp3.dev.blockchain.ibm.com:443` +- The body for the request: + + ```json + { + "enrollId": "", + "enrollSecret": "" + } + ``` + +- Send the request. If everything goes smoothly, you will see a response like the one below + + ![/registrar response](imgs/registrar_post_response.PNG) + + If you didn't receive a "Login successful" response, go back and make sure you properly copied your enrollment ID and secret. Now, you have an ID that you can use when deploying, invoking, and querying chaincode in the subsequent steps. + +### Deploying the chaincode +In order to deploy chaincode through the REST interface, you will need to have the chaincode stored in a public git repository. When you send a deploy request to a peer, you send it the url to your chaincode repository, as well as the parameters necessary to initialize the chaincode. **Before you deploy** the code, make sure it builds locally! - Open terminal/command prompt -- Browse to the folder that contains `chaincode_start.go` and type: +- Browse to the folder that contains `chaincode_start.go` and try to build your chaincode: - ``` - go build ./ - ``` -- It should return with no errors/text - - -- Expand the "Chaincode" API section by clicking it -- Expand the `POST /chaincode` section by clicking it -- Set the `DeploySpec` text field (make the other fields blank). Copy the example below but substitute in your chaincode repo path. Also use the same enrollID you used in the `/registrar` step. -- The `"path":` will look something like this `"https://github.com/johndoe/learn-chaincode/finished"`. It's the path of your fork and then one directory down, where our chaincode_finished.go file lives. - - ```js - { - "jsonrpc": "2.0", - "method": "deploy", - "params": { - "type": 1, - "chaincodeID": { - "path": "https://github.com/johndoe/learn-chaincode/finished" - }, - "ctorMsg": { - "function": "init", - "args": [ - "hi there" - ] - }, - "secureContext": "user_type1_191b8c2993" - }, - "id": 1 - } - ``` - -The response should look like: - -![Deploy Example](https://raw.githubusercontent.com/IBM-Blockchain/learn-chaincode/master/imgs/deploy_response.PNG) - -The response for the deployment will contain an ID that is associated with this chaincode. The ID is a 128 character alphanumeric hash. Copy this ID on your notepad as well. You should now have a set of enrollID credentials and the cryptographic hash representing your chaincode. + ```bash + cd $GOPATH/src/github.com//learn-chaincode/start + go build ./ + ``` + +- It should return with no errors/text. This indicates that your chaincode compiled successfully. A good omen. + +- Create a POST request like the example below. + + ![/chaincode deploy example](imgs/deploy_example.png) + +- The body for the request: + + ```json + { + "jsonrpc": "2.0", + "method": "deploy", + "params": { + "type": 1, + "chaincodeID": { + "path": "https://github.com//learn-chaincode/finished" + }, + "ctorMsg": { + "function": "init", + "args": [ + "hi there" + ] + }, + "secureContext": "" + }, + "id": 1 + } + ``` + +- The `"path":` is the path to your fork of the repository on Github, going one more directory down into `/finished`, where your `chaincode_finished.go` file lives. +- Send the request. If everything goes smoothly, you will see a response like the one below + + ![/chaincode deploy response](imgs/deploy_response.PNG) + +The long string response for the deployment will contain an ID that is associated with this chaincode. The ID is a 128 character alphanumeric hash. Copy this ID on your notepad as well. You should now have a set of enrollID credentials and the cryptographic hash representing your chaincode. This is how you will reference the chaincode in any future invoke or query requests. -###Query -Next, let’s query the chaincode for the value of the `hello_world` key we set with the `Init` function. -- Expand the "Chaincode" API section by clicking it -- Expand the `POST /chaincode` section by clicking it -- Set the `QuerySpec` text field (make the other fields blank). Copy the example below but substitute in your chaincode name (the hashed ID from deploy). Also use the same enrollID you used in the `/registrar` step. - - ```js - { - "jsonrpc": "2.0", - "method": "query", - "params": { - "type": 1, - "chaincodeID": { - "name": "CHAINCODE_HASH_HERE" - }, - "ctorMsg": { - "function": "read", - "args": [ - "hello_world" - ] - }, - "secureContext": "user_type1_xxxxxxxxx" - }, - "id": 2 - } - ``` - -![Query Example](https://raw.githubusercontent.com/IBM-Blockchain/learn-chaincode/master/imgs/query_response.PNG) - -Hopefully you see that the value of `hello_world` is "hi there". -This was set by the body of the deploy call you sent earlier. - -###Invoke -Next, call your generic write function with `invoke`. -Change the value of `hello_world` to "go away". -- Expand the "Chaincode" API section by clicking it. -- Expand the `POST /chaincode` section by clicking it. -- Set the `InvokeSpec` text field (make the other fields blank). Copy the example below but substitute in your chaincode name (the hashed ID from deploy). Also use the same enrollID you used in the `/registrar` step. - - ```js - { - "jsonrpc": "2.0", - "method": "invoke", - "params": { - "type": 1, - "chaincodeID": { - "name": "CHAINCODE_HASH_HERE" - }, - "ctorMsg": { - "function": "write", - "args": [ - "hello_world", - "go away" - ] - }, - "secureContext": "user_type1_xxxxxxxxx" - }, - "id": 3 - } - ``` - -![Invoke Example](https://raw.githubusercontent.com/IBM-Blockchain/learn-chaincode/master/imgs/invoke_response.PNG) - -Now to test if it's stuck, just re-run the query above. - -![Query2 Example](https://raw.githubusercontent.com/IBM-Blockchain/learn-chaincode/master/imgs/query2_response.PNG) +### Query + +Next, let’s query the chaincode for the value of `hello_world`, the key we set with the `Init` function. +- Create a POST request like the example below. + + ![/chaincode query example](imgs/query_example.png) + +- The body for the request: + + ```json + { + "jsonrpc": "2.0", + "method": "query", + "params": { + "type": 1, + "chaincodeID": { + "name": "" + }, + "ctorMsg": { + "function": "read", + "args": [ + "hello_world" + ] + }, + "secureContext": "" + }, + "id": 2 + } + ``` + +- Send the request. If everything goes smoothly, you will see a response like the one below + + ![/chaincode query response](imgs/query_response.PNG) + +Hopefully you see that the value of `hello_world` is "hi there", as you specified in the body of the deploy request. + +### Invoke + +Next, call your generic `write` function by invoking your chaincode, changing the value of `hello_world` to "go away". +- Create a POST request like the example below. + + ![/chaincode invoke example](imgs/invoke_example.png) + +- The body for the request: + + ```json + { + "jsonrpc": "2.0", + "method": "invoke", + "params": { + "type": 1, + "chaincodeID": { + "name": "" + }, + "ctorMsg": { + "function": "write", + "args": [ + "hello_world", "go away" + ] + }, + "secureContext": "" + }, + "id": 3 + } + ``` + +- Send the request. If everything goes smoothly, you will see a response like the one below + + ![/chaincode invoke response](imgs/invoke_response.PNG) + +- Test if our change stuck by sending another query like the one from before. + + ![/chaincode query2 response](imgs/query2_response.PNG) That’s all it takes to write basic chaincode. diff --git a/imgs/deploy_example.PNG b/imgs/deploy_example.PNG new file mode 100644 index 000000000..a96e11738 Binary files /dev/null and b/imgs/deploy_example.PNG differ diff --git a/imgs/deploy_response.PNG b/imgs/deploy_response.PNG index 190f4240d..100579b27 100644 Binary files a/imgs/deploy_response.PNG and b/imgs/deploy_response.PNG differ diff --git a/imgs/invoke_example.PNG b/imgs/invoke_example.PNG new file mode 100644 index 000000000..c2792b007 Binary files /dev/null and b/imgs/invoke_example.PNG differ diff --git a/imgs/invoke_response.PNG b/imgs/invoke_response.PNG index 06400d4ed..825ced1b4 100644 Binary files a/imgs/invoke_response.PNG and b/imgs/invoke_response.PNG differ diff --git a/imgs/query2_response.PNG b/imgs/query2_response.PNG index ced616f40..211d2911b 100644 Binary files a/imgs/query2_response.PNG and b/imgs/query2_response.PNG differ diff --git a/imgs/query_example.PNG b/imgs/query_example.PNG new file mode 100644 index 000000000..6ecc964ac Binary files /dev/null and b/imgs/query_example.PNG differ diff --git a/imgs/query_response.PNG b/imgs/query_response.PNG index 08409f7f5..89d5c8806 100644 Binary files a/imgs/query_response.PNG and b/imgs/query_response.PNG differ diff --git a/imgs/register_response.PNG b/imgs/register_response.PNG deleted file mode 100644 index 0721b1366..000000000 Binary files a/imgs/register_response.PNG and /dev/null differ diff --git a/imgs/registrar.PNG b/imgs/registrar.PNG deleted file mode 100644 index 4ed0cf1b3..000000000 Binary files a/imgs/registrar.PNG and /dev/null differ diff --git a/imgs/registrar_post.png b/imgs/registrar_post.png new file mode 100644 index 000000000..05a4e9d3c Binary files /dev/null and b/imgs/registrar_post.png differ diff --git a/imgs/registrar_post_response.png b/imgs/registrar_post_response.png new file mode 100644 index 000000000..032607af9 Binary files /dev/null and b/imgs/registrar_post_response.png differ