Zero to Blockchain: 실제 Peer 만들기

Hyperledger Fabric에서 Peer 역할 정의하고 수행할 액션 로직 짜기

Jiwon Yeom
Sep 9, 2018 · 12 min read

chapter 5에서 어드민 사용자를 추가하여 네트워크 뼈대를 만들고, 핵심 관리 기능을 구현하는 실제 로직(스크립트 부분)을 자세히 보았습니다. 그 뒤의 6~10 챕터는 Peer들을 참여시켜 네트워크를 확장합니다. 한 피어가 등장할 때, 그에 따라 개발해야 하는 부분이 어떤 것이 있는지 소개하기 위해 저는 Buyer의 예만 이 포스팅에 소개합니다. 아래 흐름을 정확히 파악하시면 나머지 챕터의 내용은 일부 로직을 다듬는 정도이니 쫓아가기 어렵지 않으실 겁니다.

본 포스팅의 예시 코드는 Zero To Blockchain github을 발췌한 것임을 미리 밝힙니다.

1 ACL 설정

// permission.aclrule BuyerACL {   description: "Allow buyer full access to order"
participant(m): "org.acme.Z2BTestNetwork.Buyer"
operation: READ, CREATE, UPDATE, DELETE
resource(v): "org.acme.Z2BTestNetwork.**"
condition: (v.buyer.buyerID == m.getIdentifier())
action: ALLOW
}rule netAccessBuyer { description: "Allow participants access to the network"
participant: "org.acme.Z2BTestNetwork.Buyer"
operation: READ, CREATE, UPDATE, DELETE
resource: "org.hyperledger.composer.system.**"
action: ALLOW
}

acl 파일은 위와 같은 구조로 정의합니다. participant는 cto 모델 파일에 정의되어 있는 조직 번호를 가져와야 합니다. Operation은 기본 CRUD 이며, 해당 참여자가 조작을 가할 수 있는 자원에 대해서도 정의합니다. 그리고 condition을 넣어서 이 역할과 관련된 제약 조건을 넣습니다. 이 경우에는, 대상 자원의 buyer ID가 participant의 ID와 동일할 경우에만 buyer로 인식한다는 간단한 조건 체크가 있네요. 즉, 그 바이어가 가지고 있는 자산만 확인할 수 있도록 하는 겁니다.

그런데 여기서 유의해야 할 점은, buyerACL과 netAccessBuyer 룰을 분리해서 정의해줘야 한다는 것입니다. 얼핏 보기에는 중복된 내용인 것 같으나, ACL은 시스템 레벨에서 하나, 비즈니스 네트워크 레벨에서 하나 정의가 되어야 합니다. 시스템 레벨의 ACL은 어떤 사람이 뭔가를 할 수 있냐는 관점에서 검증을 한다면, 비즈니스 네트워크 레벨에서의 ACL은 ‘특정 자산'에 대해서 그런 행위를 할 수 있냐를 다시 한 번 검증합니다. 위 예시 코드의 resource 부분이 달라진 것에서 알 수 있죠.

2 Query

query selectOrders {
description: "Select all Orders"
statement:
SELECT org.acme.Z2BTestNetwork.Order
}
query selectOrdersByBuyer {
description: "Select all orders for a specific buyer"
statement:
SELECT org.acme.Z2BTestNetwork.Order
WHERE (seller.sellerID == _$id)
}

이런 ACL 제약을 설정하고 나면 Query를 정의합니다. 위의 selectOrder의 경우는 사용자 계정에서 조회 가능한 — 위에 v.buyer.buyerId == m.getIdentifier()에서 정의했죠 — 레코드를 가져옵니다.

3 Business Logic: add Order

이제는 추가된 Peer가 수행해야 하는 로직들을 정의합니다. 이전 포스팅의 로직 살펴보기와 약간 비슷합니다만, Peer가 등장할 때의 흐름을 유지한다는 생각으로 다시 정리해봅니다.

