Skip to content

A Hyperledger Fabric Private Data Store in Multi Org Network, written in Go Fabric SDK

License

Notifications You must be signed in to change notification settings

Deeptiman/privateledger

Repository files navigation

Private Ledger

GitHub last commit GitHub language count GitHub top language

N|Solid

Private Ledger is a web application written in Go to demonstrate storing private data in Hyperleder fabric multi-org environment. The blockchain network consists of four organization joined with a single channel. The ledger data created in private collection, so that the data only accessible to the related organization unless the collection data is shared to other organization. And also, this repo will be demonstrate sharing the private collection data among the organizations.

However, this explanation guide does not explain how Hyperledger Fabric works, so for the information, you can follow at Hyperledger.

Medium writeup : https://medium.com/@deeptiman/confidentiality-and-private-data-in-hyperledger-fabric-1279c8e2e57f

Installation

Private Ledger requires Docker & Go to run.

Docker

$ sudo apt install docker.io
$ sudo apt install docker-compose

Go

Installation

$ sudo apt-get update
$ sudo apt-get install golang-go

Set your Go path as environmental variable

add these following variable into the profile
$ export GOPATH=$HOME/go
$ export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
then
$ source ~/.profile
$ go version
$ go version go1.11 linux/amd64

Setup the Host

  • Sometimes, in some of the local machine not able to identify or link the hyperledger endpoints with localhost or 127.0.0.1. So, it's better to add the hyperledger endpoints in /etc/hosts mapping with 127.0.0.1
  • 
      
        	127.0.0.1	orderer.private.ledger.com
    	127.0.0.1	ca.org1.private.ledger.com
    	127.0.0.1	peer0.org1.private.ledger.com
    	127.0.0.1	peer1.org1.private.ledger.com
    	127.0.0.1	peer0.org2.private.ledger.com
    	127.0.0.1	peer1.org2.private.ledger.com
    	127.0.0.1	peer0.org3.private.ledger.com
    	127.0.0.1	peer1.org3.private.ledger.com
    	127.0.0.1	peer0.org4.private.ledger.com
    	127.0.0.1	peer1.org4.private.ledger.com
      
      

Setup the Config

  • In a multi-org environment, few endorsement policies need to meet between all the organization to commit a transaction otherwise anybody can submit a transaction by signing as any member of the group.

  • The policies is mentioned in configtx.yaml

    • Readers, Writers & Admins all have a specific Rules with Signing type
  • Ex : "OR('Org1MSP.member')" means any memeber of Org1 can sign a transaction and commit blocks to the blockchain. There are various endorsing principals like admin, client, peer or member. The principals can be used based on the network architecture or business logic requirements.

  1. configtxgen tool will create the channel artifacts and four Anchor peers of the organizations.

  2. Anchor peers of all organization will be useful to communicate with each other so that any organization peer perform a transaction then other peers of the organization will get notified.

  3. All the CAs, Peers and CouchDB for all the Orgs need to be mention in the docker-compose.yaml. In this repo, you can find there are four docker-compose YAML files, which are just the extended files from the base docker-compose.yaml. I have done this for code readability and changing config will be simpler.

  4. Now the script to generate the artifacts mentioned below

config.sh

 

./bin/cryptogen generate --config=./crypto-config.yaml

./bin/configtxgen -profile FourOrgsOrdererGenesis -outputBlock ./artifacts/orderer.genesis.block

./bin/configtxgen -profile FourOrgsChannel -outputCreateChannelTx ./artifacts/privateledger.channel.tx -channelID privateledger

./bin/configtxgen -profile FourOrgsChannel -outputAnchorPeersUpdate ./artifacts/Org1MSPanchors.tx -channelID privateledger -asOrg Org1MSP

./bin/configtxgen -profile FourOrgsChannel -outputAnchorPeersUpdate ./artifacts/Org2MSPanchors.tx -channelID privateledger -asOrg Org2MSP

./bin/configtxgen -profile FourOrgsChannel -outputAnchorPeersUpdate ./artifacts/Org3MSPanchors.tx -channelID privateledger -asOrg Org3MSP

./bin/configtxgen -profile FourOrgsChannel -outputAnchorPeersUpdate ./artifacts/Org4MSPanchors.tx -channelID privateledger -asOrg Org4MSP

  1. Start the network

    docker-compose up --force-recreate -d 
    

