Sawtooth
이번 포스팅에서는 Hyperledger 프로젝트 중의 하나인 Sawtooth를 소개해드리고자 합니다. Hyperledger란 리눅스 재단에서 주관하는 블록체인 오픈소스 프로젝트입니다. 제조, IoT, 물류 등 여러 산업에 걸쳐 응용 가능한 블록체인 기술을 만드는 것을 목표로 하고 있습니다. Hyperledger는 기업용 블록체인 기술을 양상하기 위해 여러 오픈소스 블록체인 프로젝트들을 인큐베이팅 하고있습니다. Sawtooth는 Hyperledger에 인큐베이팅 되고있는 프로젝트들중 하나로 분산원장(Distributed Ledger)을 구축, 배포 및 실행하기 위한 모듈식 플랫폼으로 확장가능한 transaction 유형을 가진 소프트웨어 프레임워크입니다.
특징
- PoET(scalable consensus algorithm)
- Feature swappable consensus
- Easy to develop and deploy
- Supporting muli-language(python, go, rust, javascript)
Compare with Fabric
Hyperledger Fabric은 모듈러 아키텍처를 이용한 어플리케이션 개발을 가능하게 하는 프로젝트 입니다. Fabric은 Hyperledger프로젝트 중에 가장 유명하고 전체의 핵심이 되는 프로젝트로 Consensus나 MSP서비스등 핵심 기술 요소들이 Plug-and-play방식으로 구현될 수 있도록 합니다. 아래 표는 Fabric과 Sawtooth를 비교한 표입니다. 사용되는 Consensus 알고리즘, 지원하는한 분산 네트워크의 크기, 그리고 Node들의 관리 방법이 서로 다른 특징을 가지고 있습니다.
Sawtooth | Fabric | |
---|---|---|
Consensus Algorithm | PoET | Kafka |
Size of the network | Large networks | Small networks |
Security | Roles and permissions | MSP |
Governance | Less tighter governance | Tighter governance |
Architecture
Sawtooth는 크게 Client, Validator, Transaction processor로 구성되어 있습니다. Client는 transaction의 실행을 요청하고, Validator의 데이터베이스(State)에게 데이터 조회를 요청합니다. Validator는 Sawtooth의 node로 데이터베이스(State)의 접근을 관리하고 transaction 검증 및 Transaction processor에게 transaction실행 요청을 전달합니다. Transaction Processor는 Smartcontract로 비지니스 로직을 처리하는 Process입니다.
Client
- Transaction 전송
- 데이터베이스의 데이터 조회
Validator Process
- 데이터베이스 접근 관리
- Transaction 검증
Transaction Processor(Chain code, SmartContract)
- 비니지스 로직 수행
Transaction Handling
- Transaction 병렬 실행 관리
Core
Sawtooth의 공식 문서를 보면 Sawtooth의 Core는 중요한 3개의 아키텍처 레이어로 구성되어 있다고 합니다.
- Ledger layer
- Journal layer
- Communication Layer
Ledger
보통 Ledger라고 하면 장부라는 의미로 데이터 저장소를 많이 떠올리는데 Sawtooth의 공식 문서에서는 Ledger를 개념적인 데이터 모델이라고 정의하고 있습니다. 예를 들어 Sawtooth를 사용하는 은행 Ledger가 있다고 하면, 계좌 혹은 금액 정보가 저장되는 저장소 뿐만 아니라 은행 ledger를 돌아가게 하는 모든 transaction processor, client 등을 포함하는 포괄적인 개념으로 사용되고 있습니다. 내장 된 시스템 Ledger (Endpoint Registry 및 Integer Key Registry) 외에도 원장 계층에 새 클래스를 구현하면 새로운 Trasnaction Familiy를 만들 수 있습니다. Trasnaction Familiy는 Client, State, Transaction processor로 구성됩니다.
Journal
Journal은 거래 블록에 대한 합의를 처리합니다. Journal은 블록 순서, 블록 내 트랜잭션 순서 및 트랜잭션 내용에 대한 글로벌 합의를 제공합니다.
Communication
Sawtooth Lake는 Poet을 합의 메커니즘으로 구현합니다.
Repository Structure
Sawtooth의 프로젝트는 다음과 같은 패키지들로 구성되어 있고 https://github.com/hyperledger/sawtooth-core에서 확인 할 수 있습니다.
- Gossip networking layer
- Basic transaction, block, and message objects
- PoET journal consensus mechanism
- Built-in transaction families
- Validator(acts as a node on the gossip network)
Example of Transaction Processors(Smart Contract)
Transaction processor의 전체적인 구조는 Fabric의 chaincode
와 유사합니다. 독립적인 process로 실행되며, 실행시 validator와 연결을 시도하고 validator가 client로 부터 transaction요청을 받으면 Transaction processor의 Apply함수를 통해 transaction을 실행 시킵니다.
Package Structure
— src |
——— main.go |
——— handler |
—————— hander.go |
SmallBack Transaction Processor
type SmallbankHandler struct {
}
func (self *SmallbankHandler) FamilyName() string {
return "smallbank"
}
func (self *SmallbankHandler) FamilyVersions() []string {
return []string{"1.0"}
}
func (self *SmallbankHandler) Namespaces() []string {
return []string{namespace}
}
func (self *SmallbankHandler) Apply(request *processor_pb2.TpProcessRequest, context *processor.Context) error {
payload, err := unpackPayload(request.GetPayload())
if err != nil {
return err
}
logger.Debugf("smallbank txn %v: type %v",
request.Signature, payload.PayloadType)
switch payload.PayloadType {
case smallbank_pb2.SmallbankTransactionPayload_CREATE_ACCOUNT:
return applyCreateAccount(payload.CreateAccount, context)
case smallbank_pb2.SmallbankTransactionPayload_DEPOSIT_CHECKING:
return applyDepositChecking(payload.DepositChecking, context)
case smallbank_pb2.SmallbankTransactionPayload_WRITE_CHECK:
return applyWriteCheck(payload.WriteCheck, context)
case smallbank_pb2.SmallbankTransactionPayload_TRANSACT_SAVINGS:
return applyTransactSavings(payload.TransactSavings, context)
case smallbank_pb2.SmallbankTransactionPayload_SEND_PAYMENT:
return applySendPayment(payload.SendPayment, context)
case smallbank_pb2.SmallbankTransactionPayload_AMALGAMATE:
return applyAmalgamate(payload.Amalgamate, context)
default:
return &processor.InvalidTransactionError{
Msg: fmt.Sprintf("Invalid PayloadType: '%v'", payload.PayloadType)}
}
}
func applyCreateAccount(createAccountData *smallbank_pb2.SmallbankTransactionPayload_CreateAccountTransactionData, context *processor.Context) error {
account, err := loadAccount(createAccountData.CustomerId, context)
if err != nil {
return err
}
if account != nil {
return &processor.InvalidTransactionError{Msg: "Account already exists"}
}
if createAccountData.CustomerName == "" {
return &processor.InvalidTransactionError{Msg: "Customer Name must be set"}
}
new_account := &smallbank_pb2.Account{
CustomerId: createAccountData.CustomerId,
CustomerName: createAccountData.CustomerName,
SavingsBalance: createAccountData.InitialSavingsBalance,
CheckingBalance: createAccountData.InitialCheckingBalance,
}
saveAccount(new_account, context)
return nil
}
func applyDepositChecking(depositCheckingData *smallbank_pb2.SmallbankTransactionPayload_DepositCheckingTransactionData, context *processor.Context) error {
account, err := loadAccount(depositCheckingData.CustomerId, context)
if err != nil {
return err
}
if account == nil {
return &processor.InvalidTransactionError{Msg: "Account must exist"}
}
new_account := &smallbank_pb2.Account{
CustomerId: account.CustomerId,
CustomerName: account.CustomerName,
SavingsBalance: account.SavingsBalance,
CheckingBalance: account.CheckingBalance + depositCheckingData.Amount,
}
saveAccount(new_account, context)
return nil
}
func applyWriteCheck(writeCheckData *smallbank_pb2.SmallbankTransactionPayload_WriteCheckTransactionData, context *processor.Context) error {
account, err := loadAccount(writeCheckData.CustomerId, context)
if err != nil {
return err
}
if account == nil {
return &processor.InvalidTransactionError{Msg: "Account must exist"}
}
new_account := &smallbank_pb2.Account{
CustomerId: account.CustomerId,
CustomerName: account.CustomerName,
SavingsBalance: account.SavingsBalance,
CheckingBalance: account.CheckingBalance - writeCheckData.Amount,
}
saveAccount(new_account, context)
return nil
}