유용한 정보

Fabric에 대해 살펴보겠습니다.(3편)

CyberI 2018. 6. 7. 14:25

앞의 내용에서 Hyperledger Fabric을 사용하기 위한 환경 설정을 하였습니다. 이제 Fabric에서 Smartcontract의 기능을 하는 Chaincode Fabric-Java-SDK에 대해서 알아보도록 하겠습니다

Hyperledger Fabric 시리즈물을 살펴보고 싶다면, 글 하단의 링크를 참고해주세요.

 

 

1. ChainCode

Chaincode 지정된 인터페이스를 구현하는 Go 작성된 프로그램 입니다. Chaincode는 검증된 피어 프로세스에서 실행됩니다. 지금 이 환경의 경우 Peer로 생성된 Docker내에서 실행되며 Chaincode는 응용 프로그램에서 제출 한 트랜잭션을 통해 원장 상태를 초기화 하거나 관리합니다

 

2. Chaincode 작성

 

모든 체인 코드 프로그램은 수신 된 트랜잭션에 대한 응답으로 메소드가 호출되는 방식으로 되어있습니다. 특히, 체인 코드가 응용 프로그램 상태의 초기화를 수행 할 수 있도록 체인 코드가 인스턴스화 또는 업그레이드 트랜잭션을 수신 할 때 Init 메서드가 호출됩니다. Invoke 메서드는 트랜잭션 제안을 처리하기 위해 호출 트랜잭션 수신에 대한 응답으로 호출됩니다.

Chaincode를 작성하기에 앞서 Chaincode에 필요한 패키지를 import문을 통해 추가합니다.

 package main

 

 import (

             "bytes"

             "encoding/json"

             "fmt"

             "strconv"

 

             "github.com/hyperledger/fabric/core/chaincode/shim"

             "github.com/hyperledger/fabric/protos/peer"

 )

import 된 패키지들에 대해서 보자면 bytes에 대한 패키지와 json에 대한 패키지, 출력하기 위한 패키지, 형변환을 위한 패키지가 상단에 적혀 있으며 아래 두줄에 대한 패키지는 Chaincode shim패키지와 peer패키지 입니다. shim 패키지는 Chaincode원장의 상태에 접속하거나, 트랜잭션에 액세스하고 다른 체인 코드를 출하는 API 제공합니다. 링크를 통해 document를 볼 수 있습니다.

 

다음은 Init 함수 입니다.

 func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {

             return shim.Success(nil)

 }

Init함수는 Chaincode 인스턴스화 중에 호출되는 함수이며 필요에 따라 Init함수를 수정하여 사용하면 됩니다.
필수적으로 초기화를 해야 한다거나 미리 작업을 할 것이 있는 경우에는 Init함수를 이용하면 되나 미리 할 작업이 없는 경우 위와 같이 빈 값으로 리턴하는 식으로 사용하면 될 것 같습니다.

 

다음으로는 Invoke 함수를 보도록 하겠습니다.

 func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {

             function, args := APIstub.GetFunctionAndParameters()

             if function == "queryBoard" {

                           return s.queryBoard(APIstub, args)

             } else if function == "initLedger" {

                           return s.initLedger(APIstub)

             } else if function == "recordBoard" {

                           return s.recordBoard(APIstub, args)

             } else if function == "queryAllBoard" {

                           return s.queryAllBoard(APIstub)

             } else if function == "modifyBoard" {

                           return s.modifyBoard(APIstub, args)

             } else if function == "rangeBoard" {

                           return s.rangeBoard(APIstub, args)

             } else if function == "delBoard" {

                           return s.delBoard(APIstub, args)

             } else if function == "upHitCount" {

                           return s.upHitCount(APIstub, args)

             }

 

             return shim.Error("Invalid Smart Contract function name.")

 }

Invoke 함수는 트랜잭션당 한번씩 호출되는 Chaincode API의 함수입니다. Chaincod API shim에서 제공하는 GetFunctionAndParameters를 통해서 같이 넘어온 파라미터를 받아서 필요한 함수 등을 구현하여 사용하고 있습니다

