Yhteenveto
Solanassa on kolme transaktioformaattia: legacy, v0 ja v1. v0 lisää Address Lookup Tables (ALT) -taulut, joiden avulla tileihin viitataan 1-tavuisilla indekseillä. v1 nostaa kokorajaksi 4 096 tavua, siirtää resurssirajat itse viestiin ja poistaa ALT-tuen.
Solana tukee kolmea transaktioformaattia: legacy, v0 ja v1. Jokainen näistä kuvataan alla samalla kolmiosaisella rakenteella: miten tavut järjestetään siirtomuodossa, miten tileihin viitataan ja mistä resurssirajat tulevat.
v1-aktivointitila
V1-formaatti on käytössä mainnetissa, devnetissä ja testnetissä. Transaktioiden lukeminen on rikkova muutos: asiakkaat, jotka eivät ota sitä käyttöön, epäonnistuvat v1-transaktioiden kanssa. Olemassa olevien sovellusten kannattaa tutustua kohtaan v1:een valmistautuminen.
Formaattien vertailu
| Raja | legacy | v0 | v1 |
|---|---|---|---|
| Transaktion enimmäiskoko | 1 232 tavua | 1 232 tavua | 4 096 tavua |
| Tiliosoitteet | ~32, kokorajoitettu | 64, hakutaulujen kautta | 64, suoraan (96 SIMD-0596:n kanssa) |
| Osoitehakutaulut | ei tuettu | tuettu | ei tuettu |
| Resurssirajat | ComputeBudget-ohjeet | ComputeBudget-ohjeet | viestiasetus |
Legacy-formaatti
Alkuperäinen formaatti, joka on edelleen useimpien työkalujen oletusarvo. Siinä ei ole lainkaan versioliitettä: transaktion ensimmäinen tavu on allekirjoitustaulukon compact-u16-lukumäärä ja viestin ensimmäinen tavu on num_required_signatures, jonka korkein bitti on aina nollattuna.
Legacy-siirtomuoto
| Kenttä | Koko | Kuvaus |
|---|---|---|
num_signatures | compact-u16 | Allekirjoitusten lukumäärä |
signatures | num_signatures × 64 tavua | Ed25519-allekirjoitukset |
header | 3 tavua | MessageHeader — ensimmäisessä tavussa versiobittiä ei ole asetettu |
num_account_keys | compact-u16 | Tiliavainten lukumäärä |
account_keys | num_account_keys × 32 tavua | Julkiset avaimet, kaikki suoraan |
recent_blockhash | 32 tavua | Elinajan määrittäjä |
num_instructions | compact-u16 | Ohjeiden lukumäärä |
instructions | vaihteleva | Jokainen ohje sarjallistettuna peräkkäin |
Jokainen muuttuvanpituinen taulukko alkaa compact-u16-pituudella: 1 tavu arvoille 0–127, 2–3 tavua suuremmille arvoille. Ohjekohtaisen rakenteen ja koon laskentaesimerkin löydät transaktion binäärimuodosta.
Tilit legacy-formaatissa
Jokainen tili kirjoitetaan account_keys-kohtaan täydellisenä 32-tavuisena julkisena avaimena, ja ohjeet viittaavat niihin 1-tavuisella indeksillä kyseiseen taulukkoon. Tiliä ei voi viitata, ellei sitä ole kirjoitettu transaktioon, mikä rajoittaa legacy-transaktion noin 32 tiliin ennen kuin 1 232 tavun raja täyttyy.
Resurssirajat legacy-formaatissa
Laskentayksikkörajaa, ladattujen tilitietojen kokorajaa, keon kokoa ja prioriteettimaksua pyydetään sisällyttämällä ComputeBudget-ohjelmaohjeita transaktioon. Jokainen niistä kuluttaa yhden ohjauspaikan ja 150 laskentayksikköä. Niiden pois jättäminen on turvallista: ajonaikainen ympäristö käyttää oletusarvoja — 200 000 laskentayksikköä per ohje (enintään 1,4 M), 64 MiB:n datatietoraja, 32 KiB:n keko ja nolla prioriteettimaksua.
V0-formaatti
v0 on legacy-viesti lisättynä kahdella asialla: 0x80-versioliitetavulla ja
address_table_lookups-taulukolla, joka lisätään ohjeiden jälkeen. Kaikki sitä edeltävä on tavutasolla identtinen legacyn kanssa.
V0-siirtomuoto
| Kenttä | Koko | Kuvaus |
|---|---|---|
num_signatures | compact-u16 | Allekirjoitusten lukumäärä |
signatures | num_signatures × 64 tavua | Ed25519-allekirjoitukset |
0x80 | 1 tavu | Versioliitetavu — viestin ensimmäinen tavu |
header | 3 tavua | MessageHeader (sama kuin perinteinen) |
num_account_keys | compact-u16 | Staattisten tiliavainten lukumäärä |
static_account_keys | num_account_keys × 32 tavua | Avaimet, jotka esiintyvät kirjaimellisesti transaktiossa |
recent_blockhash | 32 tavua | Elinajan määrittäjä |
num_instructions | compact-u16 | Ohjeiden lukumäärä |
instructions | vaihteleva | Sama muoto kuin perinteinen |
address_table_lookups | compact-u16 + vaihteleva | ALT-viittaukset (katso alla) |
Jokainen osoitetaulukon hakumerkintä sisältää:
| Kenttä | Koko | Kuvaus |
|---|---|---|
account_key | 32 tavua | ALT-tilin julkinen avain |
writable_indexes | compact-u16 + N x 1 tavu | Indeksit ALT:iin kirjoitettaville tileille |
readonly_indexes | compact-u16 + N x 1 tavu | Indeksit ALT:iin vain luku -oikeudella oleville tileille |
Osoitehakutaulut
ALT on ketjussa oleva tili, joka tallentaa enintään 256 julkistaavainta. Viittaamalla ALT:iin, transaktio voi sisältyttää lisätilejä käyttäen 1-tavuisia indeksejä 32-tavuisten julkisten avainten sijaan, mikä vähentää merkittävästi tilikohtaista kuormitusta.
Ajonaikana, ennen suorituksen alkamista, validaattori selvittää kaikki ALT-viittaukset täysiksi julkisiksi avaimiksi. Selvitetyt osoitteet liitetään staattisten tiliavainten perään muodostaen täydellisen tiliavainten listan. ALT:n kautta selvitetyt tilit noudattavat samaa järjestystä kuin staattiset tilit: kirjoitettavat haut tulevat ennen vain luku -oikeudella olevia hakuja.
Osoitetaulukot vaikuttavat vain siihen, miten tileihin viitataan verkossa välitettävässä transaktiossa. Suoritusaikana runtime selvittää kaikki indeksit täysiksi tiliosoitteiksi. ALT:n kautta selvitetyt tilit voivat olla vain kirjoitettavia tai vain luku -oikeudella olevia (ei-allekirjoittajia); ne eivät voi olla allekirjoittajia.
Osoitehakutaulun luominen ja käyttö
Tämä esimerkki luo hakutaulun, lisää siihen osoitteen ja käyttää taulua v0-transaktion kokoamiseen ja lähettämiseen.
Hakutaulusta voi ladata vain muita kuin allekirjoittajatilejä. Siirtomaksun maksaja ja jokainen vaadittu allekirjoittaja pysyvät viestin staattisessa tililuettelossa. Osoitetta ei myöskään voi hakea samassa slot-yksikössä, jossa se lisättiin, joten esimerkki odottaa seuraavaa vahvistettua slot-yksikköä ennen v0-viestin kokoamista.
Resurssirajat v0:ssa
Sama kuin legacyssä: ComputeBudget-ohjeet, samoilla oletusarvoilla kun ne jätetään pois.
V1-formaatti
v1 nostaa kokorajaksi 4 096 tavua ja rakentaa viestin uudelleen transaktioasetusten ympärille: resurssirajat siirtyvät ComputeBudget-ohjeista viestin omiin kiinteäpaikkaisiin kenttiin. Tämä mahdollistaa verkolle transaktion prioriteetin luokittelun yhdellä kiinteän siirtymän lukuoperaatiolla sen sijaan, että ohjeluettelo pitäisi käydä läpi ja purkaa sarjallistuksesta.
V1-siirtomuoto
| Kenttä | Koko | Kuvaus |
|---|---|---|
0x81 | 1 tavu | Versioliitetavu — transaktion ensimmäinen tavu |
header | 3 tavua | MessageHeader (sama kuin perinteinen) |
config_mask | 4 tavua | u32 LE -bittipeite, joka merkitsee läsnä olevat asetusarvot |
recent_blockhash | 32 tavua | Elinajan määrittäjä |
num_instructions | 1 tavu | Kiinteälevyinen laskuri, enintään 64 |
num_addresses | 1 tavu | Kiinteälevyinen laskuri, enintään 64 (96 SIMD-0596:n kanssa) |
addresses | N × 32 tavua | Tiliosoitteet, kaikki suoraan — ei hakutauluviittauksia |
config_values | 0–20 tavua | Yksi arvo per asetettu bitti, bittijärjestyksessä (ks. alla) |
instruction_headers | N × 4 tavua | Per ohje: program_id_index (u8), num_accounts (u8), data_len (u16 LE) |
instruction_payloads | vaihteleva | Per ohje: tili-indeksit, sitten instruction data |
signatures | N × 64 tavua | Lopussa ilman pituusliitettä — lukumäärä saadaan headerista |
Kaksi rakenteellista eroa legacyyn ja v0:aan nähden on syytä huomioida dekooderia kirjoitettaessa. Laskurit ovat kiinteälevyisiä u8-kenttiä compact-u16:n sijaan, ja ohjeet on jaettu kahteen jaksoon: ensin kaikki kiinteäkokoiset headerit, sitten kaikki vaihtuvanpituiset hyötykuormat — sen sijaan että jokainen ohje olisi peräkkäinen kokonaisuus.
Tilit v1:ssä: ei osoitehakutauluja
v1 poistaa ALT-tuen tarkoituksella. 64 raakaosoitetta on 2 048 tavua, ja SIMD-0596:n jälkeen sallitut 96 osoitetta ovat 3 072 tavua, molemmat selvästi 4 096 tavun rajan sisällä, joten jokainen osoite on suoraan viestissä kuten legacy-formaatissa. Jos sovelluksesi käyttää hakutaulut, siirtyminen v1:een tarkoittaa näiden osoitteiden sisällyttämistä suoraan viestiin.
Resurssirajat v1:ssä: transaktioasetukset
Asetukset koostuvat u32-bittipeitteestä, jota seuraa kiinteälevyiset arvot kullekin kentälle, jonka bitti on asetettu:
| Bitti(t) | Kenttä | Leveys | Huomiot |
|---|---|---|---|
| 0–1 | Prioriteettimaksu | u64 | lamport-kokonaissumma — molemmat bitit asetetaan yhdessä |
| 2 | Laskentayksiköraja | u32 | |
| 3 | Ladattujen tilitietojen kokoraja | u32 | |
| 4 | Pyydetty keon koko | u32 |
Tuntemattomat bitit hylätään. Koska viesti on allekirjoitettu, tunnistamattomia asetuskenttiä ei voida hiljaisesti pudottaa pois.
Prioriteettimaksu on lamport-kokonaissumma, ei yksikköhinta
Legacy- ja v0-formaateissa prioriteettimaksu asetetaan SetComputeUnitPrice-komennolla
mikro-lamporteina laskentayksikköä kohti, kerrottuna laskentayksikkörajalla. V1:ssä se on
absoluuttinen kokonaissumma lamporteissa — ei kertolaskua, ei pyöristystä.
Älä käytä yksikkökohtaista laskukaavaa. Konaismaksukaava on muuten muuttumaton:
(allekirjoitukset × lamports_per_signature) + priority_fee.
ComputeBudget-ohjeet ovat no-op-toimintoja v1:ssä
V1-transaktio ei hylkää ComputeBudget-ohjeita — se ohittaa ne asetusten osalta. Ne suoritetaan edelleen onnistuneina no-op-toimintoina kuluttaen 150 laskentayksikköä ja yhden 64:stä ohjausvälistä ilman vaikutusta budjettiin. Poista ne v1-transaktioita rakentaessasi ja lopeta niiden etsiminen v1-transaktioita lukiessasi: arvot sijaitsevat viestiasetusosiossa.
Asetuskentät on asetettava erikseen
Tärkein käyttäytymismuutos lähettäjille: toisin kuin legacy- ja v0-formaateissa, v1-transaktioissa on asetettava laskentayksiköraja ja ladattujen tilitietojen kokoraja erikseen, muuten transaktio epäonnistuu.
| Asettamaton kenttä | legacy / v0 | v1 | Oire puuttuessa |
|---|---|---|---|
| Laskentayksiköraja | 200k per ohje, enintään 1,4M | 0 laskentayksikköä | Epäonnistuu välittömästi, budjetti ylitetty |
| Ladattujen tilitietojen koko | 64 MiB | 0 tavua | MaxLoadedAccountsDataSizeExceeded ensimmäisellä ladatulla tilillä |
| Prioriteettimaksu | 0 | 0 | — |
| Keon koko | 32 KiB | 32 KiB | — |
Suositeltava lähestymistapa on simuloida kerran molemmilla rajoilla maksimoituina, kirjoittaa sitten palautetut unitsConsumed- ja loadedAccountsDataSize-arvot asetuksiin pyöristäen datatietojen koko ylöspäin seuraavaan 32 KiB:n sivuun pelivaraa varten (lohkon kustannusmalli laskee 32 KiB:n sivuissa, joten varaa seuraavaan sivurajaan asti ei lasketa).
V1-transaktion rakentaminen
V1-viesti koostetaan saman putken kautta kuin legacy- tai v0-viesti.
Ero on setTransactionMessageConfig-funktiossa: resurssirajat ovat viestin
ominaisuus, joten ohjeluettelo sisältää vain ohjelmaohjeet.
import {appendTransactionMessageInstruction,assertIsTransactionWithBlockhashLifetime,createClient,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageConfig,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners,} from "@solana/kit";import { rpcAirdrop, solanaRpc } from "@solana/kit-plugin-rpc";import { airdropPayer, payer } from "@solana/kit-plugin-signer";import { getTransferSolInstruction } from "@solana-program/system";const sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();const client = await createClient().use(payer(sender)).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900",}),).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();// A v1 message is assembled through the same pipeline as a legacy or v0 one.// The difference is the config: a v1 transaction carries its resource limits in// the message itself, so no ComputeBudget instructions are compiled in.const transactionMessage = pipe(createTransactionMessage({ version: 1 }),(m) => setTransactionMessageFeePayerSigner(sender, m),(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),(m) =>appendTransactionMessageInstruction(getTransferSolInstruction({source: sender,destination: recipient.address,amount: lamports(10_000_000n),}),m,),// The compute unit limit and loaded accounts data size limit both default to// zero on v1, so a transaction that leaves them unset always fails. The// priority fee is an absolute total in lamports, not a price per compute// unit.(m) =>setTransactionMessageConfig({computeUnitLimit: 20_000,loadedAccountsDataSizeLimit: 64 * 1024,priorityFeeLamports: 5_000n,},m,),);const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);assertIsTransactionWithBlockhashLifetime(signedTransaction);await sendAndConfirmTransactionFactory({rpc: client.rpc,rpcSubscriptions: client.rpcSubscriptions,})(signedTransaction, { commitment: "confirmed" });const transactionSignature = getSignatureFromTransaction(signedTransaction);console.log("Transaction Signature:", transactionSignature);
Kit tarjoaa myös erillisen asetusfunktion jokaiselle asetuskentälle —
setTransactionMessagePriorityFeeLamports,
setTransactionMessageComputeUnitLimit,
setTransactionMessageLoadedAccountsDataSizeLimit ja
setTransactionMessageHeapSize — joilla ne voi asettaa yksi kerrallaan.
Rajojen mitoittaminen simuloinnilla
Älä arvaa kahta pakollista rajaa, vaan mittaa ne. Simulointi ajetaan siten, että molemmat rajat on nostettu ajonaikaiseen enimmäisarvoonsa, joten se ei voi epäonnistua mitattavien resurssien puutteen vuoksi. Palautettu luku on yhden simuloidun ajon tarkka kustannus, joten lisää siihen marginaali ennen kuin luotat siihen.
import {appendTransactionMessageInstruction,assertIsTransactionWithBlockhashLifetime,createClient,createTransactionMessage,estimateAndSetResourceLimitsFactory,estimateResourceLimitsFactory,fillTransactionMessageProvisoryResourceLimits,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,setTransactionMessagePriorityFeeLamports,signTransactionMessageWithSigners,} from "@solana/kit";import { rpcAirdrop, solanaRpc } from "@solana/kit-plugin-rpc";import { airdropPayer, payer } from "@solana/kit-plugin-signer";import { getTransferSolInstruction } from "@solana-program/system";const sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();const client = await createClient().use(payer(sender)).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900",}),).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const { value: latestBlockhash } = await client.rpc.getLatestBlockhash().send();// The priority fee is a pricing decision that simulation cannot measure, so it// is set directly. The two resource limits are measured below instead of// guessed. The provisory fill writes placeholder limits so the message// simulates at the same size it will be sent at.const draftMessage = pipe(createTransactionMessage({ version: 1 }),(m) => setTransactionMessageFeePayerSigner(sender, m),(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),(m) =>appendTransactionMessageInstruction(getTransferSolInstruction({source: sender,destination: recipient.address,amount: lamports(10_000_000n),}),m,),(m) => setTransactionMessagePriorityFeeLamports(5_000n, m),fillTransactionMessageProvisoryResourceLimits,);// Simulation runs with both limits raised to the runtime maximum, so it cannot// fail for want of the resources it is measuring.const estimateAndSetResourceLimits = estimateAndSetResourceLimitsFactory(estimateResourceLimitsFactory({ rpc: client.rpc }),);const transactionMessage = await estimateAndSetResourceLimits(draftMessage, {commitment: "confirmed",});console.log("Compute unit limit:", transactionMessage.config?.computeUnitLimit);console.log("Loaded accounts data size limit:",transactionMessage.config?.loadedAccountsDataSizeLimit,);const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);assertIsTransactionWithBlockhashLifetime(signedTransaction);await sendAndConfirmTransactionFactory({rpc: client.rpc,rpcSubscriptions: client.rpcSubscriptions,})(signedTransaction, { commitment: "confirmed" });// The estimate is the exact cost of one simulated run, with nothing to spare.// Add a margin before relying on it in production.console.log("Transaction Signature:",getSignatureFromTransaction(signedTransaction),);
V1:een valmistautuminen
v1 muuttaa transaktioiden lukemista, ei vain niiden lähettämistä. Jokainen asiakas, joka
kutsuu getTransaction- tai getBlock-funktiota ilman käyttöönottoa, epäonnistuu v1-transaktioiden kanssa:
- Välitä
maxSupportedTransactionVersion: 1— JSON-kokonaisluku1, ei merkkijono"1"— kohteillegetTransactionjagetBlock. Arvon0välittäminen epäonnistuu v1-transaktioilla täsmälleen samoin kuin parametrin pois jättäminen, joten v0-käyttöönoton yhteydessä päivitetty koodipohja tarvitsee silti arvon muuttamisen. getTransactionepäonnistuu v1-transaktiolla virheellä-32015, ja yksikin v1- transaktio epäonnistuttaa kokogetBlock-vastauksen samalla virheellä — osittaista tulosta ei ole.blockSubscribelähettääblock: nullja lakkaa etenemästä, joten kuluttaja, joka tulkitsee sen tyhjäksi lohkoksi, jää hiljaisesti jälkeen ensimmäisestä v1 slot:sta alkaen.getSignaturesForAddressei koskaan tutki transaktioiden sisältöä, joten v1-allekirjoitukset näkyvät normaalisti.- Käyttöönotetut vastaukset sisältävät v1-transaktioiden viestissä
transactionConfig-objektin (legacy- ja v0-transaktioissa sitä ei ole lainkaan). Putket, jotka johtavat prioriteettimaksut tai laskentarajat etsimällä ComputeBudget-ohjeita, raportoivat jokaisen v1-transaktion kohdalla hiljaa arvoksi nolla. - Käytä
encoding: 'base64'dekoodatessasi transaktioita asiakaspuolella sekäsendTransaction-/simulateTransaction-kutsuissa yli 1 232 tavun transaktioille — base58-koodaus on edelleen rajattu vanhaan kokoon. - Asiakaskirjastotuki edellyttää tuoreita versioita:
@solana/kit8.0+, Agave 4.2.x-sukupolven Rust-paketit tai web3.js v3. web3.js v1 lukee v1:tä versiosta 1.99.0 eteenpäin, mutta ei pysty rakentamaan tai lähettämään sitä.
V1-transaktion lukeminen
Käyttöönotto tapahtuu JSON-kokonaisluvulla 1. Palautettujen tavujen dekoodaus antaa
version ja asetukset, joissa resurssirajat sijaitsevat — ohjeluettelon läpikäynti
ComputeBudget-ohjeiden etsimiseksi ei löydä mitään.
import {decompileTransactionMessage,getBase64Encoder,getCompiledTransactionMessageDecoder,getTransactionDecoder,} from "@solana/kit";// Reading a v1 transaction requires opting in. The integer 1 is mandatory:// omitting the parameter, or passing 0, fails on a v1 transaction rather than// returning it in a degraded form.const fetched = await client.rpc.getTransaction(transactionSignature, {commitment: "confirmed",encoding: "base64",maxSupportedTransactionVersion: 1,}).send();if (fetched === null) {throw new Error("the transaction just sent was not found");}// The compiled message stores the config as a bitmask plus a positional list,// so it is decompiled to address the fields by name. Decompiling fetches no// accounts, because v1 does not support address lookup tables.const wireTransaction = getBase64Encoder().encode(fetched.transaction[0]);const transaction = getTransactionDecoder().decode(wireTransaction);const compiledMessage = getCompiledTransactionMessageDecoder().decode(transaction.messageBytes,);const message = decompileTransactionMessage(compiledMessage);console.log("Version:", message.version);if ("config" in message) {// Scanning the instruction list for ComputeBudget instructions finds nothing// here — on v1 the limits live in the config.console.log("Compute unit limit:", message.config?.computeUnitLimit);console.log("Priority fee (lamports):", message.config?.priorityFeeLamports);}
Täydellisen siirto-oppaan — mukaan lukien asiakaskirjastojen tuki, streamauksen (Geyser/gRPC) versiontunnistus ja simuloinnin toiminta — löydät Transaction Format v1 -päivityssivulta. Suoritettavia esimerkkejä TypeScriptillä, Rustilla, Pythonilla ja Golla löytyy osoitteesta solana-foundation/transaction-v1-examples.
Is this page helpful?