요약
Solana에는 세 가지 트랜잭션 형식이 있습니다: 레거시, v0, v1. v0는 1바이트 인덱스로 계정을 참조하는 주소 조회 테이블(ALT)을 추가합니다. v1은 크기 제한을 4,096바이트로 높이고, 리소스 한도를 메시지 자체로 이동하며, ALT를 제거합니다.
Solana는 레거시, v0, v1의 세 가지 트랜잭션 형식을 지원합니다. 각 형식은 동일한 세 가지 측면으로 설명됩니다: 와이어에서의 바이트 배치 방식, 계정 참조 방식, 리소스 한도의 출처.
형식 비교
| 한도 | 레거시 | v0 | v1 |
|---|---|---|---|
| 최대 트랜잭션 크기 | 1,232바이트 | 1,232바이트 | 4,096바이트 |
| 계정 주소 | ~32개, 크기 제한 | 64개, 조회 테이블 경유 | 64개, 인라인 |
| 주소 조회 테이블 | 미지원 | 지원 | 미지원 |
| 리소스 한도 | ComputeBudget 명령어 | ComputeBudget 명령어 | 메시지 설정 |
레거시 형식
원래의 형식으로, 대부분의 툴링에서 여전히 기본값입니다. 버전 접두사가 전혀 없으며, 트랜잭션의 첫 번째 바이트는 서명 배열의 compact-u16 개수이고, 메시지의 첫 번째 바이트는 num_required_signatures로 상위 비트가 항상 설정되지 않습니다.
레거시 와이어 레이아웃
| 필드 | 크기 | 설명 |
|---|---|---|
num_signatures | compact-u16 | 서명 수 |
signatures | num_signatures x 64바이트 | Ed25519 서명 |
header | 3바이트 | MessageHeader — 첫 번째 바이트의 버전 비트가 설정되지 않음 |
num_account_keys | compact-u16 | 계정 키 수 |
account_keys | num_account_keys x 32바이트 | 공개 키, 모두 인라인 |
recent_blockhash | 32바이트 | 수명 지정자 |
num_instructions | compact-u16 | 명령어 수 |
instructions | 가변 | 각 명령어가 연속으로 직렬화됨 |
모든 가변 길이 배열은 compact-u16 길이로 접두사됩니다: 0–127 값은 1바이트, 더 큰 값은 2–3바이트. 명령어별 레이아웃과 크기 계산 예시는 트랜잭션 이진 형식을 참조하세요.
레거시의 계정
모든 계정은 account_keys에 32바이트 공개 키 전체로 기록되며, 명령어는 해당 배열의 1바이트 인덱스로 이를 참조합니다. 트랜잭션에 명시되지 않은 계정을 참조할 방법이 없으므로, 레거시 트랜잭션은 1,232바이트 한도에 도달하기 전에 약 32개의 계정으로 제한됩니다.
레거시의 리소스 한도
컴퓨트 유닛 한도, 로드된 계정 데이터 크기 한도, 힙 크기, 우선순위 수수료는 모두 트랜잭션에 ComputeBudget 프로그램 명령어를 포함하여 요청합니다. 각 명령어는 명령어 슬롯 하나와 150 컴퓨트 유닛을 소비합니다. 생략해도 안전합니다: 런타임은 명령어당 200,000 CU(최대 1.4M), 64 MiB 데이터 크기 한도, 32 KiB 힙, 우선순위 수수료 0의 기본값으로 대체합니다.
V0 형식
v0는 레거시 메시지에 두 가지가 추가된 형식입니다: 0x80 버전 접두사 바이트와 명령어 뒤에 추가되는 address_table_lookups 배열. 그 이전의 모든 것은 레거시와 바이트 단위로 동일합니다.
V0 와이어 레이아웃
| 필드 | 크기 | 설명 |
|---|---|---|
num_signatures | compact-u16 | 서명 수 |
signatures | num_signatures x 64바이트 | Ed25519 서명 |
0x80 | 1바이트 | 버전 접두사 바이트 — 메시지의 첫 번째 바이트 |
header | 3바이트 | MessageHeader(레거시와 동일) |
num_account_keys | compact-u16 | 정적 계정 키 수 |
static_account_keys | num_account_keys x 32바이트 | 트랜잭션에 문자 그대로 나타나는 키 |
recent_blockhash | 32바이트 | 수명 지정자 |
num_instructions | compact-u16 | 명령어 수 |
instructions | 가변 | 레거시와 동일한 형식 |
address_table_lookups | compact-u16 + 가변 | ALT 참조(아래 참조) |
각 주소 테이블 조회 항목에는 다음이 포함됩니다:
| 필드 | 크기 | 설명 |
|---|---|---|
account_key | 32바이트 | ALT 계정의 공개 키 |
writable_indexes | compact-u16 + N x 1바이트 | 쓰기 가능한 계정에 대한 ALT 인덱스 |
readonly_indexes | compact-u16 + N x 1바이트 | 읽기 전용 계정에 대한 ALT 인덱스 |
주소 조회 테이블
ALT는 최대 256개의 공개 키를 저장하는 온체인 계정입니다. ALT를 참조함으로써 트랜잭션은 32바이트 공개 키 대신 1바이트 인덱스를 사용하여 추가 계정을 포함할 수 있으며, 이를 통해 계정당 오버헤드를 크게 줄일 수 있습니다.
런타임에서 실행이 시작되기 전에 validator는 모든 ALT 참조를 전체 공개 키로 해석합니다. 해석된 주소는 정적 계정 키에 추가되어 전체 계정 키 목록을 형성합니다. ALT로 해석된 계정은 정적 계정과 동일한 순서를 따릅니다: 쓰기 가능한 조회가 읽기 전용 조회보다 먼저 옵니다.
주소 조회 테이블은 온와이어 트랜잭션에서 계정이 참조되는 방식에만 영향을 미칩니다. 실행 시점에 런타임은 모든 인덱스를 전체 계정 주소로 해석합니다. ALT로 해석된 계정은 쓰기 가능하거나 읽기 전용(비서명자)일 수만 있으며, 서명자가 될 수 없습니다.
v0의 리소스 한도
레거시와 동일: ComputeBudget 명령어를 사용하며, 생략 시 동일한 기본값이 적용됩니다.
V1 형식
v1은 크기 제한을 4,096바이트로 높이고 트랜잭션 설정을 중심으로 메시지를 재구성합니다: 리소스 한도가 ComputeBudget 명령어에서 벗어나 메시지 자체의 고정 위치 필드로 이동합니다. 이를 통해 네트워크는 명령어 목록을 스캔하고 역직렬화하는 대신, 단일 고정 오프셋 읽기만으로 트랜잭션의 우선순위 수수료를 기준으로 순위를 매길 수 있습니다.
V1 와이어 레이아웃
| 필드 | 크기 | 설명 |
|---|---|---|
0x81 | 1바이트 | 버전 접두사 바이트 — 트랜잭션의 첫 번째 바이트 |
header | 3바이트 | MessageHeader (레거시와 동일) |
config_mask | 4바이트 | 어떤 설정 값이 존재하는지 표시하는 u32 LE 비트마스크 |
recent_blockhash | 32바이트 | 수명 지정자 |
num_instructions | 1바이트 | 고정 너비 카운트, 최대 64 |
num_addresses | 1바이트 | 고정 너비 카운트, 최대 64 |
addresses | N x 32바이트 | 계정 주소, 모두 인라인 — 조회 테이블 참조 없음 |
config_values | 0–20바이트 | 설정된 마스크 비트당 하나의 값, 비트 순서대로 (아래 참조) |
instruction_headers | N x 4바이트 | 명령어별: program_id_index (u8), num_accounts (u8), data_len (u16 LE) |
instruction_payloads | 가변 | 명령어별: 계정 인덱스, 이후 instruction data |
signatures | N x 64바이트 | 끝부분에 위치, 길이 접두사 없음 — 개수는 헤더에서 가져옴 |
디코더를 작성할 때 레거시 및 v0와의 두 가지 구조적 차이점을 주목해야 합니다. 카운트는 compact-u16 대신 고정 너비 u8 필드이며, 명령어는 두 구간으로 분리됩니다: 각 명령어가 연속으로 오는 대신, 고정 크기 헤더 전체가 먼저 오고 그 다음에 가변 길이 페이로드 전체가 옵니다.
v1의 계정: 주소 조회 테이블 없음
v1은 ALT 지원을 의도적으로 제거했습니다. 64개의 원시 주소는 2,048바이트로 4,096바이트 한도 내에 충분히 들어맞으므로, 모든 주소는 레거시처럼 인라인입니다. 애플리케이션이 조회 테이블에 의존하는 경우, v1으로 이전하면 해당 주소를 인라인으로 포함해야 합니다.
v1의 리소스 한도: 트랜잭션 설정
설정은 u32 비트마스크와 각 비트가 설정된 필드의 고정 너비 값으로 구성됩니다:
| 비트 | 필드 | 너비 | 비고 |
|---|---|---|---|
| 0–1 | 우선순위 수수료 | u64 | 총 lamport — 두 비트를 함께 설정 |
| 2 | 컴퓨트 유닛 한도 | u32 | |
| 3 | 로드된 계정 데이터 크기 한도 | u32 | |
| 4 | 요청된 힙 크기 | u32 |
알 수 없는 비트는 거부됩니다. 메시지가 서명되므로, 인식되지 않는 설정 필드는 자동으로 제거될 수 없습니다.
우선순위 수수료는 가격이 아닌 총 lamport입니다
레거시 및 v0에서 우선순위 수수료는 SetComputeUnitPrice를 통해 컴퓨트 유닛당 마이크로 lamport로 설정되며, 컴퓨트 유닛 한도를 곱합니다. v1에서는 lamport 단위의 절대 합계입니다 — 곱셈도, 반올림도 없습니다. 기존의 CU당 계산 방식을 그대로 가져오지 마세요. 총 수수료 공식은 그 외에는 동일합니다: (서명 수 × lamports_per_signature) + priority_fee.
ComputeBudget 명령어는 v1에서 no-op입니다
v1 트랜잭션은 ComputeBudget 명령어를 거부하지 않습니다 — 설정을 위해 무시할 뿐입니다. 해당 명령어는 여전히 성공적인 no-op으로 실행되어 150 컴퓨트 유닛과 64개 명령어 슬롯 중 하나를 소비하지만 예산에는 아무런 영향을 미치지 않습니다. v1 트랜잭션을 빌드할 때는 이를 제거하고, v1 트랜잭션을 읽을 때는 이를 스캔하는 것을 중단하세요: 값은 메시지 설정에 있습니다.
설정 필드는 반드시 명시적으로 설정해야 합니다
송신자에게 가장 중요한 동작 변경 사항: 레거시 및 v0와 달리, v1 트랜잭션은 컴퓨트 유닛 한도와 로드된 계정 데이터 크기 한도를 명시적으로 설정해야 하며, 그렇지 않으면 트랜잭션이 실패합니다.
| 미설정 필드 | 레거시 / v0 | v1 | 생략 시 증상 |
|---|---|---|---|
| 컴퓨트 유닛 한도 | 명령어당 200k, 최대 1.4M | 0 CU | 즉시 실패, 예산 초과 |
| 로드된 계정 데이터 크기 | 64 MiB | 0바이트 | 첫 번째 계정 로드 시 MaxLoadedAccountsDataSizeExceeded |
| 우선순위 수수료 | 0 | 0 | — |
| 힙 크기 | 32 KiB | 32 KiB | — |
권장하는 방법은 두 한도를 모두 최대값으로 설정하여 한 번 시뮬레이션한 후, 반환된 unitsConsumed와 loadedAccountsDataSize를 설정에 다시 기록하는 것입니다. 데이터 크기는 여유 공간 확보를 위해 다음 32 KiB 페이지 경계까지 올림합니다 (블록 비용 모델은 32 KiB 페이지 단위로 청구하므로, 다음 페이지 경계 이전의 여유 공간은 무료입니다).
v1 준비하기
v1은 트랜잭션 전송뿐만 아니라 읽기 방식도 변경합니다. v1이 활성화되면, 옵트인 없이 getTransaction 또는 getBlock을 호출하는 클라이언트는 v1 트랜잭션에서 실패하기 시작합니다:
getTransaction및getBlock에maxSupportedTransactionVersion: 1— 문자열"1"이 아닌 JSON 정수1— 을 전달하세요.0을 전달하면 매개변수를 생략한 것과 마찬가지로 v1 트랜잭션에서 실패하므로, v0 출시 당시에 업데이트된 코드베이스도 값을 변경해야 합니다.getTransaction은 v1 트랜잭션에서 오류-32015로 실패하며, v1 트랜잭션 하나가 동일한 오류로 전체getBlock응답을 실패시킵니다 — 부분 결과는 없습니다.blockSubscribe는block: null을 반환하고 진행을 멈추므로, 이를 빈 블록으로 읽는 소비자는 첫 번째 v1 slot부터 조용히 뒤처지게 됩니다.getSignaturesForAddress는 트랜잭션 본문을 검사하지 않으므로, v1 서명은 정상적으로 나열됩니다.- 옵트인된 응답에는 v1 트랜잭션의 메시지에
transactionConfig객체가 포함됩니다 (레거시 및 v0에서는 완전히 없음). ComputeBudget 명령어를 스캔하여 우선순위 수수료나 컴퓨트 한도를 도출하는 파이프라인은 모든 v1 트랜잭션에 대해 0을 조용히 보고하게 됩니다. - 클라이언트 측에서 트랜잭션을 디코딩할 때, 그리고 1,232바이트를 초과하는 트랜잭션에
sendTransaction/simulateTransaction을 사용할 때는encoding: 'base64'를 사용하세요 — base58 인코딩은 여전히 이전 크기 한도에 묶여 있습니다. - 클라이언트 라이브러리 지원은 최신 버전이 필요합니다:
@solana/kit8.0 이상, Agave 4.2.x 세대 Rust 크레이트, 또는 web3.js v3. web3.js v1은 1.99.0부터 v1을 읽을 수 있지만, 빌드하거나 전송할 수는 없습니다.
클라이언트 라이브러리 지원, 스트리밍(Geyser/gRPC) 버전 감지, 시뮬레이션 동작을 포함한 전체 마이그레이션 가이드는 트랜잭션 형식 v1 업그레이드 페이지를 참조하세요.
Is this page helpful?