Libra — Validator 핵심 컴포넌트 이해하기

Seungwon Go
ReturnValues
Published in
13 min readJun 23, 2019

--

By Seungwon Go, CEO & Founder at ReturnValues (seungwon.go@returnvalues.com)

Validator의 핵심 컴포넌트를 살펴보기 전에, 앞서 작성된 “Libra Protocol”, “Libra — Life of a Transaction” 을 읽어보시기 바랍니다.

우리는 앞서 클라이언트가 요청한 트랜잭션을 Validator가 어떤 과정을 거쳐 검증하고 다른 Validator들과 합의를 한 후 블록체인에 저장하는지 살펴봤습니다.

이때 Validator의 핵심 컴포넌트인 AC(Admission Control), VM(Virtual Machine), Mempool, Consensus, Execution, Storage 컴포넌트에 대해서 알게 되었습니다.

각각의 컴포넌트가 어떻게 동작을 하고, 어떤 역할을 수행하는지 좀더 자세히 알아보도록 하겠습니다.

Admission Control

AC는 Validator 노드의 유일한 외부 인터페이스로써 클라이언트가 요청한 트랜잭션을 받는 역할을 합니다. 또한 실행에 앞서 VM(Virtual Machine)을 통해 기본적인 검증 작업을 한후 통과된 트랜잭션에 대해서 Mempool로 전송하는 역할을 담당하고 있습니다.

이미지 출처 : 리브라 공식문서

Client → AC (AC.1)

클리언트는 AC::SubmitTransaction()함수를 이용해서 Validator의 AC(Admission Control)로 트랜잭션을 전송합니다.

AC → VM (AC.2)

AC는 유효하지 않은 트랜잭션을 빠르게 검토하기 위해 VC의 VM(Virtual Machine)에 액세스하여 VM::ValidateTransaction()함수를 통해 트랜잭션에 대한 예비 검사를 수행합니다.

AC → Mempool (AC.3)

AC는 VM::ValidateTransaction() 함수를 실행한 결과가 아무런 에러가 없다면, Mempool::AddTransactionWithValidation().함수를 호출하여 트랜잭션을 Mempool로 이동시킵니다. Mempool은 추가된 트랜잭션의 sequence 번호가 보낸 사람의 account의 sequence 번호보다 크거나 같은 경우에만 AC로 부터 트래잭션을 받아드립니다. sequence 번호를 비교하므로써, 재진입 공격을 막을 수 있습니다.

AC → Storage (AC.4)

클라이언트가 Alice의 계정 잔액 정보 같은 정보를 읽기 위해 쿼리를 수행하면, AC는 Storage 컴포넌트에 바로 접속해서 요청된 정보를 얻습니다.

Virtual Machine (VM)

클라이언트가 전송한 트랜잭션에는 transaction script 가 있고, VM은 Move bytecode 안의 transaction script을 검증하고 실행하는 역할을 수행합니다.

이미지 출처 : 리브라 공식문서

AC → VM (VM.1)

AC가 클리이언트가 전송한 트랜잭션을 받고 나면, VM의 VM::ValidateTransaction() 함수를 호출하여 트랜잭션을 검증하게 됩니다.

VM → Storage (VM.2)

AC 혹은 Mempool이 VM::ValidateTransaction()함수를 호출하면, VM은 Storage로 부터 보낸사람의 account 정보를 로드하고 트랜잭션과 다음과 같은 검증 작업을 진행합니다.

  • 트랜잭션에 서명된 정보가 트래잭션을 보낸 사람의 account의 서명과 동일한지 검증합니다.
  • 보낸 사람의 account의 인증키가 공개키의 hash와 같은지 검증합니다.
  • 트랜잭션의 sequence 번호가 보낸사람의 account의 sequence 번호 보다 작지 않은지 검증합니다. 동일한 트랜잭션이 재호출 되는것을 방지하기 위함입니다.
  • 트랜잭션에 포함된 Program의 형식이 잘못되었는지 확인합니다. 형식이 잘못된 프로그램이 VM에서 실행되는것을 방지 하기 위함입니다.
  • 보낸 사람 계정에 충분한 잔액이 있는지 확인합니다. 트랜잭션에 지정된 최대 가스금액 보다 잔고가 더 많아야 합니다.

Execution → VM (VM.3)

Execution 컴포넌트는 VM의VM::ExecuteTransaction(). 함수를 이용하여 트랜잭션을 실행시킵니다. 이때 트랜잭션을 실행시킨다고해서 원장의 상태를 업데이트하고 결과를 저장하지는 않습니다. 나중에 Validator들에 의해 합의가 완료되어야 원장에 상태가 업데이트되고 저장이 되게 됩니다.

