블록체인/HYPERLEDGER FABRIC

HYPERLEDGER FABRIC - Developing Applications - Smart Contract Processing

펭귀니 :) 2020. 8. 24. 16:18

HYPERLEDGER FABRIC

  ㄴ Developing Applications

    ㄴ Smart Contract Processing

 

독자 : Architects, Application and smart contract developers

 

블록체인 네트워크 중심에는 smart contract(스마트 계약)가 있습니다.

PaperNet에서 기업 어음 스마트 계약안에 있는 code는 기업어음의 유효한 state로 정의되어 있고 하나의 state에서 다른 state로 어음의 상태를 바꾸는 트랜잭션 로직으로 정의되어 있습니다.

이번 주제에서 어떻게 현실 세계의 issuing, buying, redeeming 기업 어음의 프로세스를 관리하는 스마트 계약을 구현하는지 알아볼 것입니다.

  • 스마트 계약이란 무엇인가 그리고 왜 중요한가 (Smart Contract)
  • 스마트 계약을 정의하는 방법 (Contract class)
  • 트랜잭션을 정의하는 방법 (Transaction definition)
  • 트랜잭션을 구현하는 방법 (Transaction logic)
  • 스마트 계약안에서 비즈니스 객체를 표현하는 방법 (Representing an object)
  • 원장안에서 객체를 저장하고 검색하는 방법 (Access the ledger)

만약 원한다면, 샘플을 다운로드 받을 수 있고, 로컬에서 실행해볼 수 있습니다.

JavaScript와 Java로 작성되어있지만, 로직은 언어와 무관하기 때문에 무슨 일이 일어나고 있는지 쉽게 알 수 있을 것입니다. (샘플은 Go에서도 사용할 수 있게 될 것입니다.)

 

Smart Contract

스마트 계약은 비즈니스 객체의 여러 state를 정의하고 다른 state간에 객체를 이동하는 프로세스들을 다룹니다.

스마트 계약은 블록체인 네트워크 안에서 협력하는 다른 조직과 공유되는 data와 주요 비즈니스 프로세스를 정의하는 스마트 계약 개발자, 설계자를 인정하기 때문에 중요합니다.

 

PaperNet 네트워크에서 스마트 계약은 MagnetoCorp와 DigiBank같은 다른 네트워크 참여자와 공유됩니다.

동일한 버전의 스마트 계약은 네트워크와 연결된 모든 application에서 사용되어 동일하게 공유되는 비즈니스 프로세스와 data를 공동으로 구현해야 합니다.

 

Implementation Languages

지원되는 런타임 환경에는 Java Virtual Machine과 Node.js가 있습니다.

JavaScript, TypeScript, Java 또는 지원되는 런타임 중 하나에서 실행할 수 있는 다른 언어를 사용할 수 있습니다.

 

Java나 TypeScript에서 annotations과 decorators는 스마트 계약과 그 구조에 대한 정보를 제공하는데 사용됩니다.

이를 통해 풍부한 개발 경험을 얻을 수 있습니다.

예를 들어, 작성자 정보 또는 return 타입을 적용할 수 있습니다.

JavaScript내에서는 규칙을 따라야하므로 자동으로 결정될 수 있는 것에 한계가 있습니다.

 

여기에 JavaScript와 Java로 작성된 예제가 있습니다.

 

Contract class

PaperNet 기업 어음 스마트 계약의 복사본은 하나의 파일에 포함되어 있습니다.

브라우저로 보거나 다운로드 후 원하는 에디터로 열어보세요.

 

 

파일 경로를 보면 이것이 MagnetoCorp의 스마트 계약 사본임을 알 수 있습니다.

MagnetoCorp와 DigiBank는 그들이 사용할 스마트 계약의 버전에 동의해야합니다.

현재로서는 모두 동일하기 때문에 어떤 조직의 복사본을 사용하든지 상관없습니다.

 

스마트 계약의 전체 구조를 살펴보세요. 매우 짧습니다.

파일 상단에 기업 어음 스마트 계약의 정의가 있음을 알 수 있습니다.

 

JavaScript

class CommercialPaperContract extends Contract {...}

Java

@Contract(...)
@Default
public class CommercialPaperContract implements ContractInterface {...}

 

CommercialPaperContract 클래스는 기업 어음의 issue, buy, redeem 트랜잭션 정의를 포함하고 있습니다.

그 트랜잭션은 그들의 생명주기를 바꾸고 기업어음을 존재하게합니다.