function, args := APIstub.GetFunctionAndParameters()

Go언어에서는 하나의 함수를 호출하더라도 여러 개의 리턴 값을 받을 수 구현 할 수 있으며 이 GetFunctionAndParameters역시 2개의 리턴 값을 받을 수 있는 함수 입니다. 여기서 function은 호출될 함수명, args는 같이 넘어온 데이터를 담고 있으며 이렇게 받아온 데이터를 이용하여 필요한 부분을 구현 및 호출하도록 구성되어 있습니다.

아래의 소스는 호출되는 몇가지 함수들에 대한 내용입니다.

 

 func (s *SmartContract) queryBoard(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

             if len(args) != 1 {

                           return shim.Error("Incorrect number of arguments. Expecting 1")

             }

 

             boardAsBytes, _ := APIstub.GetState(args[0])

             if boardAsBytes == nil {

                           return shim.Error("Could not locate board")

             }

             return shim.Success(boardAsBytes)

 }

 

 func (s *SmartContract) recordBoard(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

             if len(args) != 9 {

                           return shim.Error("Incorrect number of arguments. Expecting 9")

             }

             var text string

             var bufferNum int = 0

             text = getCount(APIstub)

             bufferNum, _ = strconv.Atoi(text)

 

             bufferNum = bufferNum + 1

             text = strconv.Itoa(bufferNum)

             var board = Board{Key: text, Title: args[1], Content: args[2], Writer: args[3],

 RegDd: args[4], Hit: args[5], BoardTp: args[6], Depth: args[7], DelYn: args[8]}

             boardAsBytes, _ := json.Marshal(board)

             err := APIstub.PutState(text, boardAsBytes)

             if err != nil {

                           return shim.Error(fmt.Sprintf("Failed to record board catch: %s", text))

             }

             return shim.Success(nil)

 }

 

APIstub.GetState , APIstub.PutState 이 부분이 shim API를 이용하여 fabric에 데이터를 넣고, 가져오는 등의 작업을 진행하게 됩니다. 이 함수들 이외에도 많은 여러 함수가 있으니 document 사이트를 참고하여 필요한 함수를 사용하면 될 것 같습니다. 해당 값은 shim API Success라는 함수를 이용하게 되며 해당 값으로는 bytes형식의 데이터를 이용하여 값을 전달하여 주고 있습니다.

 

3. Fabric Java SDK를 이용한 Client 소스

 

위의 부분은 Server에서 동작하는 작업에 대해 언급한 것이라면 아래의 내용은 Client에서 사용하는 방법에 대해서 적어보도록 하겠습니다.
우선 Fabric을 이용하기 위해서 역시 마찬가지로 필요한 것이 있는데 Fabric Java SDK를 필요로 합니다. 설치 방법은 여러 가지 방법이 있으므로 필요에 따라 맞게 사용하기면 될 것 같습니다.

그림 7-Fabric Java SDK 다운로드(링크)

(Spring을 이용하여 진행하였기 때문에 Maven dependency를 이용하여 pom.xml파일에 추가하는 것으로 간단하게 SDK를 사용하였습니다.)

 

아래의 소스는 SDK를 이용하여 Fabric을 이용하는 방법에 대해 나열한 것입니다.

 private static final Logger log = Logger.getLogger(Program.class);

 public HFCAClient caClient = null;

 public AppUser admin = null;

 public AppUser appUser = null;

 public HFClient client = null;

 public Channel channel = null;

   

 public Program() throws Exception {

        caClient = getHfCaClient("http://192.168.0.190:7054", null);

        admin = getAdmin(caClient);

        client = getHfClient();

        client.setUserContext(admin);

        channel = getChannel(client);

 }

 static HFCAClient getHfCaClient(String caUrl, Properties caClientProperties) throws Exception {

        CryptoSuite cryptoSuite = CryptoSuite.Factory.getCryptoSuite();

        HFCAClient caClient = HFCAClient.createNewInstance(caUrl, caClientProperties);

        caClient.setCryptoSuite(cryptoSuite);

        return caClient;

    }

