Thursday, April 23, 2020

Hyperledger Fabric chaincode "strange bug" Part 2

According to the previous blog post, we learn that the getState() function would always return the state before the chaincode execution due to the logic of the transaction flow. If we want to query the intermediate state, we may need some utils to help us.


package killa

import (
 "github.com/hyperledger/fabric/core/chaincode/shim"
)

// StateManager is a bridge for fabric world state with cache ability
type StateManager struct {
 APIstub shim.ChaincodeStubInterface
 cache   map[string][]byte
}

// NewStateManager is the constructor of StateManager
func NewStateManager(APIstub shim.ChaincodeStubInterface) *StateManager {
 ret := new(StateManager)
 ret.APIstub = APIstub
 ret.cache = make(map[string][]byte)
 return ret
}

// GetState is used to get fabric world state
func (state *StateManager) GetState(key string) ([]byte, error) {
 ret := state.cache[key]
 if ret != nil {
  return ret, nil
 }
 return state.APIstub.GetState(key)
}

// PutState is used to set fabric world state
func (state *StateManager) PutState(key string, value []byte) error {
 err := state.APIstub.PutState(key, value)
 if err == nil {
  state.cache[key] = value
 }
 return err
}


So now we can use the StateManager to interact with the blockchain state instead.
func update(APIstub shim.ChaincodeStubInterface, args []string) peer.Response {
  state := killa.NewStateManager(APIstub)
  state.PutState("state", []byte(args[0]))
  bytes, _ := state.GetState("state")
  return shim.Success(bytes)
}

Monday, April 20, 2020

Hyperledger Fabric chaincode "strange bug" Part 1

When your application grows and your business logic becomes more and more complicated, have you tried the getState() function didn't return the value as you expected?

Here is a very simple chaincode function, the API store your argument to the blockchain, and return the latest value back.

func update(APIstub shim.ChaincodeStubInterface, args []string) peer.Response {
  APIstub.PutState("state", []byte(args[0]))
  bytes, _ := APIstub.GetState("state")
  return shim.Success(bytes)
}


Can you see what's going wrong?
Actually, it will return the state before it has been updated, not after. This "strange bug" is coming from people treat blockchain as a database, or they have a misunderstanding of how chaincode and fabric transactions work. According to the official document, the transaction flow can briefly define as 6 phases:
1. Initiate transaction
2. Execute transaction
3. Inspect proposal response

4. Assemble endorsements
5. Validate transaction
6. Update blockchain

You may discover that the chaincode execution happens in phase 2, while the update happens in phase 6. Therefore no matter how do you update the state in your chaincode, the SDK is going to get the state before this transaction happens, not the intermediate state.
As a fabric developer, we should always keep this flow in our mind, because it may cause many "strange bugs".