Libra — Life of a Transaction

Seungwon Go
ReturnValues
Published in
7 min readJun 20, 2019

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

본 내용에 앞서 transaction에는 어떤 내용이 포함되어져 있는지 앞서 작성된 “Libra Protocol” 부분을 읽어보시기 바랍니다.

설명에 앞서 우리는 Validator 노드에는 Admission Control, Virtual Machine, Mempool, Consensus, Execution, Storage 컴포넌트가 있는것을 확인하였습니다. 실제 트랜잭션이 일어나고 해당 트랜잭션이 실행 후 블럭에 기록될때까지의 트랜잭션 처리 과정을 설명하기 위해서 리브라 공식 문서에 나와 있는데로 전체 시나리오 기반으로 순서대로 설명하는것이 맞을지, Validator에 있는 각각가의 컴포넌트에 대한 설명을 먼저하는것이 맞을지 굉장히 고민이 되었습니다.

실제로 제가 이 부분을 이해하기 위해서 몇차례 걸쳐서 컴포넌트 기능을 별도로 설명하고 있는 문서들을 여러차례 찾아봐서야 이해를 할 수 있었기 때문입니다. 저도 일단은 리브라 공식 문서에 나와 있는 순서에 맞춰서 설명을 할까 합니다. 추후 각 컴포넌트의 역할은 별도로 정리하도록 하겠습니다.

리브라 공식문서에서 제공하는 시나리오는 아래와 같습니다. 일단 시나리오를 먼저 보고 얘기하도록 하겠습니다.

트랜잭션 시나리오

  • Alice와Bob이 account를 가지고 있습니다.
  • Alice는 110 Libra를 가지고 있습니다.
  • 현재 Alice account의 sequence 번호는 5번입니다. (Alice가 이미 트랜잭션을 5번 실행했다는것을 의미합니다.)
  • 총 100개의 Validator가 있습니다. 편의상 V1 ~ V100 이라고 하겠습니다.
  • 클라이언트가 트랜잭션을 V1에 전송했습니다. 편의상 이때 전송된 트랜잭션을 T5라고 하겠습니다. (T5는 Alice가 실행시킨 5번째 트랜잭션입니다.)
  • 클라이언트가 전송한 트랜잭션을 받은 Validator를 propoer 혹은 leader validator라고 부릅니다. 여기서는 Validator V1이 proposer/leader 입니다.

보시는것 처럼, 시나리오는 매우 간단합니다. 위의 시나리오가 실제 요청이 되어서 Validator를 통해 검증이 되고, 실행이 되고, 합의가 되어 최종적으로 블럭에 저장되기까지의 전체 과정을 도식화 하면 아래와 같습니다.

LIFECYCLE OF A TRANSACTION(이미지 출처 : 리브라공식문서)

일단 우리는 아직 리브라 전문가가 아니니까, 위에 도식이 꽤 어려워 보일겁니다. 이제 하나씩 이해해 보도록 하겠습니다.

클라이언트로 부터 트랜잭션이 일어나고 Validator를 통해 트랜잭션이 실행되고, 블록에 기록되기 까지 전체 주기를 크게 5가지로 구분할 수 있습니다.

1. Accepting the Transaction (그림에서 1~3번)

1. 클라이언트가 AC::SubmitTransaction() 함수를 이용해서 트랜잭션 전송하면, 해당 트랜잭션을 요청받은 leader인 V1의 AC(Admission Control)가 트랜잭션을 받는 역할을 하게 됩니다. 참고로 AC는 Validator 노드의 컴포턴트 중 유일한 외부 인터페이스 입니다.

2. AC는 VM::ValidateTransaction() 함수를 이용해서 요청된 트랜잭션을 Mempool에 담기전에 VM(Virtual Machine)을 이용해서 해당 트랜잭션이 유효한 트랜잭션이 맞는지 기본적인 검사를 하게됩니다. 유효한 서명인지, Alice의 잔고가 충분히 있는지, 혹시 지금 실행되고 있는 transaction이 재실행된건 아닌지 등의 유효성 검증을 하게 됩니다.

