Chaincode는 Go와 Node.js로 쓰여진 프로그램입니다. 그리고 규정된 인터페이스를 실행합니다.

차후엔 자바와 같은 다른 프로그래밍 언어들도 지원할 것입니다.

Chaincode는 보안된 Docker container에 허가된 피어 프로세스와 격리되어 실행됩니다.

Chaincode는 어플리케이션에서 제출하여 트랜잭션으로 원장 State 초기화 및 관리를 합니다.

Chaincode는 전형적으로 네트워크의 구성원으로부터 동의된 비즈니스 로직을 다루고, 그래서 Smart Contract와 유사합니다.

Chaincode는 트랜잭션 제안에 업데이트 또는 쿼리를 하기 위해서 호출될 수 있습니다.

허가를 받은 State에서, Chaincode는 state에 액세스 하기 위해서 같은 Channel이나 다른 Channel에서도 또 다른 Chaincode를 호출할 수 있습니다.

만약 다른 Channel에서 호출된 Chaincode일 경우 읽기 전용으로만 접근이 가능하다는 점을 알고 계셔야합니다.

즉, 다른 Channel에서 불려진 Chaincode는 단지 쿼리일 뿐이라는 것을 의미합니다. 이는 이후의 커밋 페이즈에서 State 인증에 참여하지 않는다는 것을 의미하고 있습니다.

다음의 섹션에선, 어플리케이션 개발자의 입장에서 Chaincode를 배워볼 것 입니다.

단순한 어플리케이션의 샘플 코드와 Chaincode Shim API의 각각의 메소드의 목적을 확인하는 것을 보여드리겠습니다.

Chaincode API

각각의 Chaincode 프로그램은 아래의 Chaincode interface로 실행되어야합니다:

  • Go
  • Node.js

해당 인터페이스들의 전달받은 트랜잭션의 반응으로 메소드 콜을 발생시킵니다.

특히 Init 메소드는 Chaincode가 Instantiate나 Upgrade와 같은 트랜잭션 메소드를 어플리케이션 State에 관한 초기화를 포함하여 Chaincode가 필요한 초기화를 위해서 불려집니다.

Invoke 메소드는 Invoke 트랜잭션이 트랜잭션의 제안을 받았을 때 발생하게 됩니다.

다른 "Shim" API에 들어있는 Chaincode는 ChaincodeStubInterface 아래의 언어로 실행됩니다:

  • Go
  • Node.js

이들은 원장에 접근하거나 수정하기위해서 사용되고, Chaincode 간에 호출을 위해서 사용됩니다.

이번 튜토리얼에선, 단순한 "Asset"을 관리하는 Chaincode 어플리케이션을 실행함으로서 이러한 종류의 API 사용을 시연하겠습니다.

Simple Asset Chaincode

아래의 어플리케이션은 원장에 Asset을 Key-Value 페어로 만드는 간단한 샘플 Chaincode 입니다.

code의 디렉토리 설정하기

Go언어로 개발한 경험이 없으시다면, Go언어가 시스템에 적절하게 설정된 State로 설치되어 있어야 합니다.

지금부터, Chaincode 어플리케이션을 위한 하나의 child directory로서 $GOPATH/src/ 디렉토리를 만들겠습니다.

간단히 실행하기 위해선, 아래의 커맨드를 입력하세요.

mkdir -p $GOPATH/src/sacc && cd $GOPATH/src/sacc

코드를 써넣을 소스 코드를 만들어 보겠습니다.

touch sacc.go

시스템 하우스 관리

우선, 하우스 관리를 하면서 시작합시다. 모든 Chaincode들 처럼, Chaincode 인터페이스를 실행해주어야 합니다.

특히, Init과 Invoke 함수를 실행하셔야 합니다.

go언어에 Chaincode 의존성을 추가하십시오.

Chaincode shim package와 peer protobuf package를 임포트 할 것입니다.

다음으로, Simple Asset을 Chaincode Shim Function으로서 추가할 것 입니다.

package main

import (
    "fmt"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    "github.com/hyperledger/fabric/protos/peer"
)

// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}

Chaincode 초기화

다음으로, Init 함수를 실행할 것입니다.

// Init is called during chaincode instantiation to initialize any data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {

}

해당 함수에서 이미 Chaincode 업그레이드가 이루어진다는 것을 확인하세요.

Chaincode를 작성할 때 이전의 것을 업그레이드 할 것입니다.

Init을 적절하게 수정하셔야합니다.

