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.
Jokaisen allekirjoituksen tuottaa yksityinen avain. Se, missä kyseinen avain sijaitsee — paikallinen keypair, pilvi-HSM tai KMS, tai hallittu lompakkopalvelu — on tuotantoympäristön suunnittelupäätös. Katso Allekirjoittaminen tuotannossa.
Taulukon ensimmäinen allekirjoitus kuuluu maksun maksajalle, tilille joka maksaa transaktion perusmaksun ja priorisointimaksun. Tämä ensimmäinen allekirjoitus toimii myös transaktion tunnuksena, jota käytetään transaktion hakemiseen verkosta. Transaktion tunnukseen viitataan yleisesti nimellä transaktioallekirjoitus.
Maksun maksajan vaatimukset:
- Täytyy olla ensimmäinen tili viestissä (indeksi 0) ja allekirjoittaja.
- Täytyy olla System Program -omistuksessa oleva tili tai nonce-tili
(vahvistetaan
validate_fee_payertoimesta). - Täytyy pitää hallussaan riittävästi lamport-yksiköitä kattaakseen
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 transaktion käskyjen vaatimista tilin osoitteistarecent_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
oikeusryhmiin:
num_required_signatures: Transaktion vaatimien allekirjoitusten kokonaismäärä.num_readonly_signed_accounts: Vain luku -tilassa olevien allekirjoitettujen tilien lukumäärä.num_readonly_unsigned_accounts: Vain luku -tilassa olevien allekirjoittamattomien tilien lukumäärä.
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,}
Perinteiset ja versioidut viestietuliitteet
Perinteisessä transaktioviestissä ensimmäinen viestin tavu on
num_required_signatures, jota seuraavat muut kaksi MessageHeader tavua.
Versioidussa transaktioviestissä ensimmäinen tavu on sen sijaan
versioprefiksi; kolmitavuinen MessageHeader alkaa heti tämän prefiksin
jälkeen. Katso versioidut
transaktiot täydellisen
v0-viestin asettelun osalta.
Kaavio, joka näyttää viestiotsikon kolme osaa
Tilin osoitteet
account_keys-kenttä
on kompaktikoodattu taulukko julkisista avaimista. Jokainen merkintä yksilöi
tilin, jota vähintään yksi transaktion ohjeista käyttää. Taulukon on
sisällettävä kaikki tilit ja sen on noudatettava tätä tiukkaa järjestystä:
- Allekirjoittaja + Kirjoitettava
- Allekirjoittaja + Vain luku
- Ei allekirjoittaja + Kirjoitettava
- Ei allekirjoittaja + Vain luku
Tämä tiukka järjestys mahdollistaa account_keys-taulukon yhdistämisen
viestin header-otsikon kolmeen lukumäärään, jolloin kunkin tilin
käyttöoikeudet voidaan määrittää tallentamatta tilikohtaisia metatietolippuja.
Otsikon lukumäärät jakavat taulukon neljään edellä mainittuun
käyttöoikeusryhmään.
Kaavio, joka näyttää tilitaulukon järjestyksen
Viimeisin lohkohajautusarvo
recent_blockhash-kenttä on 32-tavuinen hajautusarvo, jolla on kaksi
tarkoitusta:
- Aikaleima: todistaa, että transaktio on luotu äskettäin.
- Deduplikointi: estää saman transaktion käsittelyn kahdesti.
Lohkohajautusarvo vanhenee 150 slot:n jälkeen. Jos lohkohajautusarvo ei ole enää
voimassa transaktion saapuessa, se hylätään virhekoodilla
BlockhashNotFound, ellei kyseessä ole voimassa oleva
kestävä nonce-transaktio.
getLatestBlockhash RPC-metodi
mahdollistaa nykyisen lohkohajautusarvon ja viimeisen lohkokorkeuden
hakemisen, johon asti lohkohajautusarvo on voimassa.
Ohjeet
instructions-kenttä
on kompaktikoodattu taulukko
CompiledInstruction-rakenteita.
Jokainen CompiledInstruction viittaa tileihin indeksin kautta
account_keys-taulukkoon täydellisen julkisen avaimen sijaan. Se sisältää:
program_id_index: Indeksiaccount_keys-taulukkoon, joka yksilöi kutsuttavan ohjelman.accounts: Taulukko indekseistäaccount_keys-taulukkoon, jotka määrittelevät ohjelmalle välitettävät tilit.data: Taulukko tavuja, joka sisältää instruction discriminatorin ja serialisoidut 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 taulukko ohjeista
Transaktion binäärimuoto
Transaktiot serialisoidaan kompaktia koodausjärjestelmää käyttäen. Kaikkia muuttuvanpituisia taulukoita (allekirjoitukset, tilin avaimet, ohjeet) edeltää compact-u16-pituuskoodaus. Tämä muoto käyttää 1 tavua arvoille 0–127 ja 2–3 tavua suuremmille arvoille.
Vanhan transaktion rakenne (siirrettäessä):
| 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 tilin avainten määrä |
account_keys | num_account_keys x 32 tavua | Julkiset avaimet (pubkey) |
recent_blockhash | 32 tavua | Lohkohajautusarvo |
num_instructions | 1–3 tavua (compact-u16) | Ohjeiden määrä |
instructions | vaihteleva | Taulukko kootuista ohjeista |
Jokainen koottu ohje serialisoidaan seuraavasti:
| Kenttä | Koko | Kuvaus |
|---|---|---|
program_id_index | 1 tavu | Indeksi tilin avaimiin |
num_accounts | 1–3 tavua (compact-u16) | Tiliindeksien määrä |
account_indices | num_accounts x 1 tavu | Tilin avainindeksit |
data_len | 1–3 tavua (compact-u16) | instruction data -pituus |
data | data_len tavua | Läpinäkymätön instruction data |
Koon laskeminen
Koska 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-siirtotapahtuma
Alla oleva kaavio näyttää, kuinka tapahtumat ja käskyt toimivat yhdessä, jotta käyttäjät voivat olla vuorovaikutuksessa verkon kanssa. Tässä esimerkissä SOL siirretään yhdeltä tililtä toiselle.
Lähettäjätilin metatiedot osoittavat, että sen on allekirjoitettava tapahtuma. Tämä sallii System Program in vähentää lamport-yksiköitä. Sekä lähettäjän että vastaanottajan tilien on oltava kirjoitettavissa, jotta niiden lamport-saldo voi muuttua. Tämän käskyn suorittamiseksi lähettäjän lompakko lähettää tapahtuman, joka sisältää allekirjoituksen ja viestin, joka sisältää SOL-siirtokäskyn.
SOL-siirtokaavio
Tapahtuman lähettämisen jälkeen System Program käsittelee siirtokäskyn ja päivittää molempien tilien lamport-saldon.
SOL-siirtoprosessin kaavio
Tarkista vastaanottaja ennen SOL:n lähettämistä
System Program -siirto lisää lamport-yksiköitä mihin tahansa tilille. Protokollatasolla ei ole tarkistusta siitä, voiko vastaanottaja siirtää SOL:n takaisin ulos. lamport-yksiköitä voi siirtää ulos vain tilin omistavan ohjelman kautta, joten SOL:n lähettäminen token mintille, ohjelmalle tai PDA:lle, jota et hallitse, vaarantaa varojen pysyvän menetyksen — vain omistavan ohjelman määrittämä valtuutus voi palauttaa ne. SOL, joka lähetetään token account -tilille, on palautettavissa vain kyseisen tilin omistajan toimesta, ei koskaan lähettäjän toimesta.
SPL token -siirrot ovat osittain itsensä suojaavia: Token Program hylkää siirron, jonka tilit eivät vastaa odotettua minttiä. Natiiville SOL-siirroille ei ole tällaista suojausta, joten lähettäjän on varmistettava vastaanottaja ennen allekirjoittamista. Katso Osoitteen varmistaminen täydellisen luokittelulogiikan osalta.
Alla oleva esimerkki näyttää yllä oleviin kaavioihin liittyvän 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ää sellaisen transaktion rakenteen, joka sisältää yksittäisen SOL-siirtoinstruktion.
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. Muoto vaihtelee SDK:iden välillä, mutta huomaa, että jokainen instruktio 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 mihin tahansa tiliin, tarkista vastaanottaja ennen allekirjoittamista. Hae tili ja lähetä vain System Program -lompakkoon (tai rahoittamattomaan on-curve -osoitteeseen); hylkää mintit, token account -tilit, 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ä katkelma tarkistaa natiivit SOL-vastaanottajat. Täydellinen luokittelu, joka käsittelee myös SPL-tokenien lähetykset (token account -tilit, ATA:t, Token-2022), löytyy kohdasta Verify Address.
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 Explorer -palvelun avulla.
{"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"}
Raakavastaus tunnistaa tilit indeksin perusteella ja tallentaa sisäiset (CPI) ohjeet koodattuina blobeina. Näiden muuntamiseksi osoitteiksi ja koko ohjeiden puun läpikäymiseksi katso Tapahtuman tarkastelu.
Is this page helpful?