This command will create the docker images for the organizations. It's needed to stop the existing running network and start the network as new, so I have mentioned the network down/up in a Makefile. Please use the make to run all at one instance.

Deploy the network

There are sequence steps to follow to completely setup the multi-org network.

  • Initialize SDKs for each Organziations.

    This steps will be called as an initial entry point to perform other activities in the network. Every organization will have its own SDK and will be initialized with config.yaml. The initialization process will create CA clients, MSP clients, Resource Management clients, signing identity.

1. Create Channel
  • The channel will create by Orderer and will use all the signing identities of the organization admins.
    
req := resmgmt.SaveChannelRequest{
    ChannelID: "multiorgledger", 
    ChannelConfigPath: os.os.Getenv("GOPATH")+"/multiorgledger.channel.tx", 
    SigningIdentities: []msp.SigningIdentity{Org1SignIdentity, Org2SignIdentity, Org3SignIdentity, Org4SignIdentity},
}

txID, err := s.Resmgmt.SaveChannel(
    req, resmgmt.WithOrdererEndpoint(Orderer.OrdererID))

if err != nil || txID.TransactionID == "" {
    return errors.WithMessage(err, "failed to save anchor channel for - "+s.OrgName)
}

  • Each organization admins will create Anchor peers for their organization using the anchor peer artifacts.
   
    req := resmgmt.SaveChannelRequest{
           ChannelID: "multiorgledger", 
           ChannelConfigPath: os.os.Getenv("GOPATH")+"/", //Org1MSPanchors.tx or Org2MSPanchors.tx or Org3MSPanchors.tx or Org4MSPanchors.tx
            SigningIdentities: []msp.SigningIdentity{Org1SignIdentity or Org2SignIdentity or Org3SignIdentity or Org4SignIdentity},
    }

    txID, err := s.Resmgmt.SaveChannel(
            req, resmgmt.WithOrdererEndpoint(Orderer.OrdererID))

    if err != nil || txID.TransactionID == "" {
            return errors.WithMessage(err, "failed to save anchor channel for - "+s.OrgName)
     }

2. Join Channel
  • After creating the channel, each anchor peer will join the channel and remember Orderer will only create the channel and he can't join the channel.

    s.Resmgmt - is the resource management client created during Org SDK initialization. So, each organization resource management client will execute the join channel action indivisually.

    
    

    if err := s.Resmgmt.JoinChannel(s.ChannelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(Orderer.OrdererID)); err != nil { return errors.WithMessage(err, "failed to make admin join channel") }

  • There are specific channel queries can be use to check if the peers of an organization has already join the channel or not.

    for this query specific peer details needs to be pass, so it will check for the channel join status.

     
    
        resp, err := orgResmgmt.QueryChannels(resmgmt.WithTargets(peer))
        if err != nil {
              fmt.Println("IsJoinedChannel : failed to Query >>> "+err.Error())
              return false, err
        }
    
        for _, chInfo := range resp.Channels {
              fmt.Println("IsJoinedChannel : "+chInfo.ChannelId+" --- "+s.ChannelID)
               if chInfo.ChannelId == s.ChannelID {
                        return true, nil
                }
        }
    

3. Install Chaincode
  • Installing chaincode for the organization will use the same chaincode id & version unless it requires to be different for an organization on specific circumstances.

  • InstallCCRequest will be the same for all organizations but the request will be used by the individual resource management client of the organizations. So, the chaincode will be installed in all the organization separately.

      
    
        req := resmgmt.InstallCCRequest{
                Name:    s.ChaincodeId,
                Path:    s.ChaincodePath,
                Version: s.ChainCodeVersion,
                Package: ccPkg,
        }
    
        _, err = s.Resmgmt.InstallCC(req,
                resmgmt.WithRetry(retry.DefaultResMgmtOpts))
    
        if err != nil {
           fmt.Println("failed to install chaincode : "+err.Error())
           return errors.WithMessage(err, "  failed to install chaincode")
        }
    

4. Instantiate Chaincode
  • Chaincode instantiation in a multi org environment will be done only once. It will be initiated by any organization peers and will specify certain endorsement policy considering all the organization member or peer or admin depending on the network configurations.

  • As this application specifically developed to store private data in the chaincode, so each organization will have its policy and collections.

  • For Example

    Collection : collectionOrg1
      Policy : "OR ('Org1MSP.member')"
    

    so this specifies that only and only "org1" can use the collectionOrg1 data. The data is completely restricted to other organizations. As this application mainly intended to store private data per organization and later share the data among organizations with certain access query. So, each organization will have there own private collection and policies and it will be added to the chaincode instantiation request.

