Mỗi giao dịch Solana bao gồm một blockhash gần đây—một tham chiếu đến trạng thái mạng gần đây chứng minh giao dịch được tạo "bây giờ". Mạng từ chối bất kỳ giao dịch nào có blockhash cũ hơn ~150 khối (~60-90 giây), ngăn chặn các cuộc tấn công phát lại và gửi lỗi thời. Điều này hoạt động hoàn hảo cho thanh toán thời gian thực. Nhưng nó phá vỡ các quy trình cần khoảng cách giữa ký và gửi, chẳng hạn như:
| Tình huống | Tại sao giao dịch tiêu chuẩn thất bại |
|---|---|
| Vận hành kho bạc | CFO ở Tokyo ký, Controller ở NYC phê duyệt—90 giây là không đủ |
| Quy trình tuân thủ | Giao dịch cần xem xét pháp lý/tuân thủ trước khi thực thi |
| Ký lưu trữ lạnh | Máy cách ly không khí yêu cầu chuyển giao thủ công các giao dịch đã ký |
| Chuẩn bị hàng loạt | Chuẩn bị bảng lương hoặc giải ngân trong giờ làm việc, thực thi qua đêm |
| Phối hợp đa chữ ký | Nhiều người phê duyệt trên các múi giờ khác nhau |
| Thanh toán theo lịch | Lên lịch thanh toán để thực thi vào ngày tương lai |
Trong tài chính truyền thống, một tấm séc đã ký không hết hạn trong 90 giây. Một số hoạt động blockchain cũng không nên như vậy. Durable nonces giải quyết vấn đề này bằng cách thay thế blockhash gần đây bằng một giá trị được lưu trữ, bền vững chỉ tiến lên khi bạn sử dụng nó—mang lại cho bạn các giao dịch vẫn hợp lệ cho đến khi bạn sẵn sàng gửi.
Cách hoạt động
Thay vì sử dụng blockhash gần đây (có hiệu lực ~150 block), bạn sử dụng tài khoản nonce, một tài khoản đặc biệt lưu trữ giá trị duy nhất có thể được sử dụng thay cho blockhash. Mỗi giao dịch sử dụng nonce này phải "nâng cấp" nó như là lệnh đầu tiên. Mỗi giá trị nonce chỉ có thể được sử dụng cho một giao dịch.
Tài khoản nonce tốn ~0.0015 SOL để miễn phí lưu trữ. Một tài khoản nonce = một giao dịch đang chờ xử lý tại một thời điểm. Đối với quy trình song song, hãy tạo nhiều tài khoản nonce.
Tạo tài khoản nonce
Việc tạo tài khoản nonce yêu cầu hai lệnh trong một giao dịch duy nhất:
- Tạo tài khoản sử dụng
getCreateAccountInstructiontừ System Program - Khởi tạo nó như một nonce sử dụng
getInitializeNonceAccountInstruction
Tạo keypair
Tạo một keypair mới để sử dụng làm địa chỉ tài khoản nonce và tính toán không gian cần thiết cũng như phí lưu trữ.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();
Tạo lệnh tạo tài khoản
Tạo tài khoản thuộc sở hữu của System Program với đủ lamport để miễn phí lưu trữ.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();const createNonceAccountIx = getCreateAccountInstruction({payer: sender,newAccount: nonceKeypair,lamports: nonceRent,space: nonceSpace,programAddress: SYSTEM_PROGRAM_ADDRESS});
Khởi tạo lệnh nonce
Khởi tạo tài khoản như một tài khoản nonce, thiết lập quyền quản lý có thể nâng cấp nó.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();const createNonceAccountIx = getCreateAccountInstruction({payer: sender,newAccount: nonceKeypair,lamports: nonceRent,space: nonceSpace,programAddress: SYSTEM_PROGRAM_ADDRESS});const initNonceIx = getInitializeNonceAccountInstruction({nonceAccount: nonceKeypair.address,nonceAuthority: sender.address});
Xây dựng giao dịch
Xây dựng một giao dịch với cả hai lệnh.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();const createNonceAccountIx = getCreateAccountInstruction({payer: sender,newAccount: nonceKeypair,lamports: nonceRent,space: nonceSpace,programAddress: SYSTEM_PROGRAM_ADDRESS});const initNonceIx = getInitializeNonceAccountInstruction({nonceAccount: nonceKeypair.address,nonceAuthority: sender.address});const { value: blockhash } = await rpc.getLatestBlockhash().send();const createNonceTx = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),(tx) =>appendTransactionMessageInstructions([createNonceAccountIx, initNonceIx],tx));
Ký và gửi
Ký và gửi giao dịch để tạo và khởi tạo tài khoản nonce.
Xây dựng giao dịch hoãn lại
Thay vì blockhash gần đây, hãy sử dụng blockhash của tài khoản nonce làm thời
gian sống của giao dịch.
Lấy nonce
Lấy dữ liệu từ tài khoản nonce. Sử dụng blockhash từ tài khoản nonce làm thời
gian sống của giao dịch.
{version: 1,state: 1,authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',lamportsPerSignature: 5000n}
Tạo chỉ thị chuyển khoản
Tạo chỉ thị cho khoản thanh toán của bạn. Ví dụ này cho thấy việc chuyển token.
Xây dựng giao dịch với durable nonce
Sử dụng setTransactionMessageLifetimeUsingDurableNonce để đặt nonce làm
blockhash và tự động thêm chỉ thị advance nonce vào đầu.
Ký giao dịch
Ký giao dịch. Bây giờ nó sử dụng durable nonce thay vì blockhash tiêu chuẩn.
Lưu trữ hoặc gửi giao dịch
Sau khi ký, mã hóa giao dịch để lưu trữ. Khi sẵn sàng, gửi nó đến mạng lưới.
Mã hóa để lưu trữ
Mã hóa giao dịch đã ký sang base64. Lưu giá trị này vào cơ sở dữ liệu của bạn.
Gửi giao dịch
Gửi giao dịch đã ký khi sẵn sàng. Giao dịch vẫn hợp lệ cho đến khi nonce được nâng cấp.
Demo
// Generate keypairs for sender and recipientconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();console.log("Sender Address:", sender.address);console.log("Recipient Address:", recipient.address);// Demo Setup: Create RPC connection, mint, and token accountsconst { rpc, rpcSubscriptions, mint } = await demoSetup(sender, recipient);// =============================================================================// Step 1: Create a Nonce Account// =============================================================================const nonceKeypair = await generateKeyPairSigner();console.log("\nNonce Account Address:", nonceKeypair.address);const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();// Instruction to create new account for the nonceconst createNonceAccountIx = getCreateAccountInstruction({payer: sender,newAccount: nonceKeypair,lamports: nonceRent,space: nonceSpace,programAddress: SYSTEM_PROGRAM_ADDRESS});// Instruction to initialize the nonce accountconst initNonceIx = getInitializeNonceAccountInstruction({nonceAccount: nonceKeypair.address,nonceAuthority: sender.address});// Build and send nonce account creation transactionconst { value: blockhash } = await rpc.getLatestBlockhash().send();const createNonceTx = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),(tx) =>appendTransactionMessageInstructions([createNonceAccountIx, initNonceIx],tx));const signedCreateNonceTx =await signTransactionMessageWithSigners(createNonceTx);assertIsTransactionWithBlockhashLifetime(signedCreateNonceTx);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedCreateNonceTx,{ commitment: "confirmed" });console.log("Nonce Account created.");// =============================================================================// Step 2: Token Payment with Durable Nonce// =============================================================================// Fetch current nonce value from the nonce accountconst { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);console.log("Nonce Account data:", nonceData);const [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipientAta] = await findAssociatedTokenPda({mint: mint.address,owner: recipient.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});console.log("\nMint Address:", mint.address);console.log("Sender Token Account:", senderAta);console.log("Recipient Token Account:", recipientAta);const transferInstruction = getTransferInstruction({source: senderAta,destination: recipientAta,authority: sender.address,amount: 250_000n // 0.25 tokens});// Create transaction message using durable nonce lifetime// setTransactionMessageLifetimeUsingDurableNonce automatically prepends// the AdvanceNonceAccount instructionconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) =>setTransactionMessageLifetimeUsingDurableNonce({nonce: nonceData.blockhash as string as Nonce,nonceAccountAddress: nonceKeypair.address,nonceAuthorityAddress: nonceData.authority},tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);assertIsTransactionWithDurableNonceLifetime(signedTransaction);const transactionSignature = getSignatureFromTransaction(signedTransaction);// Encode the transaction to base64, optionally save and send at a later timeconst base64EncodedTransaction =getBase64EncodedWireTransaction(signedTransaction);console.log("\nBase64 Encoded Transaction:", base64EncodedTransaction);// Send the encoded transaction, blockhash does not expireawait rpc.sendTransaction(base64EncodedTransaction, {encoding: "base64",skipPreflight: true}).send();console.log("\n=== Token Payment with Durable Nonce Complete ===");console.log("Transaction Signature:", transactionSignature);// =============================================================================// Demo Setup Helper Function// =============================================================================
Vô hiệu hóa giao dịch đang chờ xử lý
Mỗi tài khoản nonce blockhash chỉ có thể được sử dụng một lần. Để vô hiệu hóa
giao dịch đang chờ xử lý hoặc chuẩn bị tài khoản nonce để sử dụng lại, hãy nâng
cấp nó theo cách thủ công:
import { getAdvanceNonceAccountInstruction } from "@solana-program/system";// Submit this instruction (with a regular blockhash) to invalidate any pending transactiongetAdvanceNonceAccountInstruction({nonceAccount: nonceAddress,nonceAuthority});
Điều này tạo ra một giá trị nonce mới, làm cho bất kỳ giao dịch nào được ký bằng giá trị cũ trở nên vô hiệu vĩnh viễn.
Quy trình phê duyệt đa bên
Giải mã hóa giao dịch để thêm chữ ký bổ sung, sau đó mã hóa lại để lưu trữ hoặc gửi:
import {getBase64Decoder,getTransactionDecoder,getBase64EncodedWireTransaction,partiallySignTransaction} from "@solana/kit";// Deserialize the stored transactionconst txBytes = getBase64Decoder().decode(serializedString);const partiallySignedTx = getTransactionDecoder().decode(txBytes);// Each approver adds their signatureconst fullySignedTx = await partiallySignTransaction([newSigner],partiallySignedTx);// Serialize again for storage or submissionconst serialized = getBase64EncodedWireTransaction(fullySignedTx);
Giao dịch có thể được mã hóa, lưu trữ và chuyển giữa các bên phê duyệt. Sau khi thu thập đủ tất cả chữ ký cần thiết, hãy gửi lên mạng lưới.
Các cân nhắc khi triển khai
Quản lý tài khoản nonce:
- Tạo một nhóm các tài khoản nonce để chuẩn bị giao dịch song song
- Theo dõi các nonce đang "được sử dụng" (có giao dịch đã ký đang chờ xử lý)
- Triển khai tái sử dụng nonce sau khi giao dịch được gửi hoặc bị hủy bỏ
Bảo mật:
- Quyền hạn nonce kiểm soát việc giao dịch có thể bị vô hiệu hóa hay không. Hãy xem xét tách quyền hạn nonce khỏi người ký giao dịch để có thêm quyền kiểm soát và phân tách nhiệm vụ
- Bất kỳ ai có byte giao dịch được tuần tự hóa đều có thể gửi nó lên mạng
Tài nguyên liên quan
Is this page helpful?