트랜잭션
솔라나 네트워크와 상호작용하려면 트랜잭션을 보내야 합니다. 트랜잭션은 여러 양식을 담고 있는 봉투라고 생각할 수 있습니다. 각 양식은 네트워크에 무엇을 해야 할지 알려주는 명령어입니다. 트랜잭션을 보내는 것은 양식이 처리될 수 있도록 봉투를 우편으로 보내는 것과 같습니다.
아래 예시는 두 개의 트랜잭션을 간소화한 버전을 보여줍니다. 첫 번째 트랜잭션이 처리되면 단일 명령어가 실행됩니다. 두 번째 트랜잭션이 처리되면 순차적으로 세 개의 명령어가 실행됩니다: 먼저 명령어 1, 그 다음 명령어 2, 마지막으로 명령어 3이 실행됩니다.
트랜잭션은 원자적입니다: 단일 명령어가 실패하면 전체 트랜잭션이 실패하고 어떠한 변경도 발생하지 않습니다.
두 개의 트랜잭션을 보여주는 간소화된 다이어그램
Transaction는
다음 정보로 구성됩니다:
signatures: 서명 배열message: 처리될 명령어 목록을 포함한 트랜잭션 정보
pub struct Transaction {#[wasm_bindgen(skip)]#[serde(with = "short_vec")]pub signatures: Vec<Signature>,#[wasm_bindgen(skip)]pub message: Message,}
트랜잭션의 두 부분을 보여주는 다이어그램
트랜잭션의 총 크기 제한은
1232
바이트입니다. 이 제한에는 signatures 배열과
message 구조체가 모두 포함됩니다.
이 제한은 IPv6 최대 전송 단위(MTU) 크기인 1280바이트에서 네트워크 헤더(IPv6 40바이트 + 헤더 8바이트)를 위한 48바이트를 뺀 값에서 비롯됩니다.
트랜잭션 형식과 크기 제한을 보여주는 다이어그램
서명
트랜잭션의 signatures 배열에는 Signature 구조체가 포함되어 있습니다. 각
Signature는
64바이트이며 계정의 개인 키로 트랜잭션의 Message에 서명하여 생성됩니다.
트랜잭션의 명령어에 포함된 각 서명자 계정에 대해 서명을
제공해야 합니다.
첫 번째 서명은 트랜잭션의 기본 수수료를 지불할 계정에 속하며 트랜잭션 서명입니다. 트랜잭션 서명은 네트워크에서 트랜잭션의 세부 정보를 조회하는 데 사용할 수 있습니다.
메시지
트랜잭션의 message는 다음 정보를 포함하는
Message
구조체입니다:
header: 메시지 헤더account_keys: 트랜잭션의 명령어에 필요한 계정 주소 배열recent_blockhash: 트랜잭션의 타임스탬프 역할을 하는 블록해시instructions: 명령어 배열
공간을 절약하기 위해 트랜잭션은 각 계정에 대한 권한을 개별적으로 저장하지
않습니다. 대신, 계정 권한은 header와 account_keys를 사용하여 결정됩니다.
pub struct Message {/// The message header, identifying signed and read-only `account_keys`.pub header: MessageHeader,/// All the account keys used by this transaction.#[serde(with = "short_vec")]pub account_keys: Vec<Pubkey>,/// The id of a recent ledger entry.pub recent_blockhash: Hash,/// Programs that will be executed in sequence and committed in/// one atomic transaction if all succeed.#[serde(with = "short_vec")]pub instructions: Vec<CompiledInstruction>,}
헤더
메시지의 header는
MessageHeader
구조체입니다. 다음 정보를 포함합니다:
num_required_signatures: 트랜잭션에 필요한 총 서명 수num_readonly_signed_accounts: 서명이 필요한 읽기 전용 계정의 총 수num_readonly_unsigned_accounts: 서명이 필요하지 않은 읽기 전용 계정의 총 수
pub struct MessageHeader {/// The number of signatures required for this message to be considered/// valid. The signers of those signatures must match the first/// `num_required_signatures` of [`Message::account_keys`].pub num_required_signatures: u8,/// The last `num_readonly_signed_accounts` of the signed keys are read-only/// accounts.pub num_readonly_signed_accounts: u8,/// The last `num_readonly_unsigned_accounts` of the unsigned keys are/// read-only accounts.pub num_readonly_unsigned_accounts: u8,}
메시지 헤더의 세 부분을 보여주는 다이어그램
계정 주소
메시지의
account_keys는
간결한 배열 형식으로 전송된
계정 주소의 배열입니다. 배열의 접두사는 길이를 나타냅니다. 배열의 각 항목은
명령어에서 사용하는 계정을 가리키는 공개 키입니다. accounts_keys 배열은
완전해야 하며 다음과 같이 엄격하게 정렬되어야 합니다:
- 서명자 + 쓰기 가능
- 서명자 + 읽기 전용
- 서명자 아님 + 쓰기 가능
- 서명자 아님 + 읽기 전용
엄격한 정렬을 통해 account_keys 배열을 메시지의 header 정보와
결합하여 각 계정의 권한을 결정할 수 있습니다.
계정 주소 배열의 순서를 보여주는 다이어그램
최근 블록해시
메시지의 recent_blockhash는 트랜잭션 타임스탬프 역할을 하고 중복 트랜잭션을
방지하는 해시 값입니다. 블록해시는
150 블록
후에 만료됩니다. (각 블록이 400ms라고 가정할 때 약 1분에 해당합니다.) 블록이
만료된 후에는 트랜잭션도 만료되어 처리될 수 없습니다.
getLatestBlockhash RPC 메서드를
사용하면 현재 블록해시와 블록해시가 유효한 마지막 블록 높이를 얻을 수
있습니다.
명령어
메시지의
instructions는
처리될 모든 명령어의 배열로,
컴팩트 배열 형식으로
전송됩니다. 배열의 접두사는 길이를 나타냅니다. 배열의 각 항목은
CompiledInstruction
구조체이며 다음 정보를 포함합니다:
program_id_index:account_keys배열의 주소를 가리키는 인덱스입니다. 이 값은 명령어를 처리하는 프로그램의 주소를 나타냅니다.accounts:account_keys배열의 주소를 가리키는 인덱스 배열입니다. 각 인덱스는 이 명령어에 필요한 계정의 주소를 가리킵니다.data: 프로그램에서 어떤 명령어를 호출할지 지정하는 바이트 배열입니다. 또한 명령어에 필요한 추가 데이터도 포함합니다. (예: 함수 인자)
pub struct CompiledInstruction {/// Index into the transaction keys array indicating the program account that executes this instruction.pub program_id_index: u8,/// Ordered indices into the transaction keys array indicating which accounts to pass to the program.#[serde(with = "short_vec")]pub accounts: Vec<u8>,/// The program input data.#[serde(with = "short_vec")]pub data: Vec<u8>,}
명령어의 컴팩트 배열
트랜잭션 구조 예시
다음 예시는 단일 SOL 전송 명령어가 포함된 트랜잭션의 구조를 보여줍니다.
import {createSolanaRpc,generateKeyPairSigner,lamports,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstructions,pipe,signTransactionMessageWithSigners,getCompiledTransactionMessageDecoder} from "@solana/kit";import { getTransferSolInstruction } from "@solana-program/system";const rpc = createSolanaRpc("http://localhost:8899");const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Generate sender and recipient keypairsconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();// Define the amount to transferconst LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = getTransferSolInstruction({source: sender,destination: recipient.address,amount: transferAmount});// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Decode the messageBytesconst compiledTransactionMessage =getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);console.log(JSON.stringify(compiledTransactionMessage, null, 2));
이전 코드 스니펫의 출력을 아래 코드에서 보여줍니다. SDK마다 형식은 다르지만, 각 명령어가 동일한 필수 정보를 포함하고 있음을 확인하세요.
{"version": 0,"header": {"numSignerAccounts": 1,"numReadonlySignerAccounts": 0,"numReadonlyNonSignerAccounts": 1},"staticAccounts": ["HoCy8p5xxDDYTYWEbQZasEjVNM5rxvidx8AfyqA4ywBa","5T388jBjovy7d8mQ3emHxMDTbUF8b7nWvAnSiP3EAdFL","11111111111111111111111111111111"],"lifetimeToken": "EGCWPUEXhqHJWYBfDirq3mHZb4qDpATmYqBZMBy9TBC1","instructions": [{"programAddressIndex": 2,"accountIndices": [0, 1],"data": {"0": 2,"1": 0,"2": 0,"3": 0,"4": 128,"5": 150,"6": 152,"7": 0,"8": 0,"9": 0,"10": 0,"11": 0}}]}
트랜잭션이 제출된 후에는 트랜잭션 서명과 getTransaction RPC 메서드를 사용하여 세부 정보를 검색할 수 있습니다. 응답은 다음 스니펫과 유사한 구조를 가질 것입니다.
Solana Explorer를 사용하여 트랜잭션을 찾을 수도 있습니다.
{"blockTime": 1745196488,"meta": {"computeUnitsConsumed": 150,"err": null,"fee": 5000,"innerInstructions": [],"loadedAddresses": {"readonly": [],"writable": []},"logMessages": ["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"postBalances": [989995000, 10000000, 1],"postTokenBalances": [],"preBalances": [1000000000, 0, 1],"preTokenBalances": [],"rewards": [],"status": {"Ok": null}},"slot": 13049,"transaction": {"message": {"header": {"numReadonlySignedAccounts": 0,"numReadonlyUnsignedAccounts": 1,"numRequiredSignatures": 1},"accountKeys": ["8PLdpLxkuv9Nt8w3XcGXvNa663LXDjSrSNon4EK7QSjQ","7GLg7bqgLBv1HVWXKgWAm6YoPf1LoWnyWGABbgk487Ma","11111111111111111111111111111111"],"recentBlockhash": "7ZCxc2SDhzV2bYgEQqdxTpweYJkpwshVSDtXuY7uPtjf","instructions": [{"accounts": [0, 1],"data": "3Bxs4NN8M2Yn4TLb","programIdIndex": 2,"stackHeight": null}],"indexToProgramIds": {}},"signatures": ["3jUKrQp1UGq5ih6FTDUUt2kkqUfoG2o4kY5T1DoVHK2tXXDLdxJSXzuJGY4JPoRivgbi45U2bc7LZfMa6C4R3szX"]},"version": "legacy"}
Is this page helpful?