MỚI: Helius mua lại Light Protocol
Phí ưu tiên: Tìm hiểu cơ chế phí giao dịch của Solana
Blog/Kiến thức nền tảng

Phí ưu tiên: Tìm hiểu cơ chế phí giao dịch của Solana

Developer Experience Engineer0xIchigo trên X0xIchigo trên LinkedIn0xIchigo trên GitHub
Đọc trong 10 phút

Bài viết này nói về điều gì?

Solana rất nhanh. Tuy vậy, ngay cả trên blockchain nhanh nhất hiện có, người dùng vẫn muốn tối ưu hóa quá trình xử lý các giao dịch quan trọng. Phí ưu tiên là cách đảm bảo giao dịch của người dùng được đưa lên đầu hàng đợi thực thi. Đây là khoản phí bổ sung, không bắt buộc mà người dùng có thể thêm vào giao dịch.

Bài viết này trình bày ngắn gọn các đặc điểm tinh tế trong quá trình xử lý giao dịch trên Solana. Nội dung bao gồm giao dịch, vòng đời của giao dịch và cách hoạt động của phí giao dịch. Sau đó, bài viết sẽ tìm hiểu về phí ưu tiên, cách triển khai bằng mã và các phương pháp hay nhất.

Giao dịch và vòng đời của giao dịch

Giao dịch được dùng để gọi các chương trình Solana và thực hiện thay đổi trạng thái. Giao dịch là tập hợp các lệnh (tức là chỉ thị cho một lần gọi chương trình), cho trình xác thực biết cần thực hiện hành động nào, trên những tài khoản nào và liệu các tài khoản đó có quyền cần thiết hay không.

Vòng đời tổng quát của một giao dịch trên Solana diễn ra như sau:

  • Người dùng xác định rõ hành động muốn thực hiện. Ví dụ: Alice muốn gửi cho Bob 10 SOL
  • Người dùng tạo giao dịch cho hành động mong muốn. Ví dụ: Alice tạo một giao dịch chứa lệnh chuyển 10 SOL từ tài khoản của cô ấy sang tài khoản của Bob. Alice cũng thêm một blockhash gần đây và ký giao dịch bằng khóa riêng của mình
  • Người dùng gửi giao dịch đến mạng. Sau đó, họ nhận được thông tin cho biết giao dịch đã được thêm thành công hay chưa. Ví dụ: Alice gửi giao dịch với trạng thái cam kết đã xác nhận. Khi giao dịch được xác nhận, cô ấy nhận được chữ ký giao dịch. Alice có thể dùng chữ ký này trên trình khám phá khối như Orb để kiểm tra rằng cô ấy đã gửi thành công 10 SOL cho Bob. Tài khoản của cô ấy bị trừ 10 SOL, còn tài khoản của Bob được cộng 10 SOL

Khi gửi một giao dịch đã ký đến mạng, người dùng sử dụng nhà cung cấp RPC như Helius. Các RPC của Helius nhận giao dịch và kiểm tra lịch trình nút dẫn đầu hiện tại. Trên Solana, chỉ những trình xác thực cụ thể mới chịu trách nhiệm thêm mục nhập vào sổ cái tại các thời điểm nhất định. Nút dẫn đầu chịu trách nhiệm tạo khối cho slot hiện tại và được phân công bốn slot liên tiếp. Giao dịch đã ký được gửi đến nút dẫn đầu hiện tại và hai nút dẫn đầu tiếp theo.

Nút dẫn đầu hiện tại xác thực giao dịch đã ký và thực hiện các bước tiền xử lý khác trước khi lên lịch thực thi giao dịch. Không giống các L1 khác như Ethereum, Solana không có hàng đợi giao dịch toàn cục. Hầu hết trình xác thực sử dụng cơ chế lập lịch do Solana Labs cung cấp. Tuy nhiên, các trình xác thực chạy ứng dụng trình xác thực Jito sử dụng một pseudo-mempool (tức là MempoolStream) để sắp xếp giao dịch. Bộ lập lịch mặc định chạy đa luồng, trong đó mỗi luồng duy trì một hàng đợi giao dịch đang chờ thực thi. Giao dịch được sắp xếp vào các khối bằng cách kết hợp nguyên tắc vào trước ra trước (FIFO) và phí ưu tiên. Cần lưu ý rằng thứ tự này vốn không mang tính xác định vì các giao dịch được phân bổ vào các luồng thực thi tương đối ngẫu nhiên.

Khi một giao dịch được thực thi, giao dịch đó được truyền đi qua Turbine và các khoản phí được thanh toán tương ứng.

Cách hoạt động của phí giao dịch trên Solana

Phí giao dịch là khoản phí nhỏ được trả để xử lý giao dịch trên Solana. Nút dẫn đầu hiện tại xử lý các giao dịch được gửi qua mạng để tạo mục nhập sổ cái. Khi giao dịch được xác nhận là một phần của trạng thái, một khoản phí được thanh toán để hỗ trợ mô hình kinh tế của Solana. Về cơ bản, phí giao dịch mang lại lợi ích cho Solana bằng cách:

  • Trả thù lao cho trình xác thực
  • Giảm mức sử dụng không gian mạng bằng cách áp dụng chi phí theo thời gian thực cho giao dịch
  • Mang lại sự ổn định kinh tế dài hạn cho mạng thông qua mức phí tối thiểu mà giao thức thu được

Trong ngắn hạn, Solana dựa vào phần thưởng từ giao thức có tính lạm phát để bảo mật mạng. Để đạt mục tiêu này, mạng áp dụng tỷ lệ lạm phát toàn cục theo lịch nhằm thưởng cho trình xác thực. Về dài hạn, Solana dựa vào phí giao dịch để duy trì tính bảo mật. Một phần cố định (ban đầu được đặt ở mức 50%) của mỗi khoản phí giao dịch sẽ bị đốt, phần còn lại được gửi đến nút dẫn đầu hiện tại. Solana đốt phí để củng cố giá trị của SOL, đồng thời ngăn các trình xác thực độc hại kiểm duyệt giao dịch.

Phí giao dịch được tính dựa trên mức phí cơ sở cố định cho mỗi chữ ký và tài nguyên tính toán sử dụng trong giao dịch, được đo bằng đơn vị tính toán (CU). Mức phí cơ sở này có thể dao động từ 50% đến 1000% số lamport mục tiêu cho mỗi chữ ký. Mức mục tiêu mặc định cho mỗi chữ ký hiện được đặt là 10.000. Mỗi giao dịch được phân bổ ngân sách CU tối đa, gọi là ngân sách tính toán. Nếu vượt quá ngân sách này, môi trường thực thi sẽ dừng giao dịch và trả về lỗi. Ngân sách tối đa cho mỗi giao dịch là 1,4 triệu CU, còn giới hạn không gian khối là 48 triệu CU.

Phí ưu tiên là gì?

Do những giới hạn này, các giao dịch yêu cầu nhiều tài nguyên tính toán có thể lấp đầy không gian khối và làm chậm các giao dịch khác. Solana đã giới thiệu một khoản phí không bắt buộc, gọi là phí ưu tiên, để giao dịch có thể được ưu tiên hơn các giao dịch khác trong hàng đợi của nút dẫn đầu. Việc trả khoản phí này giúp tăng mức ưu tiên của giao dịch, nhờ đó giao dịch được thực thi nhanh hơn. Điều này hữu ích cho các giao dịch nhạy cảm về thời gian hoặc có giá trị cao. Mức ưu tiên phí của giao dịch được xác định bởi số đơn vị tính toán mà giao dịch yêu cầu. Giao dịch yêu cầu càng nhiều đơn vị tính toán thì càng phải trả nhiều phí để duy trì vị trí ưu tiên trong hàng đợi giao dịch. Việc tính phí cao hơn cho nhiều đơn vị tính toán hơn giúp ngăn chặn hành vi spam bằng các giao dịch yêu cầu nhiều tài nguyên tính toán.

Phí ưu tiên là tích của ngân sách tính toán của giao dịch và giá đơn vị tính toán, được đo bằng micro-lamport: priorityFees = computeBudget * computeUnitPrice

Ngân sách tính toán

computeBudget xác định số đơn vị tính toán tối đa mà một giao dịch có thể sử dụng, chi phí liên quan đến các thao tác khác nhau mà giao dịch có thể thực hiện và các giới hạn vận hành mà giao dịch phải tuân thủ. Các thao tác sau phát sinh chi phí tính toán:

  • Thực thi các lệnh SBF
  • Truyền dữ liệu giữa các chương trình
  • Gọi các lệnh gọi hệ thống (ví dụ: ghi log, tạo địa chỉ chương trình, CPI)

Đối với lệnh gọi liên chương trình (CPI), chương trình được gọi hoạt động trong phạm vi ngân sách tính toán của chương trình gọi (tức là chương trình cha). Nếu chương trình được gọi sử dụng hết ngân sách tính toán còn lại hoặc vượt quá giới hạn đã đặt, toàn bộ chuỗi lệnh gọi chương trình sẽ thất bại. Điều này bao gồm cả quá trình thực thi giao dịch ban đầu đã khởi chạy quy trình.

Bạn có thể xem ngân sách tính toán hiện tại tại đây.

Cách triển khai phí ưu tiên bằng mã

Phí ưu tiên của giao dịch được thiết lập bằng cách đặt một lệnh SetComputeUnitPricehttps://github.com/solana-labs/solana/blob/2971e84ec87815adb1e4def95cbcd8d0d96845f5/sdk/src/compute_budget.rs#L59và một lệnh SetComputeUnitLimit không bắt buộc. Nếu không cung cấp lệnh SetComputeUnitPrice, giao dịch sẽ mặc định có mức ưu tiên thấp nhất vì không có khoản phí bổ sung nào. Nếu không cung cấp lệnh SetComputeUnitLimit, giới hạn sẽ được tính bằng tích của số lệnh trong giao dịch và giới hạn đơn vị tính toán mặc định. Môi trường thực thi sử dụng giá đơn vị tính toán và giới hạn đơn vị tính toán để tính phí ưu tiên, rồi dùng khoản phí này để xác định mức ưu tiên của giao dịch.

Chúng ta phải thêm các lệnh này vào giao dịch mong muốn để bổ sung phí ưu tiên bằng mã. Trong Javascript, mã sẽ như sau:

Mã
import {
  Keypair,
  Connection,
  PublicKey,
  Transaction,
  SystemProgram,
  LAMPORTS_PER_SOL,
  sendAndConfirmTransaction,
  ComputeBudgetProgram,
} from "@solana/web3.js";

async function main() {
  // Initialize an RPC client
  const clusterUrl = "http://127.0.0.1:8899";
  const connection = new Connection(clusterUrl, "confirmed");

  // Initialize new sender and receiver keypairs
  const fromKeypair = Keypair.generate();
  const toPubkey = new PublicKey(Keypair.generate().publicKey);

  // Airdrop SOL to the from_keypair
  const airdropAmount = 100 * LAMPORTS_PER_SOL;
  try {
    const signature = await connection.requestAirdrop(
      fromKeypair.publicKey,
      airdropAmount
    );
    console.log("Airdrop requested. Signature:", signature);
    await connection.confirmTransaction({
      signature,
      confirmation: "confirmed",
    });
  } catch (e) {
    console.error("Failed to request airdrop:", e);
    return;
  }

  // Check if airdrop was successful
  const balance = await connection.getBalance(fromKeypair.publicKey);
  if (balance < airdropAmount) {
    console.error(
      "Airdrop was not successful. The current balance is insufficient"
    );
    return;
  }

  // Airdrop SOL to the toPubkey
  const airdropAmountTo = 100 * LAMPORTS_PER_SOL; // 1 SOL in lamports
  try {
    const signature = await connection.requestAirdrop(toPubkey, airdropAmount);
    console.log("Airdrop requested. Signature:", signature);
    await connection.confirmTransaction({
      signature,
      confirmation: "confirmed",
    });
  } catch (e) {
    console.error("Failed to request airdrop:", e);
    return;
  }

  // Check if airdrop was successful
  const balanceTo = await connection.getBalance(toPubkey);
  if (balance < airdropAmount) {
    console.error(
      "Airdrop was not successful. The current balance is insufficient"
    );
    return;
  }

  console.log(`Account balance: ${balance / LAMPORTS_PER_SOL} SOL`);

  // Create the priority fee instructions
  const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: 1,
  });

  const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: 200_000,
  });

  // Create the transfer instruction
  const transferIx = SystemProgram.transfer({
    fromPubkey: fromKeypair.publicKey,
    toPubkey,
    lamports: 100_000,
  });

  // Create the transaction with priority fees
  const transaction = new Transaction().add(
    computePriceIx,
    computeLimitIx,
    transferIx
  );

  // Fetch the recent blockhash and sign the transaction
  transaction.recentBlockhash = (
    await connection.getLatestBlockhash()
  ).blockhash;
  transaction.sign(fromKeypair);

  // Send the transaction
  try {
    const txid = await sendAndConfirmTransaction(connection, transaction, [
      fromKeypair,
    ]);
    console.log("Transaction sent successfully with signature", txid);
  } catch (e) {
    console.error("Failed to send transaction:", e);
  }
}

main();

Trong đoạn mã này, chúng ta:

  • Thiết lập môi trường kiểm thử bằng Localhost
  • Tạo hai ví mới (tức là fromKeypair và toPubkey)
  • Airdrop 100 SOL vào cả hai ví
  • Đảm bảo các đợt airdrop đã thành công
  • Tạo các lệnh phí ưu tiên (tức là computePriceIx và computeLimitIx)
  • Tạo một lệnh chuyển 100.000 lamport từ fromKeypair sang toPubkey
  • Tạo một giao dịch mới và thêm tất cả các lệnh vào giao dịch
  • Gắn blockhash mới nhất vào giao dịch và ký giao dịch
  • Gửi giao dịch và xác nhận giao dịch đã được gửi thành công

Chỉ vậy thôi — phí ưu tiên đơn giản là các lệnh được thêm vào giao dịch để trả phí cho thời gian thực thi nhanh hơn. Phần lớn đoạn mã này dùng để cấu hình môi trường phát triển và hai ví nhằm chuyển SOL giữa chúng. Phần chúng ta quan tâm là tạo các lệnh để thêm phí ưu tiên và gắn chúng vào một giao dịch:

Mã
// Other code

const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
	microLamports: 1,
});

const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
	units: 200_000,
});

// Other code

const transaction = new Transaction().add(computePriceIx, computeLimitIx, transferIx);

// Rest of the code

Các phương pháp hay nhất

Thứ tự của các lệnh này rất quan trọng vì chúng được thực thi tuần tự. Ví dụ: nếu một giao dịch vượt quá giới hạn tính toán mặc định (tức là 200 nghìn CU) trước khi mở rộng giới hạn tính toán, giao dịch sẽ thất bại. Giả sử chúng ta có giao dịch sau:

  • Lệnh 1 sử dụng 100 nghìn CU
  • Lệnh 2 sử dụng 150 nghìn CU
  • Lệnh 3 mở rộng giới hạn tính toán

Giao dịch này sẽ thất bại nếu không hoán đổi lệnh 2 và 3. Vì vậy, bạn nên thêm lệnh giới hạn tính toán trước khi thêm các lệnh khác vào giao dịch. Hãy nhớ rằng bạn không cần sử dụng lệnh SetComputeLimit nếu muốn thêm phí ưu tiên vào giao dịch — lệnh này hoàn toàn không bắt buộc. Vị trí của lệnh SetComputePrice không quan trọng.

Bạn nên sử dụng phương thức RPC getRecentPrioritizationFees để lấy danh sách các khoản phí ưu tiên đã trả gần đây. Dữ liệu này có thể được dùng để ước tính mức phí ưu tiên phù hợp cho giao dịch, giúp đảm bảo cụm xử lý giao dịch đồng thời giảm thiểu phí phải trả. Ngoài ra, Helius cung cấp Priority Fee API mới mà chúng ta sẽ trình bày trong phần tiếp theo.

Giao dịch cũng nên yêu cầu lượng đơn vị tính toán tối thiểu cần thiết để thực thi nhằm giảm thiểu các khoản phí này. Lưu ý rằng chi phí không được điều chỉnh khi số đơn vị tính toán được yêu cầu vượt quá tổng số đơn vị mà giao dịch thực sự sử dụng.

Một số nhà cung cấp ví như Phantom nhận thấy rằng dApp có thể đặt phí ưu tiên cho giao dịch. Tuy nhiên, họ không khuyến khích điều này vì thường tạo ra sự phức tạp không cần thiết cho người dùng cuối. Thay vào đó, họ khuyến nghị các nhà phát triển dApp để Phantom thay mặt người dùng áp dụng phí ưu tiên. Ví dụ, Solfare giải quyết vấn đề này bằng cách tự động phát hiện liệu Solana có đang chịu tải hay không và tăng nhẹ phí để ưu tiên giao dịch của bạn hơn các giao dịch khác.

Helius Priority Fee API

Việc tính phí ưu tiên bằng phương thức RPC getRecentPrioritizationFees là cách trực quan để tính phí theo từng slot. Tuy nhiên, việc này có thể khó khăn do điều kiện mạng liên tục thay đổi và bản chất phản hồi của getRecentPrioritizationFees' (tức là trả về danh sách giá trị của 150 khối trước đó, chỉ hữu ích để ước lượng giá trị tối thiểu cần đặt cho phí).

Helius Priority Fee API giới thiệu một phương thức mới, getPriorityFeeEstimate, giúp đơn giản hóa phản hồi thành một giá trị duy nhất, có xét đến thị trường phí toàn cục và cục bộ. Phương thức này sử dụng một tập hợp phân vị được xác định trước để đưa ra ước tính. Các phân vị, hay cấp độ này, dao động từ NONE (phân vị thứ 0) đến UNSAFE_MAX (phân vị thứ 100 và được gắn nhãn không an toàn để ngăn người dùng vô tình rút cạn tiền). Người dùng cũng có thể chọn nhận tất cả các cấp độ ưu tiên và điều chỉnh phạm vi được dùng trong phép tính này thông qua lookbackSlots. Vì vậy, tương tự getRecentPrioritizationFees, người dùng có thể xem lại một số lượng slot nhất định khi tính toán ước tính.

Ví dụ: chúng ta có thể tính tất cả các cấp độ phí ưu tiên cho chương trình Jupiter v6 bằng tập lệnh sau:

Mã
const url = `https://mainnet.helius-rpc.com/?api-key=`;

const getRecentPrioritizationFees = async () => {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "getPriorityFeeEstimate",
      params: [{
        "accountKeys": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
        "options": {
            "includeAllPriorityFeeLevels": true,
        }
      }]
    }),
  });
  const data = await response.json();
  console.log("Fee: ", data);
};

getRecentPrioritizationFees();

Endpoint này đang được phát triển tích cực. Để biết thêm thông tin, hãy xem tài liệu Helius.

Kết luận

Khám phá thế giới giao dịch Solana cho thấy một hệ thống tinh vi, cân bằng hiệu quả mạng với các động lực kinh tế. Việc hiểu cách giao dịch hoạt động, cùng với phí giao dịch và phí ưu tiên, giúp nhà phát triển và người dùng đưa ra quyết định sáng suốt hơn để tối ưu hóa hoạt động tương tác trên Solana. Khả năng triển khai phí ưu tiên bằng mã mở ra những hướng đi mới cho các giao dịch có giá trị cao và nhạy cảm về thời gian. Điều này mang lại tính linh hoạt và hiệu quả cao hơn cho các hoạt động trên Solana.

Nếu đã đọc đến đây, cảm ơn bạn, anon! Hãy nhập địa chỉ email bên dưới để không bao giờ bỏ lỡ thông tin mới nhất về Solana. Sẵn sàng tìm hiểu sâu hơn? Tham gia Discord của chúng tôi để bắt đầu xây dựng tương lai trên blockchain có hiệu suất cao nhất ngay hôm nay.

Tài nguyên bổ sung / Nội dung nên đọc thêm

Đăng ký nhận tin từ Helius

Luôn cập nhật những thông tin mới nhất về phát triển Solana và nhận thông báo khi chúng tôi đăng bài

Hình ảnh phóng to