Mempool → VM (VM.4)

Mempool은 트랜잭션을 다른 Validator로 부터 받는데, 이때 shared mempool를 통해서 받게 되는데, 좀 더 정확히 확인해봐야 겠지만, shared mempool이라는 공유 mempool이 있고, 이곳에 트랜잭션이 추가가 되면, 자동으로 Validator의 mempool로 전이가 되는것 같습니다. Mempool는 VM::ValidateTransaction() 함수를 실행시켜서 전송된 트랜잭션을 검증하게 됩니다.

Mempool

Mempool은 실행을 기다리는 트랜잭션을 잠시 쌓아놓는 공유 버퍼입니다. 새로운 트랜잭션이 Mempool에 추가가 되면, Mempool은 트랜잭션을 다른 모든 Validator에 공유하게 됩니다. 이때 네트워크의 자원 낭비를 줄이기 위해서 “shared mempool”을 이용하게 됩니다. Mempool은 트랜잭션을 다른 Validator로 전송하는 역할을 하며, 또한 다른 Validator로 받은 트랜잭션을 자신의 Mempool로 추가합니다. 즉 다른 Validator와 인터페이스 역할을 하게 됩니다.

이미지 출처 : 리브라 공식문서

AC → Mempool (MP.1)

  • AC가 VM을 통해 트랜잭션에 대한 기본적인 검증을 하고 나면, AC는 트랜잭션을 Mempool로 보내게 됩니다.
  • Mempool은 트랜잭션의 sequence 번호가 보낸사람의 account의 sequence 번호보다 같거나 클때만 추가하게 됩니다.

Mempool → Other Validators (MP.2)

  • Mempool은 shared mempool를 이용해서 트랜잭션을 다른 Validator와 공유하게 됩니다.
  • 다른 Validator들은 공유된 트랜잭션을 자신의 Mempool에 추가합니다.

Consensus → Mempool (MP.3)

  • 클라이언트로 부터 트랜잭션을 받은 Validator가 leader가 되고, leader의 Consensus 컴포넌트는 mempool로 부터 트랜잭션으 블록을 가져와서 다른 Validator들로 트랜잭션 블록을 복제합니다. 이는 블록안의 트랜잭션의 순서와 트랜잭션 실행 결과에 대한 합의를 위해 수행됩니다.

Mempool → VM (MP.4)

Mempool이 다른 Validator로 부터 트랜잭션을 받으면 VM의 VM::ValidateTransaction()on 함수를 실행시켜서 트랜잭션을 검증합니다.

Consensus

Consensus 컴포넌트는 네트워크에 있는 다른 Validator들과 consensus protocol 에 참여하여 트랜잭션 블록들의 순서를 결정하고 실행된 결과에 대한 동의를 할지에 대한 책임을 가지고 있습니다.

이미지 출처 : 리브라 공식문서

Consensus → Mempool (CO.1)

Validator가 leader/proposer이면(클라이언트로 부터 최초로 트랜잭션을 받은 Validator),합의를 위해서 Mempool::GetBlock()함수를 이용하여 Mempool로 부터 트랜잭션 블록을 가져오고 proposal을 만들게 됩니다.

Consensus → Other Validators (CO.2)

다른 Validator들에게 proposed 블록을 보냅니다.

Consensus → Execution, Consensus → Other Validators (CO.3)

  • 트랜잭션 블록을 실행시키기 위해서는 Consensus 컴포넌트는 Execution 컴포넌트와 통신하게 됩니다. Consensus 컴포넌트는 Execution 컴포넌트의 Execution:ExecuteBlock() 함수를 실행시킵니다.
  • 블록안의 트랜잭션 모두를 실행하고 나면, Execution 컴포넌트는 Consensus 컴포넌트로 실행 결과를 보내줍니다.
  • Consensus 컴포넌트는 실행된 결과에 서명을 하고, 이 결과를 바탕으로 다른 Validator들과 합의에 도달하도록 시도합니다.

Consensus → Execution (CO.4)

동일한 실행 결과에 대해서 충분한 Validator들이 투표를 하고나면, Consensus 컴포넌트는 Execution 컴포넌트의Execution::CommitBlock() 함수를 이용해서 현재 블록이 커밋이 될 준비가 되었다고 알려주게 됩니다.

Execution

Execution 컴포넌트는 트랜잭션 블록의 실행을 조정하고 Consensus 컴포넌트의 의해 투표 할 수있는 일시적인 상태를 유지하는 것입니다.

이미지 출처 : 리브라 공식문서

