본문 바로가기

개발

Rust 버전의 노마드 코인 nomadcoin-rs

반응형

안녕하세요, 오늘은 제가 2년 전에 했던 사이드 프로젝트 'nomadcoin-rs' 경험을 소개해 볼까 합니다. 작업한 지 2년도 더 된 프로젝트이지만 블로그에서 한 번도 다뤄본 적이 없어 다루어 보려고 합니다. 해당 프로젝트는 '노마드 코인: Go로 암호화폐 만들기'라는 노마드 코더의 온라인 강의를 Rust로 클론 코딩하는 프로젝트였습니다. 해당 프로젝트의 메인 언어는 golang이었지만 Rust에 대한 관심이 높았고, 막 공식 문서 공부를 끝낸 참이어서 언어에 좀 더 익숙해지기 위해 Rust로 프로젝트를 진행하기로 결심했습니다.

프로젝트 목표

해당 프로젝트를 진행함에 있어서 저는 크게 두 가지 목표가 있었습니다.

  1. 블록체인과 그것을 어떻게 만드는지 기술적으로 이해하기
  2. Rust라는 언어에 익숙해지기 (Be rustcean), Rust로 TDD 진행해 보기

프로젝트 히스토리

  1. 간단한 Blockchain 생성하기 (4강)
    • 위의 작업에서 간단한 블록체인 structfunctions을 만들었습니다.
    • 데이터를 해싱하는 기능을 간단하게 구현하였습니다.
  2. Rocket 라이브러리로 Explorer HTML 웹페이지 만들기 (5강)
    • 어떤 라이브러리를 사용할지 고민하던 중 가장 쉬워 보였던 Rocket을 사용하였습니다.
  3. Rocket으로 Rest API 구현하기 (6강)
    • 강의에서는 explorer와 RestAPI를 모두 지원하도록 프로젝트를 구성했지만 프로젝트 구조에 어색함을 느껴 RestAPI만 메인 포인트로 지원하도록 하였습니다.
    • 강의에서 니코쌤은 Block에 대해서 포인터(Reference)를 사용했지만 Rust에서는 Lifetime 이슈가 있어 이를 구현하기 어려웠습니다. 많은 시행착오를 거치다가 Korean Rust Discord에 질문을 했고, 대부분의 경우 clone()을 사용하는 것이 그렇게 비싸지 않고 최적화는 나중에 하는 것이 좋다는 답변을 얻어 clone()으로 이를 구현했습니다.
  4. Blockchain 스냅숏과 Blocks를 저장하기 위해 데이터베이스를 사용 (8강)
    • 여기서는 Rust 버전의 Bolt DB(강의에서 사용)인 nut을 사용했지만 버그가 있다고 느껴 아래 작업에서 PickleDB로 변경하였습니다.
    • 여기서부터 서버를 재시작해도 생성한 Blockchain이 유지되었습니다.
  5. Blockchain의 자격 증명(Proof of work) 개념을 적용해서 코인 채굴 (9강)
  6. testutils을 통해 각 테스트시에 데이터베이스 드롭
    • 각 테스트 이후 데이터베이스 리소스를 드롭하고 싶었습니다. 그래서 DBResoource라는 struct를 만들었고 Drop trait을 사용하여 이를 가능하게 하였습니다. 이런 방식으로 데이터베이스를 초기화하는 것이 좋은 인터페이스라고 생각되지 않았습니다. 그래서 나중에는 /tmp에 테스트마다 다른 DB를 생성하도록 처리하였습니다. (이때 당시에 베스트 프랙티스를 찾고 싶었는데 아직까지 찾아보지 않았네요.)
  7. Blockchain 네트워크에서 transaction 구현 (10강)
    • 작동하게 만들기 굉장히 어려웠지만 어떻게든 해냈습니다.
    • 강의에서는 golang이 레이스 컨디션 이슈가 있었는데, Rust는 동시성 이슈가 있는 상황에서 Rust는 컴파일 타임에 mutex 잠금을 강제했기 때문에 상당히 안전한 언어라고 느껴졌습니다.
  8. transaction 검증을 위해 지갑 구현 (11강)
    • p256 Elliptic Curves algorithm를 위해 p256 라이브러리를 사용했고, hex 문자열 생성을 위해 hex 라이브러리를 사용했습니다.
  9. P2P 기능 구현 (12강)
    • 강의에서는 websocket을 사용하여 이를 구현했습니다. 하지만 당시에 Rocket 라이브러리에서 websocket을 지원하지 않았습니다. 이때 이를 알고 멘붕이 왔는데 기지를 발동하여 SSE(Server Sent Event)를 통해 이를 구현하였습니다. 좀 더럽게 구현하게 된 측면이 있었습니다.
    • 비동기를 지원하기 위해 tokio 라이브러리를 사용하였습니다.
    • 비동기 상황에서 lifetime을 컨트롤하기 위한 Arc에 대해서 좀 더 자세히 알게 되었습니다.
  10. p2p 기능 리팩터링
    • 코드 적응력을 높이기 위해 이벤트 핸들러들이 같은 인자들을 받도록 하였습니다.
  11. 전체 코드와 테스트 코드 리팩터링
    • 저장소 계층을 만들고, 테스트를 위해 테스트 더블을 사용하였습니다.
    • immutables를 mutables로 빌리기 위해서 MutexRefCell을 사용해야 함을 알게 되었습니다.
  12. [Dockerfile, docker-compose 생성 및 http 파일 리팩터링]
    • 강의에서는 다루지 않았지만, 도커 환경에서 해당 프로젝트를 띄우고 싶어 이를 만들었습니다.
    • First steps with Docker + Rust블로그를 참고하였습니다.

결과 및 교훈

  • 무사히 해당 프로젝트를 완수했습니다. 꽤 오랜 시간이 지났지만 마지막 커밋을 넣고 문서를 작성하면서 기뻤던 기억이 생생하게 납니다. 처음 목표했던 2가지 내용도 달성했습니다.
  • 해당 프로젝트를 마무리하고 '노마드코더'에 해당 프로젝트를 제보하였고 뉴스레터에서 소개되기도 하였습니다.

  • 이후에 flopha라는 git versioning cli를 Rust로 만들기도 하였습니다. (아직도 잘 쓰고 있어요.)
  • 다른 언어/프레임워크로 강의내용을 구현한 경험이 매우 좋았습니다. 보통 강의를 들을 때 아무 생각 없이 코드를 따라치게 되는데 다른 언어나 프레임워크를 쓰게 되면 작동원리를 이해하고, 고민할 수밖에 없기 때문에 흡수가 더 잘되었습니다.

2년간 잊고 있던 프로젝트인데 복기하면서 그 때의 감정도 느껴보게 되고 최근 하고있지 않던 Rust에 대한 열의도 불타오르네요. 조만간 Rust로 사이드 프로젝트를 하나 더 해볼까 합니다 😎

반응형