3. VM 컴포넌트를 통한 유효성 검증을 통과하고 나면, AC는 Mempool::AddTransactionWithValidation() 함수를 이용해서 트랜잭션을 Mempool로 보냅니다.

검증이 완료된 트랜잭션들이 Mempool에 차곡 차곡 쌓이고 Validator 노드는 Mempool을 이용해서 트랜잭션을 다른 Validator들과 공유하게 됩니다.

2. Sharing the Transaction With Other Validators (그림에서 4~5번)

4. Mempool은 전송된 트랜잭션을 인메모리 버퍼에 저장합니다. Mempool은 실제 트랜잭션을 실행시키기전에 대기하는 장소로, 여러개의 트랜잭션을 보관할 수 있습니다.

5. V1은 shared-mempool 프로토콜을 사용하여 다른 Validator들과 전송된 트랜잭션을 공유하게 됩니다. V1을 제외한 나머지 V2~V100은 각자의 mempool로 전송된 트랜잭션을 받게 됩니다.

3. Proposing the Block (그림에서 6~7번)

6. leader인 V1은 Mempool::GetBlock() 함수를 이용해서 mempool로 부터 트랜잭션 블럭을 불러와서 consensus 컴포너트를 통해 다른 Validator들에게 해당 블럭의 합의를 제안하게 됩니다.

7. V1의 consensus 컴포넌트는 나머지 모든 Validator들 사이에서 제안된 블럭의 트랜잭션 순서에 따라 합의(LibraBFT)를 중재하는 역할을 합니다.

4. Executing the Block and Reaching Consensus (그림에서 8~11번)

8. 합의가 완료된 블럭은 execution 컴포넌트로 전송됩니다. consensus 컴포넌트는 execution component의 Execution:ExecuteBlock() 호출 함으로써, 트랜잭션 블록의 실행 결과를 받을 수 있습니다.

9. execution 컴포넌트는 VM(Virtual Machine)에서 트랜잭션의 실행을 관리합니다.

10. 블럭안에 있는 트랜잭션들을 실행하고 난 후 execution 컴포넌트는 블럭안에 있는 트랜잭션들을 Merkle accumulator에 추가합니다. 이것을 Merkle accumulator의 인메모리/임시 버전이라고 합니다. 트랜잭션들을 실행한 결과는 consensus 컴포넌트로 반환합니다.

11. V1 은 블럭의 실행결과를 가지고 다른 Validator들과 합의를 요청하게 됩니다.

5. Committing the Block

12. 블록의 실행결과가 전체 validator의 과반수 이상 동의 되면, V1의 execution 컴포넌트는 블록 실행 결과를 speculative execution 캐시로 부터 읽어드린 후 블럭안의 모든 트랜잭션을Execution::CommitBlock() 함수를 이용해서 persistent storage로 커밋하고, Storage::SaveTransactions() 함수를 이용해서 저장 하게 됩니다.

13. 이제 Alice의 계좌에는 100 Libra가 남게 되고, sequence 번호는 6이 됩니다. 만약에 Bob에 의해서 Alice가 실행시켰던 T5를 다시 실행시켜도, 이미 sequence 번호가 6으로 바뀌었기 때문에 동일한 트랜잭션을 다시 실행시킬 수 없게 됩니다.

이상으로 클라이언트가 요청한 트랜잭션이 Validator 노드의 각 컴포넌트를 이용해서 처리되어, 다른 Validator들과 합의를 이루고 최종적으로 저장되는 과정을 살펴 보았습니다. 좀더 깊이 있게 이해 하기 위해서는 Validator의 각 컴포넌트가 하는 역할을 좀 더 상세히 살펴 볼 필요가 있습니다.

다음은 Validator의 각 컴포넌트가 하는 역할을 좀 더 상세히 살펴보도록 하겠습니다.

--

--