Gom3rye

kopoBlockChainVote] chaincode.go 분석 본문

졸업 프로젝트

kopoBlockChainVote] chaincode.go 분석

Gom3rye 2022. 5. 24. 12:02
/*
* SPDX-License-Identifier: Apache-2.0
*/

package main

import (

"fmt" //write and print function을 위해 필요하다.
"strconv" //문자열을 다른 형식으로 변환할 때 필요하다.

"github.com/hyperledger/fabric/core/chaincode/shim" // provides interfaces, 
// Package shim provides APIs for the chaincode to access its state
pb "github.com/hyperledger/fabric/protos/peer" 

)

var logger = shim.NewLogger("kopoVote") //로그를 찍기 위해 필요하다.

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {

}


// Init is somthing...["A","0","B","0","C,"0"] //instantiate, upgrade
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {

logger.Info("########### kopoVote Init ###########") //로그 찍기


_, args := stub.GetFunctionAndParameters() //:= 변수 선언 및 대입 
//체인코드와 패브릭의 피어 사이의 통신을 위한 인터페이스인 Stub 인터페이스

var A, B, C string // Entities //var 변수 이름 변수 타입
var Aval, Bval, Cval int // Asset holdings
var err error


// Initialize the chaincode
A = args[0]
Aval, err = strconv.Atoi(args[1])

if err != nil {
return shim.Error("Expecting integer value for asset holding")
}

B = args[2]
Bval, err = strconv.Atoi(args[3])

if err != nil {
return shim.Error("Expecting integer value for asset holding")
}

C = args[4]
Cval, err = strconv.Atoi(args[5])

if err != nil {
return shim.Error("Expecting integer value for asset holding")
}

logger.Info("Aval = %d, Bval = %d, Cval = %d\n ", Aval, Bval, Cval)

Avalbytes, err := stub.GetState(A)
if Avalbytes == nil { // 체인코드 업그레이드에 대비한 방어로직
//업그레이드시 Init호출될때 기존에 값이 존재하는 경우 초기화 하지 않도록 한다.

// Write the state to the ledger
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))

if err != nil {
return shim.Error(err.Error())
}
}


Bvalbytes, err := stub.GetState(B)
if Bvalbytes == nil {
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))

if err != nil {
return shim.Error(err.Error())
}
}


Cvalbytes, err := stub.GetState(C)
if Cvalbytes == nil {
err = stub.PutState(C, []byte(strconv.Itoa(Cval)))

if err != nil {
return shim.Error(err.Error())
}
}

return shim.Success(nil)
}


// Invoke is something ... Transaction makes payment of X units from A to B //query, invoke
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {

logger.Info("########### kopoVote Invoke ###########")

//Get method name and parameter from the chaincode arguments
function, args := stub.GetFunctionAndParameters()

if function == "query" { //asset ID에 대한 소유권 정보 구조를 반환
//사용자는 asset ID를 사용하여 해당 작품에 대한 소유권 정보를 확인할 수 있다.
// queries an entity state
return t.query(stub, args)
}

if function == "vote" {
// Deletes an entity from its state
return t.vote(stub, args)
}

logger.Errorf("Unknown action, check the first argument, must be one of 'delete', 'query', or 'move'. But got: %v", args[0])

return shim.Error(fmt.Sprintf("Unknown action, check the first argument, must be one of 'delete', 'query', or 'move'. But got: %v", args[0]))
}


func (t *SimpleChaincode) vote(stub shim.ChaincodeStubInterface, args []string) pb.Response {

// must be an invoke
var A string // Entities
var Aval int // Asset holdings
var err error

if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}


A = args[0]

// Get the state from the ledger
// TODO: will be nice to have a GetAllState call to ledger
Avalbytes, err := stub.GetState(A)

if err != nil {
return shim.Error("Failed to get state")
}

if Avalbytes == nil {
return shim.Error("Entity not found")
}


Aval, _ = strconv.Atoi(string(Avalbytes))

logger.Infof("Before> Aval = %d\n", Aval)


Aval = Aval + 1

logger.Infof("After> Aval = %d\n", Aval)


// Write the state back to the ledger
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))

if err != nil {
return shim.Error(err.Error())
}

return shim.Success(nil)
}