우선 이 소스는 JAVA에서 작성된 소스로 Peer가 떠있는 URL를 통해 인증서 및 여러 정보에 관한 데이터를 가져와 Fabric JAVA API를 통해 Fabric을 사용할 수 있도록 초기 설정을 하는 부분입니다.
getHfCaClient
에 있는
HFCAClient(Fabric JAVA API에서 지원)를 통해 받은 affiliation, enrollment, mspid, channel등 여러 정보를 받아와 사용하게 됩니다.

 

위 소스로 Fabric을 사용할 수 있게 설정 하였다면 아래의 소스는 사용하는 예시 소스입니다

 public String queryAllBoard(HFClient client) throws ProposalException, InvalidArgumentException {

        String stringResponse = null;

        Channel channel = client.getChannel("mychannel");

        QueryByChaincodeRequest qpr = client.newQueryProposalRequest();

        ChaincodeID fabBoardCCId = ChaincodeID.newBuilder().setName("board-app").build();

        qpr.setChaincodeID(fabBoardCCId);

        qpr.setFcn("queryAllBoard");

        Collection<ProposalResponse> res = channel.queryByChaincode(qpr);

        for (ProposalResponse pres : res) {

            stringResponse = new String(pres.getChaincodeActionResponsePayload());

            log.info(stringResponse);

        }

        return stringResponse;

    }

 

QueryByChaincodeRequest를 통해 사용할 channel, chaincode id 등을 지정 후 호출 할 함수명을 포함한 후 channel.queryByChaincode(qpr)를 통해 호출하게 됩니다.
여기서 QueryByChaincodeRequest는 조회성 데이터에 대한 처리를 할 때 주로 사용하게 됩니다. DB의 쿼리로 치자면 Select와 같은 조회에 관한 처리를 할 때 사용하는 함수입니다.

 public String createBoard(HFClient client, String[] val) throws ProposalException, InvalidArgumentException {

            String stringResponse = null;

            String tx_id = null;

            Channel channel = client.getChannel("mychannel");

            TransactionProposalRequest tpr = client.newTransactionProposalRequest();

            ChaincodeID fabBoardCCId = ChaincodeID.newBuilder().setName("board-app").build();

            tpr.setChaincodeID(fabBoardCCId);

            tpr.setFcn("recordBoard");

            tpr.setArgs(val);

            Collection<ProposalResponse> responses = channel.sendTransactionProposal(tpr);

            for (ProposalResponse response : responses) {

                          stringResponse = new String(response.getChaincodeActionResponsePayload());

            log.info(stringResponse);

            }

            return channel.sendTransaction(responses).toString();

    }

이 소스는 입력하는 소스입니다. 여기서는 QueryByChaincodeRequest와 달리 TransactionProposalRequest을 사용하는데 사용하는 방법은 동일하지만 Insert Update의 경우에 이 TransactionProposalRequest를 이용하여 작업을 진행하게 됩니다. 여기서도 마찬가지로 channel, chaincode id, 호출 할 함수명, 입력할 데이터 등을 포함하여 호출하게 됩니다. 여기서 데이터는 배열로 된 데이터를 보내게 되기 때문에 정렬 작업이나 지정된 순서의 데이터로 보내야 할 필요가 있습니다.

 

4. Fabric을 이용한 게시판 작성 시 문제점

위의 예제 소스들이 이 게시판을 작성하면서 쓰여진 내용입니다. 이 내용을 바탕으로 진행한 Fabric을 이용한 게시판에 대해서 설명 해드리고자 합니다.

 

총 글 개수에 대한 문제입니다. shim API를 통해 지정된 Index의 값을 가져오거나 쓰기, 수정 등의 작업은 가능하지만 API에서 제공되는 함수 중 쓰여진 글의 개수를 가져오는 작업을 따로 구현해야 하는 필요가 있다는 점입니다.

