Skip to content

A Hyperledger Fabric Multi Org Demo, written in Go Fabric SDK

License

Notifications You must be signed in to change notification settings

Deeptiman/multiorgledger

Repository files navigation

Multi Org Ledger

GitHub last commit GitHub language count GitHub top language

N|Solid

Multi Org Ledger is a web application written in Go to demonstrate the Multi Org setup in a Hyperledger Fabric network. The blockchain network consists of four organization joined with a single channel. The ledger data created in an organization can be accessible by the other participating organization in the network.

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/a-multi-organization-application-in-hyperledger-fabric-8612cef5bbae

Installation

Multi Org 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.multi.org.ledger.com
        127.0.0.1	ca.org1.multi.org.ledger.com
        127.0.0.1	peer0.org1.multi.org.ledger.com
        127.0.0.1	peer1.org1.multi.org.ledger.com
        127.0.0.1	peer0.org2.multi.org.ledger.com
        127.0.0.1	peer1.org2.multi.org.ledger.com
        127.0.0.1	peer0.org3.multi.org.ledger.com
        127.0.0.1	peer1.org3.multi.org.ledger.com
        127.0.0.1	peer0.org4.multi.org.ledger.com
        127.0.0.1	peer1.org4.multi.org.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/multiorgledger.channel.tx -channelID multiorgledger

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

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

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

./bin/configtxgen -profile FourOrgsChannel -outputAnchorPeersUpdate ./artifacts/Org4MSPanchors.tx -channelID multiorgledger -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.

  • In this repo, the chaincode policy as mentioned below

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

    the policy specifies that any member of any one of the organization needs to sign a transaction before committing a block to the blockchain so another member of the organization will get a copy 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.

        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,
    
        },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,       
        }
    
        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)
                }
    

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

Dependency Issues

  1. Hyperledger fabric-sdk-go is still in development. If you do dep ensure for each Gopkg.toml in MultiOrg 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 MultiOrg and Chaincode folder.
  2. Please download the vendor folder and add it in your project repo.

    MultiOrg - 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.

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

Few Screenshots

Dashboard

History

Edit

License

This project is licensed under the MIT License