이러한 트랜잭션을 조사해볼거지만 현재는 JavaScript에서 CommercialPaperContract는 Hyperledger Fabric Contract class를 extend한다는 점에 주목하세요.

 

Java에서는 클래스를 @Contract(...)로 선언해야합니다.

이를 통해 라이센스와 작성자같은 계약(contract)에 대한 추가 정보를 제공할 수 있습니다.

@Default() 어노테이션은 이 contract 클래스가 기본 contract 클래스임을 나타냅니다.

default contract 클래스로 표시한 것은 여러 contract 클래스를 가진 스마트 계약에서 유용합니다.

 

만약 TypeScript 구현을 사용한다면, Java에서 @Contract(...) 어노테이션과 동일한 목적을 수행하는 유사한 것이 있습니다.

 

사용 가능한 어노테이션에 대한 더 많은 정보는 API documentation을 참조하세요.

 

이러한 class, 어노테이션 그리고 Context class는 이전 범위에 포함되어있습니다.

JavaScript

const { Contract, Context } = require('fabric-contract-api');

Java

import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.Contact;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Info;
import org.hyperledger.fabric.contract.annotation.License;
import org.hyperledger.fabric.contract.annotation.Transaction;

우리의 기업어음 계약은 자동 메서드 호출, 트랜잭션별 context, transaction handler, class-shared state와 같은 class의 기본 제공 기능을 사용할 것입니다.

 

JavaScript class 생성자가 명시적인 contract 이름으로 자신을 초기화하기 위해 그들의 superclass를 사용하는 방법도 확인해보세요.

constructor() {
    super('org.papernet.commercialpaper');
}

Java 클래스에서 생성자는 @Contract() 어노테이션안에 명시적으로 contract name를 지정할 수 있으므로 빈칸입니다.

지정하지 않으면, class이름이 사용됩니다.

 

가장 중요한것은 org.papernet.commercialpaper이 매우 설명적이라는 것입니다.

이 스마트 계약은 모든 PaperNet 조직에서 동의한 기업어음 정의입니다.

 

contract들은 다른 생명주기를 갖는 경향이 있으므로, 그들을 분리하는 것이 합리적이라 일반적으로 파일당 하나의 스마트 계약만 존재합니다.

그러나 경우에 따라 여러 스마트 계약은 EuroBond, DollarBond, YenBond 같은 application을 위한 구문 도움말을 제공할 수 있지만, 기본적으로 동일한 기능을 제공합니다.

이러한 경우 스마트 계약과 트랜잭션의 차이를 분명하게 보여줄 수 있습니다.

 

Transaction definition

클래스안에서 issue 메서드를 찾아봅시다.

JavaScript

async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {...}

Java

@Transaction
public CommercialPaper issue(CommercialPaperContext ctx,
                             String issuer,
                             String paperNumber,
                             String issueDateTime,
                             String maturityDateTime,
                             int faceValue) {...}

Java 어노테이션 @Transaction은 트랜잭션 정의 메서드를 표시하는데 사용됩니다.

TypeScript도 동일한 어노테이션을 가지고 있습니다.

 

이 contract가 기업 어음을 issue(발행)하기 위해 호출될 때 언제든지 이 함수는 제어됩니다.

아래 트랜잭션으로 어떻게 기업 어음 00001이 생성되었는지 생각해봅시다.

Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD

프로그래밍 스타일에 따라 변수이름을 변경했지만, 어떻게 속성들이 issue 메서드 변수에 거의 직접적으로 매핑되는지 확인해보세요.

 

issue 메서드는 자동으로 application이 기업 어음 발행(issue)을 요청할 때 언제든지 contract에 의해 제어됩니다.

트랜잭션 속성 값은 상응하는 변수를 통해 메서드에서 사용가능해집니다.

다음 주제인 application topic에서 application이 샘플 application program에서 어떻게 Hyperledger Fabric SDK를 사용하여 트랜잭션을 제출하는지 확인해보세요.

 

당신은 issue 정의에서 ctx라는 추가 변수를 발견했을 것입니다.

그것은 transaction context라고 불리고, 언제나 첫번째에 위치합니다.

기본적으로 transaction logic과 관련된 contract별, transaction별 정보를 유지합니다.

예를 들어, MagnetoCorp의 명시된 트랜잭션 식별자, MagnetoCorp issuing 사용자의 디지털 인증서, 원장 API에 대한 접근을 포함합니다.

 

어떻게 스마트 계약이 default 트랜잭션 context를 extend하는지 확인하세요. default implementation하는 것 대신 그 자체 메서드인 createContext()를 구현합니다.

JavaScript