// funciton 도입부
exports.addOrder = function (req, res, next) {
let method = 'addOrder';
console.log(method+' req.body.buyer is: '+req.body.buyer );
let businessNetworkConnection;
let factory;
let ts = Date.now();
let orderNo = req.body.buyer.replace(/@/, '').replace(/\./, '')+ts;
if (svc.m_connection === null) {svc.createMessageSocket();} businessNetworkConnection = new BusinessNetworkConnection(); return businessNetworkConnection.connect(req.body.buyer)
  • Information 남기기: 일단 수행하는 로직의 정보를 콘솔에 기록합니다. method 변수를 따로 정의한 것을 보면 다른 로직에서도 재사용하고자 했던 것 같은데, 왜 따로 메서드로 모듈화하지 않았는지 개인적으로 궁금하기는 하네요.
  • businessNetworkConnection, Factory, orderNo 초기화: composer에서 주는 객체 businessNetworkConnection, factory를 초기화합니다. BNC를 통해 네트워크 연결을 형성할 것이고, factory를 통해 본 로직에서 찍어내고자 하는 asset의 템플릿을 로드할 것입니다. 그리고 이 일련의 과정을 식별해줄 오더 번호를 생성합니다.
  • Message Socket 생성하기: svc는 비즈니스 네트워크 전반에서 공통적으로 사용되는 기능들을 모아 놓은 모듈입니다. 이 튜토리얼에서는 별도로 개발하지는 않고 기본 설정 파일 중 하나로 제공합니다 — 실제 프로젝트에서는 이 부분을 네트워크 생성하는 단계에서 같이 개발을 시작해야겠죠? — 그 중 m_connection을 체크하는 부분은 클라이언트 브라우저와 소통하기 위한 소켓이 담기는 부분입니다. 만약 이 소켓이 생성되지 않았으면 createMessageSocket로 새로 생성해서 넣어줍니다.
  • BusinessNetworkConnection 인스턴스 생성 및 네트워크 연결: 위에서 초기화했던 businessNetworkConnection에 담아줍니다. 그리고 요청으로 전달 받은 buyer 정보로 네트워크 연결을 생성합니다.

이제 연결을 생성하고 나면 then 절에서 어떤 로직을 수행하는지 3부로 나누어 살펴보겠습니다.

// Order factory 정보 넣기
factory = businessNetworkConnection.getBusinessNetwork().getFactory();
let order = factory.newResource(NS, 'Order', orderNo);order = svc.createOrderTemplate(order);
order.amount = 0;
order.orderNumber = orderNo;
order.buyer = factory.newRelationship(NS, 'Buyer', req.body.buyer);
order.seller = factory.newRelationship(NS, 'Seller', req.body.seller);
order.provider = factory.newRelationship(NS, 'Provider', 'noop@dummy');
order.shipper = factory.newRelationship(NS, 'Shipper', 'noop@dummy');
order.financeCo = factory.newRelationship(NS, 'FinanceCo', financeCoID);
for (let each in req.body.items) {(function(_idx, _arr)
{ _arr[_idx].description = _arr[_idx].itemDescription;
order.items.push(JSON.stringify(_arr[_idx]));
order.amount += parseInt(_arr[_idx].extendedPrice);
})(each, req.body.items);}
  • Factory 호출해서 자원 템플릿 생성하기: 모델에서 정의된 템플릿 호출해서, 그 안에서 정의된 asset에 대한 템플릿 가져옴.
  • 요청으로 넘어온 정보를 템플릿에 넣기: order 객체를 생성하는 데에 필요한 정보를 채워넣습니다. 한 order 내에 여러 아이템들이 거래될 수 있기 때문에, 각 아이템에 대한 설명과 주문 총액은 for loop를 즉시 실행해서 처리하는 것이 인상적인 부분이네요.
// 새로운 Transaction 생성하기const createNew = factory.newTransaction(NS, 'CreateOrder');createNew.order = factory.newRelationship(NS, 'Order', order.$identifier);
createNew.buyer = factory.newRelationship(NS, 'Buyer', req.body.buyer);
createNew.seller = factory.newRelationship(NS, 'Seller', req.body.seller);
createNew.financeCo = factory.newRelationship(NS, 'FinanceCo', financeCoID);
createNew.amount = order.amount;
  • 그 다음 절에서는 위에서 생성한 order 정보를 바탕으로 Transaction 정보를 채웁니다. 직전 포스팅에서 cto 파일에 트랜잭션 정보 역시 정의한다고 이야기 했었죠. 그때 인터페이스 구현했던 내용이 여기서 정의가 되는 겁니다.
return businessNetworkConnection.getAssetRegistry(NS+'.Order').then((assetRegistry) => {
return assetRegistry.add(order)
.then(() => {
return businessNetworkConnection.submitTransaction(createNew)
.then(() => {
console.log(' order '+orderNo+' successfully added');
res.send({'result': ' order '+orderNo+' successfully added'});
})
.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1)
{console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
} else {
console.log(orderNo+' submitTransaction failed with text: ',error.message);} });})

.catch((error) => {
if (error.message.search('MVCC_READ_CONFLICT') !== -1) {console.log(orderNo+' retrying assetRegistry.add for: '+orderNo);
svc.loadTransaction(createNew, orderNo, businessNetworkConnection);
} else {console.log(orderNo+' assetRegistry.add failed: ',error.message);res.send({'result': 'failed', 'error':' order '+orderNo+' getAssetRegistry failed '+error.message});} }); })
  • 트랜잭션 실제 적용을 위해 AssetRegistry 호출: 위에서 새로운 order와 transaction 생성이 완료됐다면, 이제 그 내용을 바탕으로 AssetRegistry 를 추가해줍니다.
  • SubmitTransaction: businessNetworkConnection에서 submitTransaction 메서드를 호출하고, 위에서 생성한 createNew 객체(트랜잭션 객체)를 인자로 넘겨줍니다.
  • 이외에 에러 처리는 한 번 읽어보세요.

4 Front-end Development

프론트는 사실 Hyperledger Fabric이라고 다를 것 없습니다. 여기서는, 주문을 조회하는 페이지와 Order를 제출하는 템플릿 페이지를 두 장 만듭니다. html 페이지 두 장과 이를 컨트롤하는 javascript가 jQuery로 정의되어 있습니다. 이 부분은 화면 그리는 부분이라 포스팅에서 굳이 짚고 넘어가지는 않겠습니다.

사족: 저는 사실 모던 자바스크립트 부터 개발 공부를 시작한 사람이라, 뭐랄까 본투비 jQuery hater 입니다. ZtoB 는 jQuery로 프론트 개발을 하는데요, 나중에 시간을 따로 내서 프론트 부분을 react로 리팩토링 해야겠다는 생각을 했습니다.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade