신규: Helius가 Light Protocol을 인수했습니다
전구와 회로 기판
블로그/기초

아이디어를 Solana 프로그램(스마트 컨트랙트)으로 구현하는 방법

개발자 교육X의 Mike MacCanaLinkedIn의 Mike MacCana
읽는 데 7분

Solana 프로그램을 설계해 보겠습니다. Solana 생태계에는 Solana 프로그래밍 모델을 이해하고 작은 데모 프로그램을 만드는 방법을 다룬 글이 많습니다. 초보자가 Solana와 Anchor를 배우기에 매우 유용합니다. 하지만 실제 프로그램을 만들 때는 더 복잡한 요소를 고려해야 합니다. 

이 글에서는 실제로 만들고자 하는 프로그램을 살펴보며 다음 내용을 다룹니다. 

  1. 데이터를 Solana 계정으로 정의하기
  2. 데이터 간 관계 만들기
  3. 프로그램 내부에 토큰 저장하기
  4. 인덱스로 읽기 속도 높이기
  5. 샤딩으로 쓰기 속도 높이기

이 예시의 목표는 여러분의 아이디어를 Solana 프로그램과 계정으로 모델링하는 방법을 이해하도록 돕는 것입니다. 

참고: 이 글은 독자가 Anchor의 기본 개념을 알고 있다고 가정합니다. 

Solana 프로그램 예시 — 예측 시장 구축하기

오늘 설계할 프로그램은 Polymarket, Hedgehog 또는 Drift BET와 유사한 예측 시장입니다. 예측 시장이 익숙하지 않다면, 특정 이벤트에서 발생할 수 있는 여러 결과에 베팅하는 서비스라고 생각하면 됩니다. 슈퍼볼 우승팀, 아카데미 ‘감독상’ 수상자, 정부가 특정 시각까지 발표할지 여부처럼 명확한 결과가 있는 현실 세계의 모든 이벤트가 대상이 될 수 있습니다. 당첨 결과에 베팅한 사람은 전체 베팅 풀에서 당첨금을 받습니다.

예측 시장 아키텍처

다음은 예측 시장의 핵심 아키텍처와 각 항목의 관계입니다.

  • 여러 이벤트가 있습니다.
  • 각 이벤트에는 여러 결과가 있습니다. 최종적으로 이 중 하나가 당첨 결과로 결정됩니다.
  • 사용자는 각 결과에 여러 번 베팅합니다. 사용자가 베팅하면 자금이 해당 이벤트의 당첨 풀에 추가됩니다.

이벤트가 ‘정산’되면, 즉 당첨 결과가 확정되면 다음과 같이 처리됩니다.

  • 당첨 결과에 베팅한 사용자는 당첨금을 청구할 수 있습니다
  • 당첨자는 당첨 풀에서 운영 수수료를 제외한 일부를 받습니다
  • 각 당첨자의 당첨 풀 지분은 당첨 결과에 걸린 전체 베팅 중 자신이 베팅한 비율을 기준으로 계산됩니다.

1. 앱을 Solana 계정으로 설계하기

관계형 데이터베이스를 사용하는 프로그램을 설계한다면 다음 사항을 고려할 것입니다.

  • 유사한 데이터 항목을 테이블의 행으로 저장합니다 
  • 기본 키를 사용해 각 데이터를 고유하게 식별합니다
  • 테이블의 열은 각 데이터 항목에 필요한 속성과 데이터 타입을 정의합니다

Solana에서는 이러한 개념이 대략 다음과 같이 대응됩니다.

  • 유사한 데이터 항목은 동일한 계정 타입으로 저장됩니다 
  • 주소를 사용해 각 데이터를 고유하게 식별합니다
  • 구조체(키와 데이터 타입)는 각 항목의 속성을 정의합니다

동일한 데이터를 기존 데이터베이스 테이블과 Solana 계정으로 각각 나타내면 다음과 같습니다.

2. 데이터 간 관계 매핑하기

Solana에서 데이터 항목 간 관계는 매우 다르게 작동합니다. 기존 데이터베이스는 관계, 즉 테이블 간 논리적 연결을 사용합니다. Solana는 일대다 관계를 주소 벡터로 처리하며, 각 주소에는 해당 항목의 계정이 있습니다. 

예를 들어 이벤트의 '결과'는 결과 주소의 벡터입니다. Anchor에서는 이를 **Vec<Pubkey>**로 표시합니다. 다만 PDA 주소는 이 예시의 결과 주소처럼 실제로는 공개 키가 아닙니다.

3. 토큰 저장하기

기존 데이터베이스와 달리 Solana 프로그램은 단순한 잔액 숫자뿐 아니라 실제 자금도 계정 내부에 저장할 수 있습니다.

이 예시에서 이벤트에는 당첨 풀을 위한 토큰 계정이 필요합니다. 이벤트의 PDA가 당첨 풀 계정을 소유합니다. 사용자는 베팅할 때 이 계정으로 토큰을 보냅니다. 더 중요한 점은 사용자가 당첨금을 청구할 때 프로그램이 이벤트 계정으로 트랜잭션에 서명하여 당첨 풀에서 토큰을 전송한다는 것입니다.

4. 읽기 작업 줄이기

프로그램은 사용자에게 최대한 빠르게 반응해야 합니다. 개발자는 불필요한 계정 읽기에 비용을 낭비하지 않아야 합니다. 누적 합계와 인덱스를 사용하면 프로그램의 응답성과 효율성을 모두 높일 수 있습니다.

누적 합계 계산하기

사용자가 당첨금을 청구할 때 각 결과에 베팅된 정확한 금액을 알아야 합니다. 예측 시장은 당첨 풀 × 베팅 금액 ÷ 당첨 결과의 총 베팅 금액이라는 공식으로 당첨자의 지급액을 계산합니다.

