Yhteenveto
Tapahtuma sisältää allekirjoitukset + viestin. Viesti sisältää otsikon, tiliosoitteet, viimeaikaisen lohkohajautusarvon ja käännetyt ohjeet. Sarjallistetun koon maksimi: 1 232 tavua.
Transaction-rakenteella
on kaksi ylätason kenttää:
signatures: Allekirjoitusten taulukkomessage: Tapahtumatiedot, mukaan lukien luettelo käsiteltävistä ohjeista
pub struct Transaction {pub signatures: Vec<Signature>,pub message: Message,}
Kaavio, joka näyttää tapahtuman kaksi osaa
Tapahtuman sarjallistetun kokonaiskoon ei saa ylittää
PACKET_DATA_SIZE-arvoa
(1 232 tavua). Tämä rajoitus vastaa 1 280 tavua (IPv6:n vähimmäis-MTU) miinus 48
tavua verkon otsikoille (40 tavua IPv6 + 8 tavua fragmenttiotsikko). 1 232 tavua
sisältää sekä signatures-taulukon että
message-rakenteen.
Kaavio, joka näyttää tapahtumamuodon ja kokorajoitukset
Allekirjoitukset
signatures-kenttä on kompaktisti koodattu taulukko
Signature-arvoja.
Jokainen Signature on 64-tavuinen Ed25519-allekirjoitus sarjallistetusta
Message-rakenteesta, allekirjoitettu allekirjoittajatilin yksityisellä
avaimella. Yksi allekirjoitus vaaditaan jokaiselta
allekirjoittajatililtä, johon tapahtuman ohjeet
viittaavat.
Taulukon ensimmäinen allekirjoitus kuuluu maksajalle, tilille, joka maksaa tapahtuman perusmaksun ja priorisointimaksun. Tämä ensimmäinen allekirjoitus toimii myös tapahtumatunnisteena, jota käytetään tapahtuman hakemiseen verkosta. Tapahtumatunnistetta kutsutaan yleisesti tapahtuman allekirjoitukseksi.
Maksajan vaatimukset:
- Täytyy olla ensimmäinen tili viestissä (indeksi 0) ja allekirjoittaja.
- Täytyy olla System Program -omistuksessa oleva tili tai nonce-tili (validoitu
validate_fee_payer). - Täytyy sisältää riittävästi lamportteja kattamaan
rent_exempt_minimum + total_fee; muuten transaktio epäonnistuu virheelläInsufficientFundsForFee.
Viesti
message-kenttä on
Message
-rakenne, joka sisältää transaktion hyötykuorman:
header: Viestin otsikkoaccount_keys: Taulukko tiliosoitteista, joita transaktion käskyt vaativatrecent_blockhash: Lohkohajautusarvo, joka toimii transaktion aikaleimanainstructions: Taulukko käskyistä
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>,}
Otsikko
header-kenttä on
MessageHeader
-rakenne, jossa on kolme u8-kenttää, jotka jakavat account_keys-taulukon
käyttöoikeusryhmiin:
num_required_signatures: Transaktion vaatimien allekirjoitusten kokonaismäärä.num_readonly_signed_accounts: Allekirjoitettujen tilien määrä, jotka ovat vain luku -tilassa.num_readonly_unsigned_accounts: Allekirjoittamattomien tilien määrä, jotka ovat vain luku -tilassa.
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,}
Kaavio, joka näyttää viestin otsikon kolme osaa
Tiliosoitteet
account_keys
-kenttä on kompaktisti koodattu taulukko julkisista avaimista. Jokainen merkintä
tunnistaa tilin, jota vähintään yksi transaktion käskyistä käyttää. Taulukon
täytyy sisältää jokainen tili ja sen täytyy noudattaa tätä tarkkaa järjestystä:
- Allekirjoittaja + kirjoitettava
- Allekirjoittaja + vain luku
- Ei-allekirjoittaja + kirjoitettava
- Ei-allekirjoittaja + vain luku
Tämä tarkka järjestys mahdollistaa sen, että account_keys-taulukko voidaan
yhdistää viestin header-kentän kolmeen lukumäärään, jolloin
kunkin tilin käyttöoikeudet voidaan määrittää ilman tilikohtaisten
metatietolippujen tallentamista. Otsikon lukumäärät jakavat taulukon yllä
lueteltuihin neljään käyttöoikeusryhmään.
Kaavio, joka näyttää tilisoitteiden taulukon järjestyksen
Viimeaikainen lohkohajautusarvo
recent_blockhash-kenttä on 32-tavuinen hajautusarvo, joka palvelee kahta
tarkoitusta:
- Aikaleima: todistaa, että transaktio on luotu äskettäin.
- Deduplikointi: estää saman transaktion käsittelyn kahdesti.
Lohkohajautusarvo vanhenee 150 slotin jälkeen. Jos lohkohajautusarvo ei ole enää
voimassa transaktion saapuessa, se hylätään virheellä BlockhashNotFound,
ellei kyseessä ole voimassa oleva
kestävä nonce-transaktio.
getLatestBlockhash RPC-metodi
mahdollistaa nykyisen lohkohajautusarvon ja viimeisen lohkokorkeuden
hakemisen, jolla lohkohajautusarvo on voimassa.
Käskyt
instructions-kenttä
on kompaktisti koodattu taulukko
CompiledInstruction-rakenteita.
Jokainen CompiledInstruction viittaa tileihin indeksillä
account_keys-taulukkoon täyden julkisen avaimen sijaan. Se sisältää:
program_id_index: Indeksiaccount_keys-taulukkoon, joka tunnistaa kutsuttavan ohjelman.accounts: Taulukko indeksejäaccount_keys-taulukkoon, jotka määrittävät ohjelmalle välitettävät tilit.data: Tavutaulukko, joka sisältää käskyn erottelijan ja sarjallistetut argumentit.
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>,}
Kompakti käskyjen taulukko
Transaktion binäärimuoto
Transaktiot sarjallistetaan käyttäen kompaktia koodausjärjestelmää. Kaikki muuttuvan pituiset taulukot (allekirjoitukset, tiliavaimet, käskyt) alkavat compact-u16-pituuskoodauksella. Tämä muoto käyttää 1 tavua arvoille 0-127 ja 2-3 tavua suuremmille arvoille.
Legacy-transaktion rakenne (verkossa):
| Kenttä | Koko | Kuvaus |
|---|---|---|
num_signatures | 1-3 tavua (compact-u16) | Allekirjoitusten määrä |
signatures | num_signatures x 64 tavua | Ed25519-allekirjoitukset |
num_required_signatures | 1 tavu | MessageHeader-kenttä 1 |
num_readonly_signed | 1 tavu | MessageHeader-kenttä 2 |
num_readonly_unsigned | 1 tavu | MessageHeader-kenttä 3 |
num_account_keys | 1-3 tavua (compact-u16) | Staattisten tiliavainten määrä |
account_keys | num_account_keys x 32 tavua | Julkiset avaimet |
recent_blockhash | 32 tavua | Lohkohajautusarvo |
num_instructions | 1-3 tavua (compact-u16) | Käskyjen määrä |
instructions | vaihteleva | Taulukko käännettyjä käskyjä |
Jokainen käännetty ohje sarjallistetaan seuraavasti:
| Kenttä | Koko | Kuvaus |
|---|---|---|
program_id_index | 1 tavu | Indeksi tiliavaimiin |
num_accounts | 1-3 tavua (compact-u16) | Tili-indeksien määrä |
account_indices | num_accounts x 1 tavu | Tiliavainten indeksit |
data_len | 1-3 tavua (compact-u16) | Instruction datan pituus |
data | data_len tavua | Läpinäkymätön instruction data |
Koon laskeminen
Kun PACKET_DATA_SIZE = 1 232 tavua, käytettävissä oleva tila voidaan
laskea:
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
Esimerkki: SOL-siirtotransaktio
Alla oleva kaavio näyttää, miten transaktiot ja ohjeet toimivat yhdessä mahdollistaen käyttäjien vuorovaikutuksen verkon kanssa. Tässä esimerkissä SOL siirretään yhdeltä tililtä toiselle.
Lähettäjätilin metadata osoittaa, että sen on allekirjoitettava transaktio. Tämä mahdollistaa System Programin vähentää lamportteja. Sekä lähettäjän että vastaanottajan tilien on oltava kirjoitettavia, jotta niiden lamportti-saldo voi muuttua. Tämän ohjeen suorittamiseksi lähettäjän lompakko lähettää transaktion, joka sisältää sen allekirjoituksen ja viestin, joka sisältää SOL-siirto-ohjeen.
SOL-siirtokaavio
Kun transaktio on lähetetty, System Program käsittelee siirto-ohjeen ja päivittää molempien tilien lamportti-saldon.
SOL-siirtoprosessikaavio
Tarkista vastaanottaja ennen SOL:n lähettämistä
System Program -siirto lisää lamports-yksiköitä mihin tahansa tilille. Protokollatasolla ei ole tarkistusta sille, että vastaanottaja pystyy siirtämään SOL:n takaisin ulos. Lamports-yksiköitä voi siirtää ulos ainoastaan tilin omistava ohjelma, joten SOL:n lähettäminen token mint -osoitteeseen, ohjelmaan tai PDA:han, jota et hallitse, vaarantaa varojen pysyvän menetyksen — ainoastaan omistavan ohjelman määrittelemä auktoriteetti voi palauttaa ne. token account -tilille lähetetty SOL on palautettavissa ainoastaan kyseisen tilin omistajan toimesta, ei koskaan lähettäjän.
SPL token -siirrot ovat osittain itsensä suojaavia: Token Program hylkää siirron, jonka tilit eivät vastaa odotettua minttausta. Natiiveilla SOL-siirroilla ei ole tällaista suojaa, joten lähettäjän täytyy tarkistaa vastaanottaja ennen allekirjoittamista. Katso Verify Address täydellisen luokittelulogiikan osalta.
Alla oleva esimerkki näyttää yllä olevien kaavioiden kannalta oleellisen koodin.
Katso System Program:n
transfer-funktio.
import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { systemProgram } from "@solana-program/system";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n))).use(systemProgram());const sender = client.payer;const recipient = await generateKeyPairSigner();const LAMPORTS_PER_SOL = 1_000_000_000n;const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL// Check balance before transferconst { value: preBalance1 } = await client.rpc.getBalance(sender.address).send();const { value: preBalance2 } = await client.rpc.getBalance(recipient.address).send();// Create a transfer instruction for transferring SOL from sender to recipientconst transferInstruction = client.system.instructions.transferSol({source: sender,destination: recipient.address,amount: transferAmount // 0.01 SOL in lamports});const transactionSignature = await client.sendTransaction([transferInstruction]);// Check balance after transferconst { value: postBalance1 } = await client.rpc.getBalance(sender.address).send();const { value: postBalance2 } = await client.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.context.signature);
Seuraava esimerkki näyttää yhden SOL-siirtokäskyn sisältävän transaktion rakenteen.
import {createClient,generateKeyPairSigner,lamports,createTransactionMessage,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,appendTransactionMessageInstructions,pipe,signTransactionMessageWithSigners,getCompiledTransactionMessageDecoder} from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { systemProgram } from "@solana-program/system";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n))).use(systemProgram());const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();const sender = client.payer;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 = client.system.instructions.transferSol({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));
Alla oleva koodi näyttää edellisten koodinäytteiden tulosteen. Formaatti vaihtelee SDK:iden välillä, mutta huomaa, että jokainen käsky sisältää samat vaaditut tiedot.
{"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}}]}
Tarkista vastaanottaja ennen siirtoa
Koska SOL-siirto onnistuu mille tahansa tilille, tarkista vastaanottaja ennen allekirjoittamista. Hae tilitiedot ja lähetä vain System Program -lompakkoon (tai rahoittamattomaan käyrällä olevaan osoitteeseen); hylkää mintit, token accountit, ohjelmat ja PDA:t, joita et hallitse.
import {type Address,createSolanaRpc,fetchJsonParsedAccount,isOffCurveAddress} from "@solana/kit";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const SYSTEM_PROGRAM = "11111111111111111111111111111111" as Address;/*** Throws if `recipient` cannot safely receive native SOL.** Only System Program wallets (or unfunded on-curve addresses) are safe. Any* other account locks the lamports because no authority can debit them.*/async function assertSafeSolRecipient(recipient: Address): Promise<void> {const account = await fetchJsonParsedAccount(rpc, recipient);if (!account.exists) {// Off-curve = a PDA with no account; reject conservatively.if (isOffCurveAddress(recipient)) {throw new Error("Recipient is a PDA with no account; SOL would be locked");}// On-curve = an unfunded wallet, safe to fund.return;}if (account.programAddress !== SYSTEM_PROGRAM) {throw new Error(`Recipient is owned by ${account.programAddress}, not a wallet; SOL would be locked`);}}// A wallet: safe.await assertSafeSolRecipient("H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" as Address);// The USDC mint: rejected before any SOL leaves the sender.await assertSafeSolRecipient("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" as Address);
Tämä koodinpätkä tarkistaa natiivin SOL-vastaanottajat. Täydellinen luokittelu, joka käsittelee myös SPL-tokenien lähetyksen (token account, ATA:t, Token-2022), löytyy täältä: Vahvista osoite.
Tapahtuman tietojen hakeminen
Lähetyksen jälkeen voit hakea tapahtuman tiedot käyttämällä tapahtuman allekirjoitusta ja getTransaction-RPC-metodia.
Voit myös etsiä tapahtuman Solana Explorerista.
{"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?