This function will return the collection config object per organization, so later each collection config can be pass as an array to the chaincode instantiation.


func newCollectionConfig(colName, policy string, reqPeerCount, maxPeerCount int32, blockToLive uint64) (*cb.CollectionConfig, error) {

        p, err := cauthdsl.FromString(policy)

    	if err != nil {
            fmt.Println("failed to create newCollectionConfig : "+err.Error())
            return nil, err
        }
        cpc := &cb.CollectionPolicyConfig{
            Payload: &cb.CollectionPolicyConfig_SignaturePolicy{
                SignaturePolicy: p,
            },
        }
        return &cb.CollectionConfig{
            Payload: &cb.CollectionConfig_StaticCollectionConfig{
                StaticCollectionConfig: &cb.StaticCollectionConfig{
                    Name:              colName,
                    MemberOrgsPolicy:  cpc,
                    RequiredPeerCount: reqPeerCount,
                    MaximumPeerCount:  maxPeerCount,
                    BlockToLive:       blockToLive,
                },
            },
        }, nil
}

// The maximum no of block allocated to keep private data alive, after that the data will purge across the network 
blockToLive = 1000 

// Number of peer required to distribute the private data as a condition of the endorsement of the chaincode peerCount = 0 // This is the maxium number of peers allocated to a collection, if an endorsing peer goes down, then other peers are available maximumPeerCount = 3

collCfg1, _ := newCollectionConfig("collectionOrg1", "OR ('Org1MSP.member')", peerCount, maximumPeerCount, blockToLive) collCfg2, _ := newCollectionConfig("collectionOrg2", "OR ('Org2MSP.member')", peerCount, maximumPeerCount, blockToLive) collCfg3, _ := newCollectionConfig("collectionOrg3", "OR ('Org3MSP.member')", peerCount, maximumPeerCount, blockToLive) collCfg4, _ := newCollectionConfig("collectionOrg4", "OR ('Org4MSP.member')", peerCount, maximumPeerCount, blockToLive)

cfg := []*cb.CollectionConfig{ collCfg1, collCfg2, collCfg3, collCfg4}

this collection config specifies that each collection record is private to the organization and other organization will only get hash of the ledger data.

// Any one of organization resource management client can execute the instantiate query and their peers will be mention in the target. The chaincode policy will be adding all the member of the four organization

policy = "OR ('Org1MSP.member','Org2MSP.member','Org3MSP.member','Org4MSP.member')"     

ccPolicy, _ := cauthdsl.FromString(policy) // cauthdsl will convert the policy string to Policy object

resp, err := s.Resmgmt.InstantiateCC(
    s.ChannelID,
    resmgmt.InstantiateCCRequest{
       
        Name:       s.ChaincodeId,
        Path:       s.ChaincodePath,
        Version:    s.ChainCodeVersion,
        Args:       [][]byte{[]byte("init")},
        Policy:     ccPolicy,
        CollConfig: cfg,

},resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithTargets(orgPeers[0], orgPeers[1]))

5. Upgrade Chaincode
  • As the chaincode only be instantiated once, so if any changes made in the chaincode, then it will be upgraded with a new version code and keeping the chaincode name same (important) in the network.

    // Any one of organization resource management client can execute the upgrade query and their peers will be mention in the target.

    // Policy can be remain the same unless it requires modification based on your chaincode buiseness requirements.

    
    
    req := resmgmt.UpgradeCCRequest{
      Name: 	      s.ChaincodeId, 
      Version: 	    s.ChainCodeVersion, 
      Path: 	      s.ChaincodePath, 
      Args:  	      [][]byte{[]byte("init")},
      Policy: 	    ccPolicy,
      CollConfig:   cfg,    
    }
    
    resp, err := s.Resmgmt.UpgradeCC(s.ChannelID, req, resmgmt.WithRetry(retry.DefaultResMgmtOpts),resmgmt.WithTargets(orgPeers[0], orgPeers[1]))
    
    if err != nil {
      return errors.WithMessage(err, " >>>> failed to upgrade chaincode")
    }
    