현재는 각 베팅 금액을 해당 베팅 계정에만 저장합니다. 특정 결과의 총 베팅 금액을 계산하려면 모든 베팅 계정을 읽고 금액을 합산해야 합니다.

대신 각 결과에 total_amount 필드를 추가하고 사용자가 베팅할 때마다 값을 늘려 보겠습니다. 그러면 사용자가 당첨되었을 때 해당 결과의 모든 베팅을 읽지 않고도 지급액을 쉽게 계산할 수 있습니다.

인덱스 사용하기

특정 사용자 계정의 모든 베팅도 찾아야 합니다. getProgramAccounts()를 사용해 모든 베팅 계정을 가져온 다음 베팅한 사용자의 주소가 해당 사용자 주소로 설정된 항목을 필터링할 수 있습니다. Helius의 빠른 getprogramAccounts()를 사용하면 다른 RPC 제공업체보다 훨씬 빠르지만, 인덱스도 널리 사용되는 대안입니다. 

따라서 각 사용자의 베팅을 저장하는 인덱스를 만들어 보겠습니다. 사용자가 새로 베팅할 때 해당 항목이 없으면 생성한 후 사용자의 베팅 목록에 추가합니다.

각 이벤트에 태그도 추가하겠습니다. 그러면 '스포츠', ‘정치’, '유럽', '미국', '정치' 등의 태그가 지정된 모든 이벤트를 쉽게 찾을 수 있습니다. 이를 위한 별도의 인덱스도 만들겠습니다.

이제 이벤트 태그 계정을 확인해 원하는 모든 이벤트를 쉽게 가져올 수 있습니다.

5. 쓰기 경합 줄이기

각 이벤트에는 해당 이벤트의 베팅을 저장하는 단일 토큰 계정이 있다는 점을 기억하세요. 사용자가 새로 베팅할 때마다 토큰이 이 계정으로 이동합니다. 즉, 계정에 쓰기 작업이 발생합니다.

Solana는 작업을 병렬화하기 때문에 빠릅니다. 하지만 단일 계정의 잔액은 병렬로 업데이트할 수 없습니다. 모든 계정은 특정 시점에 하나의 잔액만 가져야 하므로 순차적으로 업데이트해야 합니다. 

새 이벤트가 발표되어 많은 베팅이 한꺼번에 들어오면 이벤트의 토큰 계정에 수많은 쓰기 작업이 동시에 발생합니다. 그 결과 프로그램의 트랜잭션이 느려 보일 수 있습니다. 이를 쓰기 경합이라고 합니다. 여러 트랜잭션이 계정에 접근하기 위해 경쟁하는 상태입니다.

이러한 상황에서 병렬 처리를 가능하게 하는 한 가지 방법은 샤딩입니다. 하나의 리소스를 병렬로 접근할 수 있는 여러 조각, 즉 샤드로 나눕니다.

샤딩의 작동 방식

들어오는 결제는 shard_num() 매크로를 사용해 베팅자 공개 키의 마지막 바이트 값에 따라 별도의 win_pool 샤드로 전송됩니다. 이를 통해 결제를 빠르게 처리할 수 있습니다.

이후 관리자가 실행하는 인스트럭션 핸들러가 이를 하나의 당첨 풀 계정으로 통합할 수 있습니다. 이렇게 하면 당첨금을 지급하는 데 필요한 유동성을 동일한 계정에 확보할 수 있습니다.

인기 이벤트가 종료되고 모든 당첨자가 동시에 당첨금을 청구할 때 발생하는 쓰기 경합도 고려해야 합니다. 당첨 풀에서 자금을 최대한 빠르게 이동해야 합니다.

가장 좋은 방법은 청구 절차 자체를 없애는 것입니다. 이벤트가 종료되는 즉시 자금을 순차적으로 보내면 사용자가 느린 청구 절차를 겪지 않아도 됩니다. 당첨금이 이미 사용자 계정에 입금되기 때문입니다.

하지만 수많은 사용자의 베팅이나 당첨금 청구를 걱정하려면 먼저 실제 사용자가 있어야 합니다. Solana 프로그램을 계획 중이지만 아직 출시하지 않았다면, 존재하지 않는 대규모 사용자 트래픽을 처리하기 위한 최적화는 필요하지 않습니다. 출시 단계에서 이러한 대규모 최적화가 필요하지 않다면 추가되는 복잡성을 고려해야 합니다. 다만 프로그램이 성장하면 나중에 구현해야 할 수도 있다는 점은 염두에 두세요.

결론

이 글에서는 Solana에서 실제 애플리케이션을 만드는 방법을 자세히 살펴봤습니다. 이제 Solana 계정을 정의하고, 데이터 관계를 설정하고, 토큰을 저장하며, 인덱스와 샤딩으로 성능을 최적화하는 실용적인 방법을 알게 되었습니다. 

추가 질문이 있다면 언제든지 X의 @helius로 문의하거나 Helius Discord에 참여하세요. 앞으로 이 예측 시장 예시를 더 자세히 다룰 예정이므로 해당 계정을 팔로우하고 업데이트를 확인하세요.

이제 새롭게 익힌 기술로 아이디어를 Solana 프로그램으로 구현하고 Solana 생태계에서 현실로 만들 차례입니다. 즐겁게 코딩하세요! 이 글을 검토해 주신 Ichigo와 계정 샤딩 기법을 알려주신 r0bre에게 감사드립니다.

추가 자료

Helius 구독하기

최신 Solana 개발 소식을 확인하고 새 게시물 알림을 받아보세요

확대 이미지