
Solana 프로그래밍 모델: Solana 개발 입문
이 글에서는 무엇을 다루나요?
Solana의 탈중앙화 컴퓨팅 방식은 간단한 원칙에 기반합니다. 모든 요소는 계정이라는 자체 메모리 영역에 저장됩니다. Solana는 공개 키가 해당 계정의 고유 식별자 역할을 하는 전역 키/값 저장소로 작동합니다. 계정은 상태를 저장하므로 Solana의 핵심 기반입니다. 프로그램부터 토큰 잔액까지 모든 것을 보관합니다. 트랜잭션은 계정을 업데이트하고 상태 변경을 반영하는 데 사용됩니다.
이 글에서는 Solana 아키텍처의 복잡성을 살펴봅니다. 먼저 클러스터와 상태 개념을 개괄한 후, Solana의 기본 구성 요소인 계정과 프로그램의 역할을 설명합니다. 이어서 트랜잭션이 계정과 프로그램 사이의 동적 상호작용을 지원하는 방식을 알아봅니다.
이 글을 마치면 Solana의 프로그래밍 모델을 깊이 이해할 수 있습니다. 클러스터 아키텍처, 데이터 저장에서 계정이 담당하는 핵심 역할, 트랜잭션이 계정 데이터를 업데이트하는 과정을 익히게 됩니다. 또한 임대료 시스템과 버전 관리 트랜잭션처럼 Solana만의 고유한 기능도 살펴봅니다.
Solana 클러스터란 무엇인가요?
Solana 아키텍처의 중심에는 클러스터가 있습니다. 클러스터는 트랜잭션을 처리하고 단일 원장을 유지하기 위해 함께 작동하는 검증인 집합입니다. Solana에는 각각 특정 목적을 담당하는 여러 독립 클러스터가 있습니다.
- Localhost: 기본 포트 8899에서 실행되는 로컬 개발 클러스터입니다. Solana 명령줄 인터페이스(CLI)에는 에어드롭이나 속도 제한 없이 개발자 개인의 요구에 맞게 설정할 수 있는 테스트 검증인이 내장되어 있습니다
- Devnet: Solana에서 부담 없이 테스트하고 실험할 수 있는 샌드박스 환경입니다
- Testnet: Solana 핵심 기여자들이 새로운 업데이트와 기능을 메인넷에 적용하기 전에 시험하는 환경입니다. 성능 테스트를 실행하려는 개발자의 테스트 환경으로도 사용됩니다
- Mainnet Beta: 실제 트랜잭션이 발생하는 라이브 무허가형 클러스터입니다. 사용자, 개발자, 토큰 보유자, 검증인이 매일 상호작용하는 “실제” Solana입니다
각 클러스터는 서로를 전혀 인식하지 못한 채 독립적으로 작동합니다. 잘못된 클러스터로 전송된 트랜잭션은 각 운영 환경의 무결성을 보장하기 위해 거부됩니다.
클러스터를 하나의 거대한 데이터 힙이라고 생각해 보세요. 컴퓨터 과학에서 힙은 데이터를 동적으로 저장하고 수정할 수 있는 메모리 영역을 의미합니다. 다만 클러스터가 실제로 힙 자료 구조를 사용하는 것은 아닙니다. 이 비유는 클러스터가 필요할 때 할당하고 해제할 수 있는 여러 메모리 영역으로 구성된다는 점을 이해하기 위한 개념적 도구입니다. 클러스터를 동적 힙으로 이해하면 네트워크에서 데이터를 관리하고 접근하며 보호하는 방식을 파악할 수 있습니다.
이 거대한 데이터 힙을 일종의 디지털 창고로 볼 수도 있습니다. 데이터는 선반 위의 상자와 같으며, 각 상자에는 고유한 라벨과 이동 및 내용 변경에 관한 구체적인 규칙이 있습니다. 따라서 승인된 이동이나 변경만 허용되는 안전하고 체계적인 시스템이 유지됩니다.
Solana에서 프로그램이라 부르는 스마트 계약에는 직접 관리할 수 있는 창고 또는 힙의 일부가 할당됩니다. 프로그램은 창고의 어느 공간에서든 데이터를 읽을 수 있지만, 소유하지 않은 공간의 내용을 변경하려면 특정 권한이 필요합니다. 보편적으로 허용되는 유일한 작업은 Solana의 네이티브 암호화폐인 lamport를 창고 내 어느 공간으로든 전송하는 것입니다.
프로그램을 포함한 모든 상태가 이 힙에 존재합니다. 각 영역에는 이를 소유하고 관리하는 프로그램이 있습니다. 예를 들어 프로그램은 온체인 프로그램의 로드, 배포, 업그레이드를 담당하는 프로그램인 BPFLoader가 소유합니다. 이러한 메모리 영역, 즉 디지털 창고의 상자를 계정이라고 부릅니다.
계정이란 무엇인가요?
Solana에서는 모든 것이 계정입니다. 계정을 컴퓨터의 파일처럼 데이터를 영구적으로 보관하는 컨테이너라고 생각해 보세요. 계정은 상태, 즉 계정 잔액, 소유권 정보, 프로그램 보유 여부, 임대료 정보를 저장하는 Solana 프로그램 모델의 기본 구성 요소입니다.
Solana에는 세 가지 유형의 계정이 있습니다.
- 데이터를 저장하는 계정
- 실행 가능한 프로그램을 저장하는 계정
- 네이티브 프로그램을 저장하는 계정
이러한 계정 유형은 기능에 따라 다음과 같이 더 세분화할 수 있습니다.
- 실행 가능 계정 - 코드를 실행할 수 있는 계정입니다
- 실행 불가능 계정 - 코드를 실행하지 못하며 데이터 저장에 사용되는 계정입니다. 코드를 보유하지 않기 때문입니다
위 그림에는 실행 가능 계정과 실행 불가능 계정의 몇 가지 예가 나와 있습니다. 실행 가능 계정 중 Bubblegum은 프로그램 계정의 예입니다. 압축 NFT를 생성하고 관리하는 데 사용되는 Metaplex 프로그램입니다. Vote Program은 네이티브 프로그램 계정의 예입니다. 검증인의 투표 상태와 보상을 추적하는 계정을 생성하고 관리하는 데 사용됩니다. 프로그램 계정과 네이티브 프로그램 계정의 차이는 프로그램이란 무엇인가요? 섹션에서 다룹니다. 지금은 Solana에 여러 유형의 실행 가능 계정이 있다는 점만 알아두면 됩니다.
또한 모든 실행 불가능 계정은 데이터 계정으로 분류할 수 있습니다. 데이터 계정의 예는 다음과 같습니다.
- 연결 토큰 계정 - 특정 토큰, 잔액, 소유자에 관한 정보를 보관하는 계정입니다. 예를 들어 Alice가 10 USDC를 보유한다는 정보를 저장합니다
- 시스템 계정 - System Program이 생성하고 소유하는 계정입니다
- 스테이크 계정 - 잠재적 보상을 얻기 위해 검증인에게 토큰을 위임하는 데 사용되는 계정입니다
계정 구조
계정은 AccountInfo 구조체에 따라 구성됩니다.
pub struct AccountInfo<'a> {
pub key: &'a Pubkey,
pub lamports: Rc>,
pub data: Rc>,
pub owner: &'a Pubkey,
pub rent_epoch: Epoch,
pub is_signer: bool,
pub is_writable: bool,
pub executable: bool,
}계정은 고유한 32바이트 공개 키인 주소(key)로 식별됩니다.
lamports 필드에는 이 계정이 보유한 lamport 수가 저장됩니다. 1 lamport는 Solana의 네이티브 토큰인 SOL의 10억분의 1입니다.
data는 이 계정이 저장하는 원시 데이터 바이트 배열을 의미합니다. 디지털 자산의 메타데이터부터 토큰 잔액까지 무엇이든 저장할 수 있으며 프로그램으로 수정할 수 있습니다.
owner 필드에는 프로그램 계정 주소로 표현된 이 계정의 소유자가 저장됩니다. 계정 소유권에는 몇 가지 규칙이 있습니다.
- 계정 소유자만 계정 데이터를 변경하고 lamport를 인출할 수 있습니다
- 누구나 계정에 lamport를 예치할 수 있습니다
- 계정 데이터가 0으로 초기화된 경우 계정 소유자는 새 소유자에게 소유권을 이전할 수 있습니다
is_signer 필드는 해당 계정의 소유자가 트랜잭션에 서명했는지를 나타내는 불리언 값입니다. 즉, 트랜잭션에 관여하는 프로그램에 이 계정이 서명자인지 알려줍니다. 서명자라는 것은 계정이 공개 키에 대응하는 비공개 키를 보유하고 있으며 제안된 트랜잭션을 승인할 권한이 있음을 의미합니다.
is_writable 필드는 계정 데이터를 수정할 수 있는지를 나타내는 불리언 값입니다. Solana에서는 병렬 처리를 지원하기 위해 트랜잭션이 계정을 읽기 전용으로 지정할 수 있습니다. 런타임은 여러 프로그램이 읽기 전용 계정에 동시에 접근하도록 허용하는 한편, 트랜잭션 처리 순서를 이용해 쓰기 가능 계정에서 발생할 수 있는 쓰기 충돌을 처리합니다. 따라서 서로 충돌하지 않는 트랜잭션만 병렬로 처리됩니다.
executable 필드는 계정이 명령을 처리할 수 있는지를 나타내는 불리언 값입니다. 즉, 프로그램도 계정에 저장됩니다. 다음 섹션에서 자세히 살펴보겠습니다. 그 전에 임대료 개념을 알아야 합니다.
rent_epoch 필드는 이 계정에 다음 임대료가 부과될 에포크를 나타냅니다. 에포크는 리더 일정이 유효한 슬롯의 수입니다. 운영 체제의 일반적인 파일과 달리 Solana 계정의 수명은 lamport 수량으로 표현됩니다. 계정의 지속적인 존재가 lamport 잔액에 달려 있다는 개념이 바로 임대료로 이어집니다.
임대료
임대료는 Solana에서 계정을 유지하고 검증인 메모리에 보관하기 위해 발생하는 저장 비용입니다. 임대료 징수는 리더 일정이 유효한 슬롯 단위의 시간인 에포크를 기준으로 평가됩니다. 임대료는 다음과 같이 작동합니다.
- 임대료 징수 - 임대료는 에포크마다 한 번 징수됩니다. 트랜잭션이 계정을 참조할 때도 징수될 수 있습니다
- 임대료 분배 - 징수된 임대료 일부는 소각되어 유통량에서 영구적으로 제거됩니다. 나머지는 매 슬롯이 끝난 후 투표 계정에 분배됩니다
- 임대료 납부 - 계정에 임대료를 낼 충분한 lamport가 없으면 가비지 컬렉션이라는 과정에서 데이터가 제거되고 계정 할당이 해제됩니다
- 임대료 면제 - 계정이 2년 치 임대료에 해당하는 최소 잔액을 유지하면 임대료를 면제받을 수 있습니다. 모든 신규 계정은 계정 크기에 따라 달라지는 이 임대료 면제 기준을 충족해야 합니다
- 임대료 회수 - 사용자는 계정을 닫아 남은 lamport를 회수할 수 있습니다. 이를 통해 계정에 보관된 임대료를 되찾을 수 있습니다
특정 계정 크기의 임대료는 getMinimumBalanceForRentExemption RPC 엔드포인트로 추정할 수 있습니다. Helius API Playground는 계정 데이터 길이를 usize로 입력받아 이 과정을 간소화합니다. Solana rent CLI 하위 명령을 사용해 계정이 임대료 면제를 받는 데 필요한 최소 SOL도 추정할 수 있습니다. 예를 들어 이 글을 작성하는 시점에 solana rent 20000 명령을 실행하면 Rent-exempt minimum: 0.14009088 SOL이 반환됩니다.
Solana의 주소
Solana에는 실제로 두 가지 “유형”의 주소가 있습니다. Solana는 주소 생성에 SHA-512(SHA-2)와 Curve22519 타원 곡선을 사용하는 EdDSA 서명 체계인 ed25519를 사용합니다. 그 결과 기본 주소 형식으로 사용되는 32바이트 공개 키가 생성됩니다. 해시 처리되지 않으므로 그대로 사용할 수 있습니다.
주소가 유효하려면 ed25519 곡선 위의 점이어야 합니다. 하지만 모든 주소가 이 곡선에서 파생될 필요는 없습니다. 프로그램 파생 주소(PDA)는 곡선 밖에서 생성되므로 대응하는 비공개 키가 없으며 서명에 사용할 수 없습니다. PDA는 System Program을 통해 생성되며 프로그램이 계정을 관리해야 할 때 사용됩니다. 이는 독자에게 Solana의 다양한 주소 유형을 알려드리기 위한 간단한 설명입니다. PDA는 향후 글에서 다루겠습니다.
Solana 계정은 Ethereum 계정과 어떻게 다른가요?
Ethereum에는 외부 소유 계정(EOA)과 계약 계정이라는 두 가지 주요 계정 유형이 있습니다. EOA는 비공개 키로 제어되지만, 계약 계정은 계약 코드의 지배를 받으며 자체적으로 트랜잭션을 시작할 수 없습니다.
EOA와 계약 계정은 모두 같은 계정 구조를 따릅니다.
- 잔액 - 모든 계정에는 Ether 단위로 측정되는 잔액이 있습니다
- Nonce - EOA에서는 계정이 전송한 트랜잭션 수입니다. 계약에서는 계정이 생성한 계약 수입니다
- 스토리지 루트 - 계정의 스토리지 내용을 인코딩한 Merkle Patricia Trie 루트 노드의 256비트 해시입니다
- CodeHash - 계약의 Ethereum Virtual Machine(EVM) 코드 해시입니다. 이는 변경 불가능하므로 생성 후에는 상태가 바뀌더라도 코드가 변경되지 않습니다. 프록시 패턴 사용처럼 Ethereum 계약을 업그레이드하는 예외가 있지만 이 글의 범위를 벗어납니다. EOA에는 코드가 없으므로 빈 문자열의 해시가 저장됩니다
Solana는 모든 계정이 프로그램이 될 수 있는 더 일관된 계정 모델을 채택합니다. 코드와 데이터를 분리해 더 효율적이고 유연한 환경을 제공합니다. Solana 프로그램은 무상태이며 중복 배포 없이 다양한 데이터 계정과 상호작용합니다. 이는 사용자가 여러 프로그램 사이에서 자산을 이동하지 않고 여러 프로토콜과 상호작용하려는 탈중앙화 금융(DeFi) 애플리케이션에 특히 유리합니다. 반면 Ethereum의 프로그래밍 모델은 코드와 상태를 하나의 엔터티로 결합합니다. 따라서 상호작용이 더 복잡하고 상태 변경에 필요한 가스로 인해 비용이 증가할 수 있습니다.
과거 Solana 계정은 임대료를 납부했으며 활성 상태를 유지하려면 최소 잔액을 보유해야 했습니다. 이를 통해 사용되지 않거나 자금이 부족한 계정은 결국 네트워크에서 회수되어 상태 팽창을 줄였습니다. 최근 업데이트 이후 메인넷에는 더 이상 임대료를 내는 계정이 없으며 모든 계정이 임대료 면제 조건을 충족해야 합니다. 반면 Ethereum은 가스로 리소스 할당을 관리합니다. 이 모델에서는 명시적으로 삭제하지 않는 한 계약 스토리지가 무기한 유지됩니다. Solana의 방식은 상태 저장 비용을 더 예측하기 쉽게 만들지만, Ethereum의 비용은 변동하며 네트워크 혼잡 시 감당하기 어려운 수준까지 오를 수 있습니다.
다음 섹션에서는 Solana가 프로그램 로직과 상태를 분리하는 방식을 살펴봅니다. Ethereum의 프로그래밍 모델과 비교하면서 이 모듈식 방식이 개발자에게 투명하고 예측 가능한 비용 구조를 제공하는 동시에 더 효율적인 온체인 작업을 지원하는 방법을 확인할 수 있습니다.
Solana 프로그램이란 무엇인가요?
프로그램은 BPF Loader가 소유하는 실행 가능 계정입니다. 트랜잭션과 프로그램 로직을 처리하도록 설계된 Solana Runtime이 이를 실행합니다.
Solana 프로그래밍 모델의 고유한 특징 중 하나는 코드와 데이터가 분리된다는 점입니다. 프로그램은 무상태이므로 내부에 상태를 저장하지 않습니다. 대신 작업에 필요한 모든 데이터는 별도의 계정에 저장되고, 트랜잭션을 통해 참조 형태로 프로그램에 전달됩니다. 이 설계 덕분에 범용 프로그램을 한 번만 배포해 여러 계정과 상호작용할 수 있습니다.
Solana 프로그램은 다음 작업을 수행할 수 있습니다.
- 추가 계정 소유
- 다른 계정 읽기 또는 입금
- 소유한 계정의 데이터 수정 또는 출금
프로그램에는 두 가지 유형이 있습니다.
- 온체인 프로그램 - 사용자가 작성해 Solana에 배포한 프로그램입니다. 일반적으로 프로그램을 배포한 계정인 업그레이드 권한자가 업그레이드할 수 있습니다
- 네이티브 프로그램 - Solana 코어에 통합된 프로그램입니다. 검증인 운영에 필요한 기본 기능을 제공합니다. 네이티브 프로그램은 네트워크 전체의 소프트웨어 업데이트를 통해서만 업그레이드할 수 있습니다. 대표적인 예로 System Program, BPF Loader Program, Vote Program이 있습니다.
온체인 프로그램과 네이티브 프로그램은 모두 사용자와 다른 프로그램이 호출할 수 있습니다. 가장 큰 차이는 업그레이드 방식입니다. 온체인 프로그램은 업그레이드 권한자가 업그레이드할 수 있지만, 네이티브 프로그램은 클러스터 업데이트의 일부로만 업그레이드할 수 있습니다.
Solana Labs는 Solana Program Library라는 엄선된 온체인 프로그램 모음을 관리합니다. 이 라이브러리는 토큰 대출과 스테이크 풀 생성을 포함한 다양한 온체인 작업을 지원합니다. 예를 들어 Associated Token Account Program은 사용자의 지갑을 해당 토큰 계정에 연결하는 표준과 메커니즘을 정의합니다. 또한 SPL은 계속 발전합니다. Token-2022 같은 프로그램은 Token Program이 제공하는 기능을 기반으로 이를 확장합니다.
Solana 프로그램은 일반적으로 상용구를 줄이고 직렬화와 역직렬화를 간소화하는 독자적인 프레임워크인 Anchor의 도움을 받아 Rust로 개발합니다. Rust가 선호되지만 반드시 사용해야 하는 것은 아닙니다. C, C++, 그리고 LLVM의 BPF 백엔드, 즉 프로그램을 BPF 바이트코드로 컴파일할 수 있는 LLVM 구성 요소를 대상으로 하는 모든 언어를 사용할 수 있습니다. 최근 Solang과 Neon Labs의 발전으로 프로그램 개발에 Solidity도 사용할 수 있게 되었습니다.
프로그램은 일반적으로 Localhost와 Devnet에서 개발 및 테스트한 후 Testnet이나 Mainnet Beta에 배포합니다. 개발자는 Solana CLI에서 solana program deploy <path to program> 명령으로 프로그램을 배포할 수 있습니다. 프로그램은 BPF 바이트코드를 포함한 ELF 공유 객체로 컴파일된 후 지정된 Solana 클러스터에 업로드됩니다. 배포된 프로그램은 executable로 표시된 계정에 존재하며, 계정 주소가 program_id 역할을 합니다.
초기에는 Solana 프로그램이 프로그램 크기의 두 배인 계정에 배포되었습니다. Solana 1.16 업데이트는 크기 조정 가능 계정 지원을 도입하여 개발자에게 더 유연한 리소스 할당을 제공합니다. 이제 개발자는 더 작은 계정에 프로그램을 배포하고 나중에 크기를 확장할 수 있습니다.
앞서 설명했듯이 프로그램이 상호작용하는 모든 데이터는 참조로 전달되는 별도 계정에 저장되므로 프로그램은 무상태로 간주됩니다. 모든 프로그램에는 명령 처리가 이루어지는 단일 진입점이 있습니다. 이 진입점은 program_id, 계정 배열, 바이트 배열 형태의 명령 데이터를 받습니다. 트랜잭션이 프로그램을 호출하면 Solana Runtime이 이를 실행합니다.
트랜잭션이란 무엇인가요?
트랜잭션은 온체인 활동의 핵심 기반입니다. 프로그램을 호출하고 상태 변경을 실행하는 메커니즘입니다. Solana의 트랜잭션은 검증인에게 어떤 계정에서 어떤 작업을 수행해야 하는지, 그리고 그 작업에 필요한 권한이 있는지를 알려주는 명령 묶음입니다.
트랜잭션은 크게 세 부분으로 구성됩니다.
- 읽거나 쓸 계정 배열
- 하나 이상의 명령
- 하나 이상의 서명
Solana 트랜잭션은 Transaction 구조체를 따릅니다. 이 구조체는 네트워크가 작업을 처리하고 검증하는 데 필요한 정보를 제공합니다. 정의는 다음과 같습니다.
pub struct Transaction {
pub signatures: Vec,
pub message: Message,
}signatures 필드에는 직렬화된 Message에 대응하는 서명 집합이 포함됩니다. 각 서명은 수수료 납부자를 시작으로 Message의 account_keys 목록에 있는 계정 키와 연결됩니다. 수수료 납부자는 트랜잭션 처리 시 발생하는 수수료를 부담하는 계정입니다. 일반적으로 트랜잭션을 시작한 계정입니다. 필요한 서명 수는 메시지의 MessageHeader에 정의된 num_required_signatures와 같습니다.
message 자체는 Message 유형의 구조체입니다. 다음과 같이 정의됩니다.
pub struct Message {
pub header: MessageHeader,
pub account_keys: Vec,
pub recent_blockhash: Hash,
pub instructions: Vec,
}메시지의 header에는 부호 없는 8비트 정수 세 개가 포함됩니다. 필요한 서명 수, 즉 num_required_signatures, 읽기 전용 서명자 수, 읽기 전용 비서명자 수입니다.
account_keys 필드에는 트랜잭션에 관여하는 모든 계정 주소가 나열됩니다. 읽기-쓰기 접근을 요청하는 계정이 먼저 나오고 읽기 전용 계정이 뒤따릅니다.
recent_blockhash는 32바이트 SHA-256 해시를 포함한 최근 블록 해시입니다. 클라이언트가 원장을 마지막으로 확인한 시점을 나타내며 최근 트랜잭션의 유효 기간 역할을 합니다. 검증인은 오래된 블록 해시가 포함된 트랜잭션을 거부합니다. 또한 최근 블록 해시를 포함하면 이전 트랜잭션과 완전히 동일한 트랜잭션이 거부되므로 중복 트랜잭션을 방지할 수 있습니다. 어떤 이유로든 네트워크에 제출하기 훨씬 전에 트랜잭션에 서명해야 한다면 최근 블록 해시 대신 영구 트랜잭션 nonce를 사용해 고유한 트랜잭션임을 보장할 수 있습니다.
instructions 필드에는 하나 이상의 CompiledInstruction 구조체가 포함되며, 각 구조체는 네트워크 검증인이 수행할 특정 작업을 지정합니다.
명령
명령은 Solana 프로그램을 한 번 호출하기 위한 지시입니다. 프로그램 실행 로직의 최소 단위이자 Solana에서 가장 기본적인 작업 단위입니다. 프로그램은 명령에서 전달된 데이터를 해석하고 지정된 계정에서 작업합니다. Instruction 구조체는 다음과 같이 정의됩니다.
pub struct Instruction {
pub program_id: Pubkey,
pub accounts: Vec,
pub data: Vec,
}program_id 필드는 실행할 프로그램의 공개 키를 지정합니다. 이는 명령을 처리할 프로그램의 주소입니다. 이 공개 키가 가리키는 프로그램 계정의 소유자는 프로그램 초기화와 실행을 담당하는 로더를 지정합니다. 로더는 배포된 온체인 Solana Bytecode Format(SBF) 프로그램을 실행 가능한 상태로 표시합니다. Solana 런타임은 실행 가능으로 표시되지 않은 계정을 호출하려는 모든 트랜잭션을 거부합니다.
accounts 필드에는 명령이 읽거나 쓸 수 있는 계정이 나열됩니다. 이러한 계정은 AccountMeta 값으로 제공해야 합니다. 명령이 데이터를 변경할 수 있는 모든 계정은 쓰기 가능으로 지정해야 하며, 그렇지 않으면 트랜잭션이 실패합니다. 프로그램은 자신이 소유하지 않거나 필요한 권한이 없는 계정에 쓸 수 없기 때문입니다. 이는 계정의 lamport를 변경할 때도 적용됩니다. 프로그램이 소유하지 않은 계정에서 lamport를 차감하면 트랜잭션이 실패하지만, 어떤 계정에든 lamport를 추가하는 것은 허용됩니다. accounts 필드는 프로그램이 읽거나 쓰지 않는 계정도 지정할 수 있습니다. 이는 런타임의 프로그램 실행 일정에 영향을 주기 위한 것이며, 그 외에는 해당 계정이 무시됩니다.
data는 프로그램에 전달되는 입력 역할을 하는 범용 8비트 부호 없는 정수 벡터입니다. 프로그램이 실행할 인코딩된 명령을 포함하므로 매우 중요한 필드입니다.
Solana는 명령 데이터 형식에 구애받지 않습니다. 하지만 bincode와 borsh(Binary Object Representation Serializer for Hashing)를 통한 직렬화를 기본 지원합니다. 직렬화는 복잡한 데이터 구조를 전송하거나 저장할 수 있는 일련의 평면 바이트로 변환하는 과정입니다. 모든 작업이 온체인에서 이루어지므로 데이터 인코딩 방식을 선택할 때 디코딩 오버헤드를 고려해야 합니다. Borsh 직렬화는 안정적인 사양과 JavaScript 구현을 갖추고 있으며 일반적으로 더 효율적이므로 bincode보다 선호됩니다.
프로그램은 지원되는 명령을 더 쉽게 구성하기 위해 도우미 함수를 사용합니다. 예를 들어 System Program은 SystemInstruction::Assign 명령을 구성하는 도우미 함수를 제공합니다.
pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
let account_metas = vec![AccountMeta::new(*pubkey, true)];
Instruction::new(
system_program::id(),
&SystemInstruction::Assign { owner: *owner },
account_metas,
)
}이 함수는 처리 시 지정된 계정의 소유자를 제공된 새 소유자로 변경하는 명령을 구성합니다.
하나의 트랜잭션에는 여러 명령이 포함될 수 있으며, 나열된 순서대로 순차적이고 원자적으로 실행됩니다. 즉, 모든 명령이 성공하거나 어떤 명령도 성공하지 않습니다. 따라서 명령 순서가 매우 중요할 수 있습니다. 프로그램은 잠재적 악용을 방지하기 위해 가능한 모든 명령 순서를 안전하게 처리하도록 강화되어야 합니다.
예를 들어 초기화 해제 과정에서 프로그램은 계정의 lamport 잔액을 0으로 설정해 계정 초기화를 해제하려 할 수 있습니다. 이는 Solana 런타임이 계정을 삭제한다고 가정합니다. 이 가정은 트랜잭션 사이에서는 유효하지만 명령 또는 교차 프로그램 호출 사이에서는 유효하지 않습니다. 교차 프로그램 호출은 향후 글에서 다루겠습니다. 프로그램은 초기화 해제 과정의 잠재적 결함을 방지하기 위해 계정 데이터를 명시적으로 0으로 설정해야 합니다. 그렇지 않으면 공격자가 후속 명령을 실행해 트랜잭션 완료 전에 계정을 재사용하는 등 삭제된 것으로 간주된 계정을 악용할 수 있습니다.
버전 관리 트랜잭션이란 무엇인가요?
Solana 트랜잭션은 클러스터 전체에서 데이터를 빠르고 안정적으로 전송하기 위해 IPv6 최대 전송 단위(MTU) 표준을 사용합니다. Solana 네트워킹 스택은 보수적으로 1280바이트의 MTU 크기를 사용합니다. 헤더 공간을 제외하면 패킷 데이터에 1232바이트를 사용할 수 있습니다. 따라서 Solana 트랜잭션 크기는 이 값으로 제한됩니다.
이 크기 제약은 다양한 네트워크 개선을 지원하지만, 단일 트랜잭션에서 수행할 수 있는 작업의 복잡성도 제한합니다. 각 계정 주소가 32바이트의 저장 공간을 차지하므로 명령이 없는 트랜잭션에는 최대 35개 계정을 저장할 수 있습니다. 따라서 단일 트랜잭션에 서명이 필요 없는 계정을 35개 넘게 포함해야 하는 사용 사례에는 어려움이 생깁니다.
이 문제를 해결하기 위해 여러 트랜잭션 형식 버전을 지원하는 새로운 트랜잭션 형식이 도입되었습니다. 현재 Solana 런타임은 두 가지 트랜잭션 버전을 지원합니다.
legacy- 최초의 트랜잭션 형식입니다0(Version 0) - Address Lookup Tables 지원이 포함된 최신 트랜잭션 형식입니다
Version 0은 Address Lookup Tables(ALT)를 지원하기 위해 출시되었습니다. ALT는 기본적으로 계정 주소를 온체인의 테이블형 데이터 구조에 저장합니다. 이 테이블은 계정 주소를 저장하는 별도 계정이며, 트랜잭션에서 1바이트 u8 인덱스로 주소를 참조할 수 있게 합니다. 포함된 각 계정에 32바이트 대신 1바이트만 사용하면 되므로 트랜잭션 크기가 크게 줄어듭니다. ALT는 DeFi 애플리케이션에서 흔히 볼 수 있는 다수의 계정이 관여하는 복잡한 작업에 특히 유용합니다.
이 다이어그램은 Solana Cookbook의 버전 관리 트랜잭션 섹션을 바탕으로 제작했습니다
“버전 관리 트랜잭션”이라는 용어는 Solana가 레거시 트랜잭션 형식과 Version 0 형식을 모두 지원하는 방식을 뜻합니다. 이 방식은 런타임 개선을 수용하면서도 결합성을 보장합니다.
버전 관리 트랜잭션의 구조
VersionedTransaction는 다음과 같이 정의됩니다.
pub struct VersionedTransaction {
pub signatures: Vec,
pub message: VersionedMessage,
}signatures 필드는 트랜잭션 서명자의 서명 목록입니다. 이 서명은 트랜잭션을 인증하고 무결성을 유지합니다. message는 트랜잭션의 실제 내용입니다. 레거시 메시지와 Version 0 메시지를 모두 처리하는 간단한 열거형 래퍼인 VersionedMessage 유형으로 캡슐화됩니다.
pub enum VersionedMessage {
Legacy(Message),
V0(Message),
}메시지 버전은 직렬화 과정의 첫 번째 비트로 결정됩니다. 첫 번째 비트가 설정되어 있으면 나머지 7비트로 Version 0부터 직렬화된 Message 버전을 판별합니다. 첫 번째 비트가 설정되어 있지 않으면 모든 바이트가 레거시 Message 형식을 인코딩하는 데 사용됩니다. 이름이 같은 Message 구조체가 두 개 있지만 서로 다른 모듈인 legacy와 v0로 분리되어 있기 때문입니다.
Message는 트랜잭션의 압축된 내부 형식을 나타냅니다. 네트워크 전송과 런타임 조작에 사용됩니다. 트랜잭션 명령에 사용되는 모든 계정의 선형 목록, 계정 배열 구조를 설명하는 MessageHeader, 최근 블록 해시, 메시지 명령의 압축 인코딩을 포함합니다. 다음은 v0 Message 구조체의 구조입니다.
pub struct Message {
pub header: MessageHeader,
pub account_keys: Vec,
pub recent_blockhash: Hash,
pub instructions: Vec,
pub address_table_lookups: Vec,
}레거시 메시지와 v0 메시지의 차이는 address_table_lookups 필드의 포함 여부입니다.
프로그래밍 모델과 Solana 트랜잭션 흐름의 통합
Solana의 프로그래밍 모델은 계정 및 트랜잭션 시스템과 긴밀하게 통합되어 있습니다. 각 개념은 다음과 같이 연결됩니다.
- 상태로서의 계정 - Solana 계정은 프로그램의 상태 컨테이너 역할을 합니다. 프로그래밍 모델은 명령에 대응해 이 컨테이너에 저장된 데이터를 수정하는 것을 중심으로 합니다
- 명령 - 프로그램은 트랜잭션에 포함된 명령을 처리하는 로직을 정의합니다. 이러한 명령은 계정 데이터와 상호작용하는 실행 가능한 구성 요소입니다
- 직렬화와 처리 - 트랜잭션이 직렬화되면 프로그램의 명령이 계정 상태의 변경 사항을 결정합니다. 직렬화 과정은 레거시 형식이나 Version 0 트랜잭션 형식 중 무엇을 사용하든 프로그램의 설계를 따릅니다
- 원자성 - Solana의 프로그래밍 모델은 명령의 원자적 처리를 보장합니다. 프로그램은 동시 트랜잭션을 안전하고 효율적으로 처리하도록 설계해야 합니다
- 확장성 - Solana의 프로그래밍 모델은 Address Lookup Tables(ALT) 같은 기능을 통해 확장성을 지원합니다. 이 테이블은 트랜잭션 크기를 줄이고 트랜잭션이 참조할 수 있는 계정 수를 늘립니다
Solana의 프로그래밍 모델은 단순히 코드를 작성하는 데 그치지 않습니다. 해당 코드가 더 넓은 생태계에서 어떻게 상호작용하는지 이해해야 합니다. 계정은 네트워크에서 데이터를 저장하고 수정하는 기본 수단으로서 이 모델의 핵심입니다. 트랜잭션은 검증인에게 생성, 업데이트, 삭제해야 할 데이터를 알려 온체인 활동을 지원합니다. 개발자가 Solana 생태계에서 성능과 시너지를 극대화한 애플리케이션을 구축하려면 이러한 요소를 깊이 이해해야 합니다.
마치며
축하합니다! 이 글에서는 Solana 시스템 아키텍처의 복잡성을 살펴보고 클러스터를 거대한 데이터 힙으로 이해하는 개념을 알아봤습니다. 이 힙이 계정이라는 개별 메모리 영역으로 구성되며 Solana 프로그래밍 모델의 핵심 기반을 이룬다는 점도 확인했습니다. 계정은 사용자 토큰부터 네트워크 동작을 정의하는 프로그램까지 모든 것을 저장하며, 이 모든 요소는 트랜잭션을 통해 수정됩니다.
개발자에게 Solana의 탈중앙화 컴퓨팅 방식을 이해하는 것은 매우 중요합니다. Solana의 기능을 최대한 활용하는 애플리케이션을 구축하려면 계정, 프로그램, 트랜잭션의 세부 구조를 파악해야 합니다. 핵심은 코드와 상태가 분리된 시스템을 이해하는 것입니다. 그 결과 무상태 프로그램이 계정을 통해 데이터와 상호작용하며 전례 없는 수준의 결합성과 업그레이드 가능성을 제공합니다.
투자자와 일반 사용자 모두에게 Solana의 설계가 견고하고 유연하며 효율적인 생태계를 만드는 방식을 이해하는 것은 중요합니다. 그래야 플랫폼의 실현 가능성과 Solana에서만 가능한 혁신적인 애플리케이션을 육성할 역량을 제대로 평가할 수 있습니다.
익명의 독자 여러분, 여기까지 읽어주셔서 감사합니다! 더 깊이 알아볼 준비가 되셨나요? 지금 Discord에 참여해 Solana 프로그래밍을 시작하세요.
추가 자료 / 더 읽어보기
관련 아티클
Helius 구독하기
최신 Solana 개발 소식을 확인하고 새 게시물 알림을 받아보세요


