Cấu trúc giao dịch

Tóm tắt

Một giao dịch có chữ ký + một thông điệp. Thông điệp chứa tiêu đề, địa chỉ tài khoản, blockhash gần đây và các lệnh đã biên dịch. Kích thước tối đa sau khi serialize: 1.232 byte.

Một Transaction có hai trường cấp cao nhất:

  • signatures: Một mảng các chữ ký
  • message: Thông tin giao dịch, bao gồm danh sách các lệnh cần được xử lý
Transaction
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}

Sơ đồ hiển thị hai phần của giao dịchSơ đồ hiển thị hai phần của giao dịch

Tổng kích thước sau khi serialize của một giao dịch không được vượt quá PACKET_DATA_SIZE (1.232 byte). Giới hạn này bằng 1.280 byte (MTU tối thiểu của IPv6) trừ đi 48 byte cho tiêu đề mạng (40 byte IPv6 + 8 byte tiêu đề phân mảnh). 1.232 byte bao gồm cả mảng signatures và struct message.

Sơ đồ hiển thị định dạng giao dịch và giới hạn kích thướcSơ đồ hiển thị định dạng giao dịch và giới hạn kích thước

Chữ ký

Trường signatures là một mảng được mã hóa compact của các giá trị Signature. Mỗi Signature là một chữ ký Ed25519 64 byte của Message đã được serialize, được ký bằng khóa riêng của tài khoản người ký. Một chữ ký được yêu cầu cho mỗi tài khoản người ký được tham chiếu bởi các lệnh của giao dịch.

Chữ ký đầu tiên trong mảng thuộc về người trả phí, tài khoản trả phí cơ bản và phí ưu tiên của giao dịch. Chữ ký đầu tiên này cũng đóng vai trò là ID giao dịch, được sử dụng để tra cứu giao dịch trên mạng. ID giao dịch thường được gọi là chữ ký giao dịch.

Yêu cầu đối với người trả phí:

  • Phải là tài khoản đầu tiên trong thông điệp (chỉ số 0) và là người ký.
  • Phải là tài khoản thuộc sở hữu của System Program hoặc tài khoản nonce (được xác thực bởi validate_fee_payer).
  • Phải có đủ lamports để chi trả rent_exempt_minimum + total_fee; nếu không giao dịch sẽ thất bại với lỗi InsufficientFundsForFee.

Thông điệp

Trường message là một struct Message chứa payload của giao dịch:

  • header: Header của thông điệp
  • account_keys: Một mảng các địa chỉ tài khoản được yêu cầu bởi các lệnh của giao dịch
  • recent_blockhash: Một blockhash đóng vai trò như dấu thời gian cho giao dịch
  • instructions: Một mảng các lệnh
Message
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>,
}

Trường header là một struct MessageHeader với ba trường u8 phân chia mảng account_keys thành các nhóm quyền:

  • num_required_signatures: Tổng số chữ ký được yêu cầu bởi giao dịch.
  • num_readonly_signed_accounts: Số lượng tài khoản đã ký chỉ đọc.
  • num_readonly_unsigned_accounts: Số lượng tài khoản chưa ký chỉ đọc.
MessageHeader
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,
}

Sơ đồ hiển thị ba phần của message headerSơ đồ hiển thị ba phần của message header

Địa chỉ tài khoản

Trường account_keys là một mảng được mã hóa compact các khóa công khai. Mỗi mục xác định một tài khoản được sử dụng bởi ít nhất một trong các lệnh của giao dịch. Mảng phải bao gồm mọi tài khoản và phải tuân theo thứ tự nghiêm ngặt này:

  1. Người ký + Có thể ghi
  2. Người ký + Chỉ đọc
  3. Không phải người ký + Có thể ghi
  4. Không phải người ký + Chỉ đọc

Thứ tự nghiêm ngặt này cho phép mảng account_keys được kết hợp với ba số đếm trong header của thông điệp để xác định quyền cho mỗi tài khoản mà không cần lưu trữ các cờ metadata cho từng tài khoản. Các số đếm trong header phân chia mảng thành bốn nhóm quyền được liệt kê ở trên.

Sơ đồ hiển thị thứ tự của mảng địa chỉ tài khoảnSơ đồ hiển thị thứ tự của mảng địa chỉ tài khoản

Blockhash gần đây

Trường recent_blockhash là một hash 32 byte phục vụ hai mục đích:

  1. Dấu thời gian: chứng minh giao dịch được tạo gần đây.
  2. Loại bỏ trùng lặp: ngăn chặn cùng một giao dịch được xử lý hai lần.

Một blockhash hết hạn sau 150 slot. Nếu blockhash không còn hợp lệ khi giao dịch đến, nó sẽ bị từ chối với BlockhashNotFound, trừ khi đó là một giao dịch durable nonce hợp lệ.

Phương thức RPC getLatestBlockhash cho phép bạn lấy blockhash hiện tại và chiều cao block cuối cùng mà tại đó blockhash sẽ còn hợp lệ.

Lệnh

Trường instructions là một mảng được mã hóa compact của các struct CompiledInstruction. Mỗi CompiledInstruction tham chiếu các tài khoản theo chỉ mục trong mảng account_keys thay vì bằng public key đầy đủ. Nó chứa:

  1. program_id_index: Chỉ mục trong account_keys xác định chương trình cần gọi.
  2. accounts: Mảng các chỉ mục trong account_keys chỉ định các tài khoản cần truyền cho chương trình.
  3. data: Mảng byte chứa instruction discriminator và các đối số đã được serialize.
CompiledInstruction
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>,
}

Mảng compact của các lệnhMảng compact của các lệnh

Định dạng nhị phân của giao dịch

Các giao dịch được serialize bằng cách sử dụng lược đồ mã hóa compact. Tất cả các mảng có độ dài biến đổi (chữ ký, khóa tài khoản, lệnh) đều được thêm tiền tố bằng mã hóa độ dài compact-u16. Định dạng này sử dụng 1 byte cho các giá trị 0-127 và 2-3 byte cho các giá trị lớn hơn.

Bố cục giao dịch legacy (trên đường truyền):

TrườngKích thướcMô tả
num_signatures1-3 byte (compact-u16)Số lượng chữ ký
signaturesnum_signatures x 64 byteChữ ký Ed25519
num_required_signatures1 byteTrường 1 của MessageHeader
num_readonly_signed1 byteTrường 2 của MessageHeader
num_readonly_unsigned1 byteTrường 3 của MessageHeader
num_account_keys1-3 byte (compact-u16)Số lượng khóa tài khoản tĩnh
account_keysnum_account_keys x 32 byteKhóa công khai
recent_blockhash32 byteBlockhash
num_instructions1-3 byte (compact-u16)Số lượng lệnh
instructionsbiến đổiMảng các lệnh đã biên dịch

Mỗi instruction đã biên dịch được tuần tự hóa như sau:

TrườngKích thướcMô tả
program_id_index1 byteChỉ mục trong account keys
num_accounts1-3 bytes (compact-u16)Số lượng account indices
account_indicesnum_accounts x 1 byteChỉ mục account key
data_len1-3 bytes (compact-u16)Độ dài instruction data
datadata_len bytesInstruction data không rõ

Tính toán kích thước

Cho PACKET_DATA_SIZE = 1.232 bytes, không gian khả dụng có thể được tính như sau:

Total = 1232 bytes
- compact-u16(num_sigs) # 1 byte
- num_sigs * 64 # signature bytes
- 3 # message header
- compact-u16(num_keys) # 1 byte
- num_keys * 32 # account key bytes
- 32 # recent blockhash
- compact-u16(num_ixs) # 1 byte
- sum(instruction_sizes) # per-instruction overhead + data

Ví dụ: giao dịch chuyển SOL

Sơ đồ dưới đây cho thấy cách các transaction và instruction hoạt động cùng nhau để cho phép người dùng tương tác với mạng lưới. Trong ví dụ này, SOL được chuyển từ một account này sang account khác.

Metadata của account người gửi cho biết rằng nó phải ký cho transaction. Điều này cho phép System Program trừ lamport. Cả account người gửi và người nhận đều phải có thể ghi được, để số dư lamport của chúng có thể thay đổi. Để thực thi instruction này, ví của người gửi gửi transaction chứa chữ ký của nó và message chứa instruction chuyển SOL.

Sơ đồ chuyển SOLSơ đồ chuyển SOL

Sau khi transaction được gửi, System Program xử lý instruction chuyển và cập nhật số dư lamport của cả hai account.

Sơ đồ quy trình chuyển SOLSơ đồ quy trình chuyển SOL

Ví dụ dưới đây cho thấy code liên quan đến các sơ đồ trên. Xem hàm transfer của System Program.

import {
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
// Create a connection to cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const LAMPORTS_PER_SOL = 1_000_000_000n;
const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL
// Fund sender with airdrop
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: sender.address,
lamports: lamports(LAMPORTS_PER_SOL), // 1 SOL
commitment: "confirmed"
});
// Check balance before transfer
const { value: preBalance1 } = await rpc.getBalance(sender.address).send();
const { value: preBalance2 } = await rpc.getBalance(recipient.address).send();
// Create a transfer instruction for transferring SOL from sender to recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount // 0.01 SOL in lamports
});
// Add the transfer instruction to a new transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
// Send the transaction to the network
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
const transactionSignature = getSignatureFromTransaction(signedTransaction);
// Check balance after transfer
const { value: postBalance1 } = await rpc.getBalance(sender.address).send();
const { value: postBalance2 } = await rpc.getBalance(recipient.address).send();
console.log(
"Sender prebalance:",
Number(preBalance1) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Recipient prebalance:",
Number(preBalance2) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Sender postbalance:",
Number(postBalance1) / Number(LAMPORTS_PER_SOL)
);
console.log(
"Recipient postbalance:",
Number(postBalance2) / Number(LAMPORTS_PER_SOL)
);
console.log("Transaction Signature:", transactionSignature);
Console
Click to execute the code.

Ví dụ sau đây cho thấy cấu trúc của một transaction chứa một instruction chuyển SOL duy nhất.

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 keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Decode the messageBytes
const compiledTransactionMessage =
getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);
console.log(JSON.stringify(compiledTransactionMessage, null, 2));
Console
Click to execute the code.

Mã bên dưới hiển thị kết quả đầu ra từ các đoạn mã trước đó. Định dạng khác nhau giữa các SDK, nhưng lưu ý rằng mỗi instruction đều chứa cùng thông tin bắt buộc.

{
"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
}
}
]
}

Lấy thông tin chi tiết giao dịch

Sau khi gửi, truy xuất thông tin chi tiết giao dịch bằng cách sử dụng chữ ký giao dịch và phương thức RPC getTransaction.

Bạn cũng có thể tìm giao dịch bằng cách sử dụng Solana Explorer.

Transaction Data
{
"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?

Mục lục

Chỉnh sửa trang

Quản lý bởi

© 2026 Solana Foundation.
Đã đăng ký bản quyền.
Kết nối