그림 8 - 글 개수 가져오는 작업

이와 같이 Chaincode로 글 개수를 가져오는 소스를 구현하였습니다. index가 되는 값을 지정하여 0부터 999까지의 값을 키 값으로 가진 fabric 노드의 데이터를 가져온 후(GetStateByRange라는 함수를 통해) 반복문을 통해 카운팅하는 방법으로 구현하였습니다.
하지만 여기서도 문제가 있는데 많은 양의 게시글이 있는 게시판의 경우 위와 같이 단순히 반복문을 이용하여 총 개수를 가져오는 것 자체가 부하가 될 것이라고 생각됩니다. 또 다른 문제로는 소스 중 endKey라는 값이 999로 지정되어있는 것을 볼 수 있습니다. key값이 0번인 데이터부터 999번인 데이터까지를 가져와서 작업을 진행한다는 뜻으로 볼 수 있습니다.

위와 같이 특정한 작업에 대한 API를 지원해주지 않아 Chaincode Server에서 다른 작업을 진행해야 경우가 발생합니다. API에 대해서 조금 더 찾아보거나 Chaincode Server에서 어떻게 작업을 효율적으로 처리할 지에 대해서 생각 해 볼 필요가 있을 것입니다.

 

5. 정리하며

 

지금까지 Fabric의 테스트 환경 구축 및 API등의 사용법에 대해서 간단히 설명해 보았습니다. 아직 설명이 부족한 점이 많지만 설치 중에 꼭 필요한 부분, 검색으로 잘 나오지 않았던 부분에 대해서 언급해 보았습니다. Fabric은 사용자가 구현함에 따라 여러 구조 다른 형태 등으로 개발이 가능 할 것으로 보이기 때문에 더욱 많은 활용 방법이 있을 것으로 보입니다

 


Fabric에 대한 더 자세한 내용을 살펴보고 싶다면, 아래 링크를 클릭해주세요.

▶ Fabric에 대해 살펴보겠습니다.(1편)

▶ Fabric에 대해 살펴보겠습니다.(2편)


 

참고자료

https://developer.ibm.com/kr/developer-%EA%B8%B0%EC%88%A0-%ED%8F%AC%EB%9F%BC/2017/01/08/blockchain-basic-01-introduction-to-distributed-ledgers/

https://developer.ibm.com/kr/developer-%EA%B8%B0%EC%88%A0-%ED%8F%AC%EB%9F%BC/2017/01/15/blockchain-basic-02-hyperledger-fabric-overview/

https://godoc.org/github.com/hyperledger/fabric/core/chaincode/shim

https://www.bloter.net/archives/276774

http://fabrictestdocs.readthedocs.io/en/latest/gettingstarted.html

https://snowdeer.github.io/blog/tags/#blockchain

https://snowdeer.github.io/blockchain/2017/06/22/introduction-of-hyperledger/

http://miiingo.tistory.com/40

https://developer.ibm.com/kr/cloud/blockchain/2017/04/04/starting_blockchain_using_hyperledger_fabric/

https://developer.ibm.com/kr/developer-%EA%B8%B0%EC%88%A0-%ED%8F%AC%EB%9F%BC/2017/01/26/blockchain-basic-03-build_development_environment/

http://hihellloitland.tistory.com/24

http://hyperledger-fabric.readthedocs.io/en/latest/

http://hyperledger-fabric.readthedocs.io/en/release-1.1/getting_started.html

http://avilos.codes/infra-management/virtualization-platform/docker/docker-compose/

https://www.ibm.com/blockchain/kr-ko/hyperledger.html 

https://developer.ibm.com/kr/developer-%EA%B8%B0%EC%88%A0-%ED%8F%AC%EB%9F%BC/2017/01/15/blockchain-basic-02-hyperledger-fabric-overview/

 

https://mvnrepository.com/artifact/org.hyperledger.fabric-sdk-java/fabric-sdk-java/1.1.0