createContext() {
  return new CommercialPaperContext()
}

Java

@Override
public Context createContext(ChaincodeStub stub) {
     return new CommercialPaperContext(stub);
}

 

이를 통해 extend된 context는 사용자 정의 속성 paperList를 기본값에 추가합니다.

JavaScript

class CommercialPaperContext extends Context {

  constructor() {
    super();
    // All papers are held in a list of papers
    this.paperList = new PaperList(this);
}

Java

class CommercialPaperContext extends Context {
    public CommercialPaperContext(ChaincodeStub stub) {
        super(stub);
        this.paperList = new PaperList(this);
    }
    public PaperList paperList;
}

 

어떻게 ctx.paperList가 나중에 모든 PaperNet 기업어음의 저장과 검색을 도울 수 있는지 곧 알게 될 것입니다.

 

스마트 계약 트랜잭션 구조의 이해를 확고히하기 위해 buyredeem 트랜잭션 정의를 찾아, 어떻게 그들이 해당하는 기업어음 트랜잭션에 매핑하는 하는지 확인할 수 있습니다.

 

The buy transaction:

Txn = buy
Issuer = MagnetoCorp
Paper = 00001
Current owner = MagnetoCorp
New owner = DigiBank
Purchase time = 31 May 2020 10:00:00 EST
Price = 4.94M USD

JavaScript

async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseTime) {...}

Java

@Transaction
public CommercialPaper buy(CommercialPaperContext ctx,
                           String issuer,
                           String paperNumber,
                           String currentOwner,
                           String newOwner,
                           int price,
                           String purchaseDateTime) {...}

 

The redeem transaction:

Txn = redeem
Issuer = MagnetoCorp
Paper = 00001
Redeemer = DigiBank
Redeem time = 31 Dec 2020 12:00:00 EST

JavaScript

async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) {...}

Java

@Transaction
public CommercialPaper redeem(CommercialPaperContext ctx,
                              String issuer,
                              String paperNumber,
                              String redeemingOwner,
                              String redeemDateTime) {...}

두 가지 경우 모두 기업어음과 스마트 계약 메서드 정의간의 1 대 1 대응을 볼 수 있습니다.

 

모든 JavaScript 함수는 동기 함수를 호출한 것처럼 처리될 수 있도록 하는 asyncawait 키워드를 사용합니다.

 

Transaction logic

어떻게 contract가 설계되고, 트랜잭션이 정의되는지 봤으므로 이제 smart contract 내의 로직에 포커스를 맞춰보겠습니다.

 

첫번째 issue 트랜잭션:

Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD

issue 메서드가 제어를 통과하게 됩니다.

JavaScript

async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {

   // create an instance of the paper
  let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);

  // Smart contract, rather than paper, moves paper into ISSUED state
  paper.setIssued();

  // Newly issued paper is owned by the issuer
  paper.setOwner(issuer);

  // Add the paper to the list of all similar commercial papers in the ledger world state
  await ctx.paperList.addPaper(paper);

  // Must return a serialized paper to caller of smart contract
  return paper.toBuffer();
}

Java

@Transaction
public CommercialPaper issue(CommercialPaperContext ctx,
                              String issuer,
                              String paperNumber,
                              String issueDateTime,
                              String maturityDateTime,
                              int faceValue) {

    System.out.println(ctx);

    // create an instance of the paper
    CommercialPaper paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime,
            faceValue,issuer,"");

    // Smart contract, rather than paper, moves paper into ISSUED state
    paper.setIssued();

    // Newly issued paper is owned by the issuer
    paper.setOwner(issuer);

    System.out.println(paper);
    // Add the paper to the list of all similar commercial papers in the ledger
    // world state
    ctx.paperList.addPaper(paper);

    // Must return a serialized paper to caller of smart contract
    return paper;
}

로직은 간단합니다.

트랜잭션 입력 변수를 가져와서 새로운 기업어음 paper를 만들고, paperList를 사용하여 모든 기업어음의 목록에 추가하고, 트랜잭션 응답으로 새로운 기업어음(serialized as a buffer)을 반환합니다.

 

어떻게 paperList는 기업어음 목록에 접근하기위해 트랜잭션 context에서 어떻게 검색하는지 확인하세요.

issue(), buy(), redeem()은 최신의 기업어음 목록을 유지하기위해 연속적으로 ctx.paperList에 재접근됩니다.

 

 

buy 트랜잭션 로직은 조금 더 정교합니다.

JavaScript