// Query callback representing the query of a chaincode
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {

var A, B, C string // Entities
var err error

// if len(args) != 1 {
// return shim.Error("Incorrect number of arguments") //Expecting name of the person to query
// }


A = args[0]

// Get the state from the ledger
Avalbytes, err := stub.GetState(A)

if err != nil {
jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
return shim.Error(jsonResp)
}


if Avalbytes == nil {
jsonResp := "{\"Error\":\"Nil qty for " + A + "\"}"
return shim.Error(jsonResp)
}


jsonResp := "{\"Name\":\"" + A + "\",\"Qty\":\"" + string(Avalbytes) + "\"}"

logger.Infof("Query Response:%s\n", jsonResp)


B = args[1]

// Get the state from the ledger
Bvalbytes, err := stub.GetState(B)

if err != nil {
jsonResp := "{\"Error\":\"Failed to get state for " + B + "\"}"
return shim.Error(jsonResp)
}


if Bvalbytes == nil {
jsonResp := "{\"Error\":\"Nil qty for " + B + "\"}"
return shim.Error(jsonResp)
}


jsonResp = "{\"Name\":\"" + B + "\",\"Qty\":\"" + string(Bvalbytes) + "\"}"

logger.Infof("Query Response:%s\n", jsonResp)



C = args[2]

// Get the state from the ledger
Cvalbytes, err := stub.GetState(C)

if err != nil {
jsonResp := "{\"Error\":\"Failed to get state for " + C + "\"}"
return shim.Error(jsonResp)
}


if Cvalbytes == nil {
jsonResp := "{\"Error\":\"Nil qty for " + C + "\"}"
return shim.Error(jsonResp)
}


jsonResp = "{\"Name\":\"" + C + "\",\"Qty\":\"" + string(Cvalbytes) + "\"}"

logger.Infof("Query Response:%s\n", jsonResp)


retStr := []string{string(Avalbytes), string(Bvalbytes), string(Cvalbytes)}
//return shim.Success(Avalbytes)
//var str = []string{"str1","str2"}
var x = []byte{}
x = append(x, '{')

for i := 0; i < len(retStr); i++ {

b := []byte(retStr[i])

for j := 0; j < len(b); j++ {
x = append(x, b[j])
}

x = append(x, ',')
}

x = append(x, '}')

return shim.Success(x)
}

 

 

Client - Server - DB의 구조와 비슷하다.

invoke -> to call functions and transactions 

log를 쓰는 이유 : 로킹은 개발자가 에러 혹은 퍼포먼스 이슈를 발견하는데 도움을 주고 로그들을 재구성해서 시스템을 파악할 수 있어서

Stub 인터페이스는 체인코드와 패브릭의 피어 사이의 통신을 위한 API를 encapsulation한 인터페이스다. 즉 피어와 통신하려면 stub 인터페이스에 있는 메서드를 사용해야 한다는 뜻이다. state를 쓰거나 읽을 때 사용하는 PutState와 GetState가 모두 stub 인터페이스의 메서드로 존재한다. 또한 인자로 전달받은 target과 data를 받기 위한 함수도 정의되어있다. 대부분의 경우에는 GetFunctionAndParameters() (string, []string) 메서드로 함수 이름과 인자를 구분하여 받아서 사용하지만 이 외에도 GetArgs() ([][]byte), GetStringArgs() ([]string), GetArgsSlice() ([]byte, error)등이 존재한다. 이를 통해 얻은 함수 이름에 해당하는 체인코드 함수를 실행한다.



invoke는 Args로 실행하고자 하는 함수 이름과 인자를 함께 전달해주어야 한다. 함수 수행 중 피어와 데이터를 주고 받기 위해서는 JSON 형태여야 하므로 구조체를 JSON으로 바꾸는 Encoding(Unmarshal)과 JSON을 구조체로 바꾸는 Decoding(Marshal) 동작이 필요하다. 이는 구조체의 메서드로 정의되어야 한다.


체인코드 수행 결과로 오는 response로는 TxID로 트랜잭션 ID가 반환된다. 즉 어떤 거래를 수행했는지에 관한 정보는 포함하지 않는다. 따라서 거래 기록을 알기 위해서는 event를 사용해야 한다. 이벤트를 통해 블록체인 상에서 발생한 사건들(블록생성, 스마트컨트랙트의 액션발생 시)을 외부로 전달할 수 있다. 이벤트를 생성하고 받는 SetEvent와 GetEvent도 마찬가지로 Stub Interface에 존재한다. 체인코드 이벤트는 피어가 블록을 받을 때 발생한다. 이는 query 트랜잭션의 경우에는 이벤트가 발생하지 않고 Invoke 트랜잭션인 경우에만 이벤트가 발생한다는 뜻이다.

원래 query 함수

async query(stub, args){

        // Generate composite key
        let searchKey = stub.createCompositeKey("Asset.", [args[0]]);
        
        // 소유권 정보 가져오기
        let asset = await stub.getState(searchKey);

        return asset;
    }

 

728x90
반응형