Consensus → Execution (EX.1)

  • Consensus 컴포넌트는 Execution 컴포넌트로 트랜잭션을 블록을실행할것을 요청합니다. Execution::ExecuteBlock().
  • Execution 컴포넌트는 Merkle accumulators 의 관련 부분의 사본을 보유하는 “scratchpad”를 관리합니다. 이 정보는 블록 체인의 현재 상태의 루트 해시를 계산하는 데 사용됩니다. Scratchpad(ScratchPad Memory) 는 캐시에서 하드웨어적으로 제공하는 태그 및 일관성 기능이 제외된 메모리입니다. Scratchpad는데이터를 직접적으로 관리해야 한다는 부담이 있는 반면, 확장성이나 에너지, 공간 효율성 등에서 캐시에 비해 뛰어난 성능을 보이므로 다양한 아키텍처에서 이를 도입하고 있습니다.
  • 현재 상태의 루트 해시는 블록의 트랜잭션에 대한 정보와 결합되어 accumulator의 새로운 루트 해시를 결정합니다. 이는 데이터를 저장하기 전에 수행되며 Validator의 쿼럼에 의해 합의가 이루어지기 전까지 상태나 트랜잭션이 저장되지 않도록합니다.
  • Execution 컴포넌트가 speculative 루트 해시를 계산 한 다음 Consensus 컴포넌트는 이 루트 해시에 서명을 하고 다른 Validator들과 함께 이 루트 해시에 대한 합의를 시도합니다.

Execution → VM (EX.2)

Consensus 컴포넌트가 Execution 컴포넌트의 Execution::ExecuteBlock() 함수를 이용해서 트랜잭션을 블록을 실행했을때, Execution 컴포넌트는 VM을 이용해서 트랜잭션 블록의 실행 결과를 계산하게 됩니다.

Consensus → Execution (EX.3)

Validator들의 쿼럼이 블록의 실행 결과를에 동의를 하면, 각 Validator의 Consensus 컴포넌트는 Execution::CommitBlock() 함수를 이용해서 실행결과를 알려주게 됩니다. 이렇게 되면 이 블럭은 커밋 준비가 된것입니다.

Execution → Storage (EX.4)

Execution 컴포넌트는 “scratchpad” 로 부터 정보를 받아와서 Storage로 Storage::SaveTransactions(). 함수를 이용해서 결과를 저장하게 됩니다. 그러고 나면, Execution 컴포넌트는 “scratchpad”에서 기존 정보가 더이상 필요가 없으므로 삭제하게 됩니다.

Storage

Storage 컴포넌트는 합의된 트랜잭션 블록과 그 실행결과를 유지합니다. 트랜잭션 블록은 과반수 이상의 Validator들이 합의가 되었을때 Storage에 저장이 됩니다.

이미지 출처 : 리브라 공식문서

VM → Storage (ST.1)

AC 또는 Mempool은 VM::ValidateTransaction() 함수를 이용해서 트랜잭션을 검증합니다. VM::ValidateTransaction() 함수를 호출하면 보낸 사람의 account 정보를 Stroage로 부터 가져오고, 트랜잭션과 비교하여 검증을 수행합니다.

Execution → Storage (ST.2)

Consensus 컴포넌트는 Execution::ExecuteBlock()함수를 호출하고 Execution 컴포넌트는 실행결과를 결정하기 위해서 Scratchpad에 결합된 Storage에서 현재 상태를 읽습니다.

Execution → Storage (ST.3)

  • 트랜잭션 블록에 대한 합의가 되고 나면, Execution 컴포넌트는 트랜잭션 블록을 영구히 블록체인에 저장하기 위해서 Storage 컴포넌트의 Storage::SaveTransactions() 함수를 실행합니다. 이때 누가 현재 트랜잭션 블록에 동이했는지 validator의 서명을 함께 저장하게 됩니다.
  • The in-memory data in “scratchpad” for this block is passed to updated storage and persist the transactions. 현재 저장되는 블록을 위한 Scratchpad의 인메모리 데이터는
  • Storage가 갱신되면, 각 트랜잭션에 의해 수정 된 모든 자원의 sequence 번호가 그에 따라 업데이트됩니다.

AC → Storage (ST.4)

클라이언트가 블록체인상의 정보를 읽기 위해서 쿼리를 하면, AC는 요청된 정보를 읽기 위해서 Storage에 바로 접근합니다.

좀 긴 내용이지만, Validator의 핵심 컴포넌트인 AC, VM, Mempool, Consensus, Execution, Storage에 대해서 알아봤습니다. 여기까지 보고 나서, 앞서 작성되었던 “Libra — Life a Transaction” 글을 다시 읽어 보신다면 좀더 쉽게 이해를 하실 수 있을것입니다.

--

--