async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) {

  // Retrieve the current paper using key fields provided
  let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
  let paper = await ctx.paperList.getPaper(paperKey);

  // Validate current owner
  if (paper.getOwner() !== currentOwner) {
      throw new Error('Paper ' + issuer + paperNumber + ' is not owned by ' + currentOwner);
  }

  // First buy moves state from ISSUED to TRADING
  if (paper.isIssued()) {
      paper.setTrading();
  }

  // Check paper is not already REDEEMED
  if (paper.isTrading()) {
      paper.setOwner(newOwner);
  } else {
      throw new Error('Paper ' + issuer + paperNumber + ' is not trading. Current state = ' +paper.getCurrentState());
  }

  // Update the paper
  await ctx.paperList.updatePaper(paper);
  return paper.toBuffer();
}

Java

@Transaction
public CommercialPaper buy(CommercialPaperContext ctx,
                           String issuer,
                           String paperNumber,
                           String currentOwner,
                           String newOwner,
                           int price,
                           String purchaseDateTime) {

    // Retrieve the current paper using key fields provided
    String paperKey = State.makeKey(new String[] { paperNumber });
    CommercialPaper paper = ctx.paperList.getPaper(paperKey);

    // Validate current owner
    if (!paper.getOwner().equals(currentOwner)) {
        throw new RuntimeException("Paper " + issuer + paperNumber + " is not owned by " + currentOwner);
    }

    // First buy moves state from ISSUED to TRADING
    if (paper.isIssued()) {
        paper.setTrading();
    }

    // Check paper is not already REDEEMED
    if (paper.isTrading()) {
        paper.setOwner(newOwner);
    } else {
        throw new RuntimeException(
                "Paper " + issuer + paperNumber + " is not trading. Current state = " + paper.getState());
    }

    // Update the paper
    ctx.paperList.updatePaper(paper);
    return paper;
}

소유자를 paper.setOwner(newOwner)로 변경하기 전에 어떻게 트랜잭션이 currentOwnerpaper TRADING인지 체크하는지 확인하세요.

기본 흐름은 간단합니다. 몇가지 조건을 확인하고, 새 소유주(new owner)를 설정하고, 원장에 있는 기업어음을 업데이트하고, 업데이트된 기업어음(serialized as a buffer)을 트랜잭션 응답으로 반환해줍니다.

 

redeem 트랜잭션 로직을 이해할 수 있는지 확인해볼까요?

 

Representing an object

CommercialPaperPaperList class를 사용하여 issue, buy, redeem 트랜잭션을 정의하고 구현하는 방법을 살펴보았습니다.

이러한 클래스들의 동작을 확인하며 이번 주제를 마무리하겠습니다.

 

CommercialPaper 클래스를 확인해봅시다.

JavaScript

paper.js 파일 안에

class CommercialPaper extends State {...}

Java

CommercialPaper.java 파일 안에

@DataType()
public class CommercialPaper extends State {...}

 

이 클래스는 기업어음 state의 인메모리 표현을 포함합니다.

createInstance 메소드가 제공된 매개 변수를 사용해 새로운 기업어음을 초기화합니다.

JavaScript

static createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
  return new CommercialPaper({ issuer, paperNumber, issueDateTime, maturityDateTime, faceValue });
}

Java

public static CommercialPaper createInstance(String issuer, String paperNumber, String issueDateTime,
        String maturityDateTime, int faceValue, String owner, String state) {
    return new CommercialPaper().setIssuer(issuer).setPaperNumber(paperNumber).setMaturityDateTime(maturityDateTime)
            .setFaceValue(faceValue).setKey().setIssueDateTime(issueDateTime).setOwner(owner).setState(state);
}

 

이 class가 issue 트랜잭션에서 어떻게 사용되었는지 생각해보세요.

JavaScript

let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);

Java

CommercialPaper paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime,
        faceValue,issuer,"");

issue 트랜잭션이 호출될 때마다, 트랜잭션 데이터를 포함하는 기업어음의 새로운 인메모리 인스턴스가 어떻게 생성되는지 확인해보세요.

 

참고해야하는 몇 가지 중요한 사항 :

  • 이것은 인메모리 표현입니다. 나중에 원장에 어떻게 나타나는지 살펴보겠습니다.
  • CommercialPaper class는 State class를 extend합니다. State는 state에 관한 공통 추상화를 생성하는 application-defined class입니다. 모든 state에는 복합키, 직렬화, 역직렬화 등이 가능한 비즈니스 객체 class를 가지고 있습니다. 우리가 원장에 둘 이상의 비즈니스 객체를 저장할 때, State는 우리의 코드를 더 읽기 쉽게 도와줍니다. state.js 파일에서 State class를 조사해보세요.
  • paper은 생성될 때 자체 키를 만듭니다. 이 키는 원장에 접근할 때 사용됩니다. key는 issuerpaperNumber의 조합으로 구성됩니다.
constructor(obj) {
  super(CommercialPaper.getClass(), [obj.issuer, obj.paperNumber]);
  Object.assign(this, obj);
}
  • paper은 paper class가 아닌 트랜잭션에 의해 ISSUED로 이동됩니다. 이는 paper의 생명주기 상태를 관리하는 스마트 계약이기 때문입니다. 예를 들어, import 트랜잭션은 TRADING state에서 paper의 새로운 set를 생성할 수 있습니다.

 

CommercialPaper class에는 간단한 도우미 메서드도 포함되어 있습니다.

getOwner() {
    return this.owner;
}

이와 같은 메서드가 스마트 계약에 의해 생명주기를 통해 기업어음을 이동하는데 사용되는지 확인해보세요. 

예를 들어, redeem 트랜잭션에서 확인할 수 있습니다.

if (paper.getOwner() === redeemingOwner) {
  paper.setOwner(paper.getIssuer());
  paper.setRedeemed();
}

 

Access the ledger

paperlist.js 파일에 있는 PaperList를 찾아봅시다.

class PaperList extends StateList {

이 유틸리티 class는 Hyperledger Fabric state database안에 모든 PaperNet 기업 어음을 관리하는데 사용됩니다.

이 PaperList data 구조는 architecture topic에 자세히 설명되어 있습니다.

 

CommercialPaper class처럼, 이 class도 state의 목록에 대한 공통 추상화를 생성하는 application-defined StateList class를 extend합니다. (이러한 경우, PaperNet 내의 모든 기업 어음)

 

addPaper() 메서드는 StateList.addState() 메서드의 단순한 형태입니다.

async addPaper(paper) {
  return this.addState(paper);
}

 

StateList.js 파일에서 StateList class가 Fabric API putState()를 원장안에서 state data로서 기업어음을 작성하기위해 어떻게 사용하는지 확인해볼 수 있습니다.

async addState(state) {
  let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey());
  let data = State.serialize(state);
  await this.ctx.stub.putState(key, data);
}

 

원장 안에 있는 state data는 두 가지 기본요소가 필요합니다.

  • Key : key는 고정된 이름과 state의 key를 사용하여 createCompositeKey()로 형성됩니다. 그 이름은 PaperList 객체가 구성될 때 할당되며, state.getSplitKey()는 각각 state의 유일 키를 결정합니다.
  • Date : data는 기업어음 state 형식을 State.serialize() utility 메서드를 사용하여 간단히 직렬화한 것입니다. 그 State class는 JSON을 사용하여 data를 직렬화하고 역직렬화합니다. 그리고 필요에 따라 State의 비즈니스 객체 class, 우리의 경우 CommercialPaperPapaerList 객체가 작성되었을 때 다시 설정됩니다..

어떻게 StateList가 개별 state 또는 state의 모든 list에 대한 어떤 것도 저장하지 않는다는 것을 확인하세요.

모든 것을 Fabric state database에 위임합니다.

Hyperledger Fabric에서 ledger MVCC 충돌의 가능성을 줄이는 이 디자인 패턴은 매우 중요합니다.

 

StateList getState()updateState() 메서드는 비슷한 방식으로 동작합니다.

async getState(key) {
  let ledgerKey = this.ctx.stub.createCompositeKey(this.name, State.splitKey(key));
  let data = await this.ctx.stub.getState(ledgerKey);
  let state = State.deserialize(data, this.supportedClasses);
  return state;
}
async updateState(state) {
  let key = this.ctx.stub.createCompositeKey(this.name, state.getSplitKey());
  let data = State.serialize(state);
  await this.ctx.stub.putState(key, data);
}

 

어떻게 Fabric API인 putState(), getState(), createCompositeKey()이 원장에 접근하기위해 사용되는지 확인해보세요.

우리는 이 스마트 계약을 확장하여 PaperNet에 모든 기업어음을 나열할 것입니다.

원장 검색을 구현하는 메서드는 무엇일까요?

 

이게 다입니다! 이 주제에서 어떻게 PaperNet에서 스마트 계약을 구현하는지 이해했습니다.

다음 하위 주제로 이동해서 어떻게 application이 Fabric SDK를 사용하여 스마트 계약을 호출하는지 확인하세요.

 

 

출처 ] 

https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/smartcontract.html#