6. Query Installed Chaincode
  • Chaincode installation can be checked by querying "QueryInstalledChaincodes", which requires the peer of the organization, so it will check whether the peer has chaincode installed or not.

      
    
      resp, err := resMgmt.QueryInstalledChaincodes(resmgmt.WithTargets(peer))
    
      if err != nil {
         return false, errors.WithMessage(err, "  QueryInstalledChaincodes for peer [%s] failed : "+peer.URL())
      }
      found := false
    
      for _, ccInfo := range resp.Chaincodes {
          fmt.Println("   "+orgID+" found chaincode "+ccInfo.Name+" --- "+ccName+ " with version "+ ccInfo.Version+" -- "+ccVersion)
      		if ccInfo.Name == ccName && ccInfo.Version == ccVersion {
              found = true
              break
         }
      }
    
      if !found {
      		fmt.Println("   "+orgID+" chaincode is not installed on peer "+ peer.URL())
            installedOnAllPeers = false
      }  
    

7. Query Instantiate Chaincode
  • Chaincode instantiation can be checked by querying "QueryInstantiatedChaincodes", which requires the peer of the organization, so it will check whether the peer has chaincode instantiated or not.
    
	chaincodeQueryResponse, err := resMgmt.QueryInstantiatedChaincodes(channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts),    resmgmt.WithTargets(peer))

	    if err != nil {
	        return false, errors.WithMessage(err, "  QueryInstantiatedChaincodes return error")
	    }
	    fmt.Println("\n   Found instantiated chaincodes on peer "+peer.URL())

	    found := false

	    for _, chaincode := range chaincodeQueryResponse.Chaincodes {
	        fmt.Println("   Found instantiated chaincode Name: "+chaincode.Name+", Version: "+chaincode.Version+", Path: "+chaincode.Path+" on peer "+peer.URL())
	        if chaincode.Name == ccName && chaincode.Version == ccVersion {
	            found = true
	            break
	        }
	   }

	    if !found {
	        fmt.Println("  "+ccName+" chaincode is not instantiated on peer "+ peer.URL())
	        installedOnAllPeers = false
	    } 

8. Affiliate an Org
  • This is most important in case CA registration. In Hyperledger fabric by default "org1 & org2" are affiliated as CA organization, so any client or peer wants to register or enroll into the network via CA can pass "org1 or org2" as an affiliated organization.

  • But in case of other organization like org3 & org4, they need to be affiliated using following CA Client API.

    
    
    	// to perform the query individual org ca client needs to be used  
    
    	affl := strings.ToLower(org) + ".department1"
    
    	_, err = caClient.AddAffiliation(&caMsp.AffiliationRequest{
    
    	      Name:   affl,
    	      Force:  true,
    	      CAName: caid,
    	})
    
    	if err != nil {
    		return fmt.Errorf("Failed to add affiliation for CA '%s' : %v ", caid, err)
    	}
    

    we can also check, whether the organization has the affiliation from the CA client by using the following API.

    
    
    	fRes, err := caClient.GetAffiliation(affl)
    
    	if afRes != nil && err != nil {
    
    		  fmt.Println("Affiliation Exists")
    
    		  AfInfo := afRes.AffiliationInfo
    		  CAName := afRes.CAName
    
    		  fmt.Println("AfInfo : " + AfInfo.Name)
    		  fmt.Println("CAName : " + CAName)
    	}
    

Dependency Issues

  1. Hyperledger fabric-sdk-go is still in development. If you do dep ensure for each Gopkg.toml in PrivateLedger and Chaincode, it will download the govendor folder for each module but it will have some compilation issues while building the project. I have corrected the error for both PrivateLedger and Chaincode folder.
  2. Please download the vendor folder and add it in your project repo.

    PrivateLedger - https://www.dropbox.com/s/ry1jmw0y9xliose/vendor.zip?dl=0

    Chaincode - https://www.dropbox.com/s/31nnqflpqwaywoa/vendor.zip?dl=0

  3. Add vendor folders at the location where Gopkg.toml file is located.

So, this concludes the essential multi-org network setup for a 4 organizations based network.

Run the application

  1. The application is developed consisting of two clients. ( REST and Web App)

  2. You can use the REST client at the server running at - http://localhost:4000

  3. And for Web App, you can use server running at - http://localhost:6000

License

This project is licensed under the MIT License

Releases

No releases published

Packages

No packages published