특히, "마이그레이션"이 없으면 아무 것도 초기화 되지 않고, 비어있는 Init 메소드를 제공합니다.

다음으로, Init 호출에서 ChaincodeStubInterface.GetStringArgs 메소드를 통해 매개변수를 돌려받습니다. 그리고 유효성을 체크합니다.

우리의 경우에, Key-Value 쌍을 기대할 수 있습니다.

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger's data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
  // Get the args from the transaction proposal
  args := stub.GetStringArgs()
  if len(args) != 2 {
    return shim.Error("Incorrect arguments. Expecting a key and a value")
  }
}

다음 단계는, 호출이 유효할 수 있도록 만들어 냈습니다, 이제 원장에 초기 State를 저장할 것입니다.

이 단계를 진행하기에 앞서, ChaincodeStubInterface.Putstate를 불러내어 Key와 Value를 인자로 전송할 것 입니다.

모든 것이 잘 되었다는 전제하에, 초기화 하는 Response 객체가 성공했을 것입니다.

 1 // Init is called during chaincode instantiation to initialize any
 2 // data. Note that chaincode upgrade also calls this function to reset
 3 // or to migrate data, so be careful to avoid a scenario where you
 4 // inadvertently clobber your ledger's data!
 5 func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
 6   // Get the args from the transaction proposal
 7   args := stub.GetStringArgs()
 8   if len(args) != 2 {
 9     return shim.Error("Incorrect arguments. Expecting a key and a value")
10   }
11 
12   // Set up any variables or assets here by calling stub.PutState()
13 
14   // We store the key and the value on the ledger
15   err := stub.PutState(args[0], []byte(args[1]))
16   if err != nil {
17     return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
18   }
19   return shim.Success(nil)
20 }

Chaincode 호출

우선, Invoke 함수의 서명을 추가합니다.

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The 'set'
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

}

위에서의 Invoke 함수와 함께, ChaincodeStubInterface로부터 인자를 추출해낼 필요가 있습니다.

Invoke 함수의 인자는 호출할 Chaincode 어플리케이션의 함수 이름입니다.

우리의 경우에, 간단히 두 가지의 함수를 가지고 있습니다: set & get

해당 함수들을 통해서 asset의 값을 set할 수 있고, 또한 현재 State를 리턴 받을 수 있습니다.

우리는 우선 ChaincodeStrubInterface.GetFunctionAndParameters를 함수의 이름과 매개변수를 chaincode 어플리케이션 함수에 추출하기 위해서 호출 할 것입니다.

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

}

다음으로, 우리는 함수의 이름을 set과 get과 같은 형태로 만들어 낼 것입니다. 그리고 해당되는 Chaincode 어플리케이션 함수를 적절한 Shim.success와 Shim.error 함수를 gRPC protobuf 메시지 형태로 응답하며 시리얼라이즈 출력을 하면서 호출해낼 것 입니다.

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

    var result string
    var err error
    if fn == "set" {
            result, err = set(stub, args)
    } else {
            result, err = get(stub, args)
    }
    if err != nil {
            return shim.Error(err.Error())
    }

    // Return the result as success payload
    return shim.Success([]byte(result))
}

Chaincode 어플리케이션 실행하기

씌여진 대로, 우리의 Invoke 함수를 통해서 두개의 함수를 가진 Chaincode 어플리케이션을 실행할 것입니다.

이제 해당하는 함수들을 실행합니다. 위에서 말한 것처럼, 원장에 접근하기 위해선, ChaincodeStubInterface.Putstate와 ChaincodeStubInterface.Getstate 함수를 Chaincode shim API로부터 불러들일 것 입니다.

// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 2 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
    }

    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return "", fmt.Errorf("Failed to set asset: %s", args[0])
    }
    return args[1], nil
}

// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 1 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key")
    }

    value, err := stub.GetState(args[0])
    if err != nil {
            return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
    }
    if value == nil {
            return "", fmt.Errorf("Asset not found: %s", args[0])
    }
    return string(value), nil
}

모든 값을 가져오기

마지막으로 main 함수를 추가해야할 필요가 있습니다. 이 함수는 shim.start 함수를 불러낼 것입니다.

이제 전체 Chaincode 소스코드를 보여드리겠습니다.

package main

import (
    "fmt"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    "github.com/hyperledger/fabric/protos/peer"
)

// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
    // Get the args from the transaction proposal
    args := stub.GetStringArgs()
    if len(args) != 2 {
            return shim.Error("Incorrect arguments. Expecting a key and a value")
    }

    // Set up any variables or assets here by calling stub.PutState()

    // We store the key and the value on the ledger
    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
    }
    return shim.Success(nil)
}

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

    var result string
    var err error
    if fn == "set" {
            result, err = set(stub, args)
    } else { // assume 'get' even if fn is nil
            result, err = get(stub, args)
    }
    if err != nil {
            return shim.Error(err.Error())
    }

    // Return the result as success payload
    return shim.Success([]byte(result))
}

// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 2 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
    }

    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return "", fmt.Errorf("Failed to set asset: %s", args[0])
    }
    return args[1], nil
}

// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 1 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key")
    }

    value, err := stub.GetState(args[0])
    if err != nil {
            return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
    }
    if value == nil {
            return "", fmt.Errorf("Asset not found: %s", args[0])
    }
    return string(value), nil
}

// main function starts up the chaincode in the container during instantiate
func main() {
    if err := shim.Start(new(SimpleAsset)); err != nil {
            fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
    }
}

Chaincode 만들기

Chaincode를 컴파일 하겠습니다.

go get -u --tags nopkcs11 github.com/hyperledger/fabric/core/chaincode/shim
go build --tags nopkcs11

오류가 없다는 전제하에, Chaincode를 테스팅 하는 다음단계로 나아가실 수 있습니다.

개발자 모드로 테스팅하기

일반적으로 Chaincode는 피어에 의해서 시작되고 관리됩니다. 그러나 개발자 모드에선 Chaincode는 사용자에 의해서 만들어지고 제작됩니다.

이 모드는 개발 도중 빠른 코딩/빌드/실행/디버그 사이클을 돌려보는 것에 매우 유용합니다.

우리는 샘플 개발자 네트워크에서 이미 만들어진 Orderer와 채널 아티팩트를 사용해서 개발자 모드를 시작할 것입니다.

마찬가지로, 사용자는 바로 Chaincode를 컴파일 하고 실행해보실 수 있습니다.

Hyperledger Fabric Sample 설치하기

앞의 과정이 문제없이 해결되셨다면, Hyperledger Sample을 설치하십시오.

Docker의 chaincode-docker-devmode 디렉토리의 fabric-samples를 복사하십시오.

cd chaincode-docker-devmode

Docker 이미지 다운로드 하기

개발자 모드를 위해선 제공된 Docker Compose 스크립트를 이용해서 4개의 Docker 이미지를 활용해야 합니다.

만약 fabric-samples 디렉토리에 복사본을 설치하셨다면 DownloadPlatform-specific Binaies의 지시를 따르시면 됩니다.

그러면, 로컬에 설치된 Docker 이미지를 가질 수 있습니다.

만약 수동으로 받아온 이미지라면 latest를 붙이셔야만 합니다.

docker images
REPOSITORY                     TAG                                  IMAGE ID            CREATED             SIZE
hyperledger/fabric-tools       latest                           b7bfddf508bc        About an hour ago   1.46GB
hyperledger/fabric-tools       x86_64-1.1.0                     b7bfddf508bc        About an hour ago   1.46GB
hyperledger/fabric-orderer     latest                           ce0c810df36a        About an hour ago   180MB
hyperledger/fabric-orderer     x86_64-1.1.0                     ce0c810df36a        About an hour ago   180MB
hyperledger/fabric-peer        latest                           b023f9be0771        About an hour ago   187MB
hyperledger/fabric-peer        x86_64-1.1.0                     b023f9be0771        About an hour ago   187MB
hyperledger/fabric-javaenv     latest                           82098abb1a17        About an hour ago   1.52GB
hyperledger/fabric-javaenv     x86_64-1.1.0                     82098abb1a17        About an hour ago   1.52GB
hyperledger/fabric-ccenv       latest                           c8b4909d8d46        About an hour ago   1.39GB
hyperledger/fabric-ccenv       x86_64-1.1.0                     c8b4909d8d46        About an hour ago   1.39GB

docker image를 발급해서 로컬 Docker Registry에 올리십시오. 아래와 같은 것을 확인하실 수 있을 것입니다.

이제 3개의 터미널을 열어 chaincode-docker-devmode 디렉토리로 모든 터미널을 이동해주십시오.

터미널1 네트워크 시작하기

docker-compose -f docker-compose-simple.yaml up

위의 커맨드는 singleSampleMSPSolo의 Orderer 정보와 함께 네트워크를 시작합니다.

그리고 피어를 개발자 모드로 실행합니다.

남은 두개의 컨테이너는 chaincode 환경과 CLI 환경을 위한 것입니다.

채널을 만들고 참여하기 위한 기능들은 CLI환경에서 제공합니다.

그렇기 때문에 바로 Chaincode 호출로 넘어갈 수 있습니다.

터미널2 빌드& 스타트 Chaincode

docker exec -it chaincode bash

입력하신 이후 아래와 같은 결과를 보실 수 있습니다.

root@d2629980e76b:/opt/gopath/src/chaincode#

Chaincode를 컴파일 하십시오.

cd sacc
go build

Chaincode를 실행하세요.

CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc

Chaincode가 피어와 함께 실행되었고, Chaincode 로그는 성공적인 등록을 피어와 마쳤을 것입니다.

이 단계에서 Chaincode가 어떤 채널과도 연관되지 않았다는 것을 아셔야합니다.

이 과정은 다음에 나올 Instantiate 커맨드에서 완료하실 수 있습니다.

터미널3 Chaincode 사용하기

비록 --peer-chaincodedev모드에 있더라도, Chaincode를 설치하셔야 chaincode 라이프 싸이클을 정상적으로 확인할 수 있습니다.

이 요구사항은 앞으로 사라질 예정입니다.

CLI 컨테이너에서 아래와 같은 명령으로 불러낼 수 있습니다.

docker exec -it cli bash
peer chaincode install -p chaincodedev/chaincode/sacc -n mycc -v 0
peer chaincode instantiate -n mycc -v 0 -c '{"Args":["a","10"]}' -C myc

이제 a 값에 20을 할당하세요.

peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C myc

마지막으로 쿼리 a를 실행하시고, 20이라는 값을 확인하세요

peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc

새로운 Chaincode 테스팅하기

기본적으로, 여러분은 sacc만 마운팅하셨을 겁니다. 그러나 chaincode(chaincode 컨테이너를 의미함)에 다른 chaincode를 올림으로서 테스트해보실 수 있습니다.

이 지점 이후로 Chaincode 컨테이너로 접근 가능하실 것 입니다.

Chaincode 암호화

특정 시나리오에서는 전체 또는 부분적으로 키와 연관된 값을 암호화하는 것이 유용 할 수 있습니다. 예를 들어 개인의 주민등록번호 또는 주소가 원장에게 쓰여진 경우에 데이터가 일반 텍스트로 나타나지 않게 할 수 있습니다. 시리얼 코드 암호화는 상품 공장과 BCCSP wrapper 와 엔티티 확장을 활용하여 암호화 및 타원 곡선형 디지털 서명과 같은 암호화 작업을 수행함으로써 수행됩니다. 예를 들어 암호화하려면 chaincode의 호출자가 transient 필드를 통해 암호화 키를 전달합니다. 그러면 동일한 키가 후속 쿼리 작업에 사용되어 암호화 된 State 값을 적절히 해독 할 수 있습니다.

자세한 정보 및 샘플 은 디렉토리 내의 Encc 예제를 참조하십시오 fabric/examplesutils.go 도우미 프로그램을 확인해보십시오. 이 유틸리티는 chaincode shim API 및 Entities 확장을 로드하고 샘플 암호화 체인 코드가 활용 하는 새로운 클래스의 기능 (예 encryptAndPutStategetStateAndDecrypt)을 빌드합니다 . 따라서 체인 코드는 및 추가 된 기능 Get과 Put 함께 추가된 기능인 Encrypt와 Decript를 포함해서 기본 shim API와 결합 할 수 있습니다.

Go언어로 씌여진 Chaincode의 외부 의존성 관리

여러분의 Chaincode가 Go에서 제공하는 라이브러리에서 제공하는 패키지가 아닌 다른 패키지를 요구할 경우에, Chaincode에 패키지를 포함하실 수 있습니다.

이러한 의존성을 관리하거나 배포할 수 있는 다양한 툴이 있습니다.

governor라는 라이브러리를 이용해서 시연을 하겠습니다.

govendor init
govendor add +external  // Add all external package, or
govendor add github.com/external/pkg // Add specific external package

이렇게 임포트한 외부 의존성 파일은 vendor 디렉토리에 보관됩니다. peer chaincode package와 peer chaincode install과 같은 명령어는 Chaincode 패키지에 의존성과 관련된 코드를 포함합니다.


출처 : http://hyperledger-fabric.readthedocs.io/en/release-1.1/chaincode4ade.html

+ Recent posts