Transaktiot ja ohjeet

Solanassa käyttäjät lähettävät transaktioita vuorovaikuttaakseen verkon kanssa. Transaktiot sisältävät yhden tai useamman ohjeen, jotka määrittelevät suoritettavat toiminnot. Ohjeiden suorituslogiikka on tallennettu Solana-verkkoon käyttöönotettuihin ohjelmiin, joissa kukin ohjelma määrittelee omat ohjeensa.

Alla on keskeisiä tietoja Solanan transaktioiden käsittelystä:

  • Jos transaktio sisältää useita ohjeita, ohjeet suoritetaan siinä järjestyksessä, jossa ne on lisätty transaktioon.
  • Transaktiot ovat "atomisia" - kaikkien ohjeiden on käsiteltävä onnistuneesti, tai koko transaktio epäonnistuu eikä muutoksia tapahdu.

Transaktio on olennaisesti pyyntö käsitellä yksi tai useampi ohje. Voit ajatella transaktiota kirjekuorena, joka sisältää lomakkeita. Jokainen lomake on ohje, joka kertoo verkolle mitä tehdä. Transaktion lähettäminen on kuin kirjekuoren postittaminen lomakkeiden käsittelyä varten.

Transaktio yksinkertaistettunaTransaktio yksinkertaistettuna

Avainasiat

  • Solana-transaktiot sisältävät ohjeita, jotka kutsuvat verkossa olevia ohjelmia.
  • Transaktiot ovat atomisia - jos jokin ohje epäonnistuu, koko transaktio epäonnistuu eikä muutoksia tapahdu.
  • Transaktion ohjeet suoritetaan peräkkäisessä järjestyksessä.
  • Transaktion kokorajoitus on 1232 tavua.
  • Jokainen ohje vaatii kolme tietoa:
    1. Kutsuttavan ohjelman osoite
    2. Tilit, joista ohje lukee tai joihin se kirjoittaa
    3. Ohjeen mahdollisesti tarvitsemat lisätiedot (esim. funktioargumentit)

SOL-siirtoesimerkki

Alla oleva kaavio esittää transaktiota, jossa on yksi ohje SOLin siirtämiseksi lähettäjältä vastaanottajalle.

Solanassa "lompakot" ovat tilejä, jotka omistaa System Program. Vain ohjelman omistaja voi muuttaa tilin tietoja, joten SOLin siirtäminen vaatii transaktion lähettämistä System Programille.

SOL-siirtoSOL-siirto

Lähettäjätilin on allekirjoitettava (is_signer) transaktio, jotta System Program voi vähentää sen lamport-saldoa. Lähettäjä- ja vastaanottajatilien on oltava kirjoitettavia (is_writable), koska niiden lamport-saldot muuttuvat.

Transaktion lähettämisen jälkeen System Program käsittelee siirto-ohjeen. System Program päivittää sitten sekä lähettäjän että vastaanottajan tilien lamport-saldot.

SOL-siirtoprosessiSOL-siirtoprosessi

Alla olevat esimerkit näyttävät, miten lähetetään transaktio, joka siirtää SOLia yhdeltä tililtä toiselle. Katso System Programin siirto-ohjeen lähdekoodi täältä.

import {
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
// Create a connection to cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
const LAMPORTS_PER_SOL = 1_000_000_000n;
const transferAmount = lamports(LAMPORTS_PER_SOL / 100n); // 0.01 SOL
// Fund sender with airdrop
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: sender.address,
lamports: lamports(LAMPORTS_PER_SOL), // 1 SOL
commitment: "confirmed"
});
// Check balance before transfer
const { value: preBalance1 } = await rpc.getBalance(sender.address).send();
const { value: preBalance2 } = await rpc.getBalance(recipient.address).send();
// Create a transfer instruction for transferring SOL from sender to recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount // 0.01 SOL in lamports
});
// Add the transfer instruction to a new transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
// Send the transaction to the network
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
const transactionSignature = getSignatureFromTransaction(signedTransaction);
// Check balance after transfer
const { value: postBalance1 } = await rpc.getBalance(sender.address).send();
const { value: postBalance2 } = await 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);
Console
Click to execute the code.

Asiakaskirjastot usein abstrahoivat ohjelmaohjeiden rakentamisen yksityiskohdat. Jos kirjastoa ei ole saatavilla, voit rakentaa ohjeen manuaalisesti. Tämä edellyttää, että tunnet ohjeen toteutuksen yksityiskohdat.

Alla olevat esimerkit näyttävät, miten siirto-ohje rakennetaan manuaalisesti. Expanded Instruction -välilehti on toiminnallisesti vastaava kuin Instruction -välilehti.

const transferAmount = 0.01; // 0.01 SOL
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount * LAMPORTS_PER_SOL
});

Seuraavissa osioissa käymme läpi transaktioiden ja ohjeiden yksityiskohdat.

Ohjeet

Solanan ohjelman käskyä voidaan ajatella julkisena funktiona, jota kuka tahansa Solana-verkon käyttäjä voi kutsua.

Voit ajatella Solana-ohjelmaa verkkopalvelimena, jota ylläpidetään Solana-verkossa, jossa jokainen käsky on kuin julkinen API-päätepiste, jota käyttäjät voivat kutsua suorittaakseen tiettyjä toimintoja. Käskyn kutsuminen on samankaltaista kuin POST -pyynnön lähettäminen API-päätepisteeseen, mikä mahdollistaa käyttäjille ohjelman liiketoimintalogiikan suorittamisen.

Kutsuaksesi ohjelman käskyä Solanassa, sinun täytyy rakentaa Instruction kolmella tietoelementillä:

  • Ohjelmatunnus: Sen ohjelman osoite, joka sisältää kutsuttavan käskyn liiketoimintalogiikan.
  • Tilit: Luettelo kaikista tileistä, joista käsky lukee tai joihin se kirjoittaa.
  • Instruction data: Tavutaulukko, joka määrittää, mikä käsky ohjelmassa kutsutaan ja mitä argumentteja käsky vaatii.
Instruction
pub struct Instruction {
/// Pubkey of the program that executes this instruction.
pub program_id: Pubkey,
/// Metadata describing accounts that should be passed to the program.
pub accounts: Vec<AccountMeta>,
/// Opaque data passed to the program for its own interpretation.
pub data: Vec<u8>,
}

Tapahtuman käskyTapahtuman käsky

AccountMeta

Kun luot Instruction:n, sinun täytyy tarjota jokainen vaadittu tili AccountMeta -muodossa. AccountMeta määrittää seuraavat:

  • pubkey: Tilin osoite
  • is_signer: Täytyykö tilin allekirjoittaa tapahtuma
  • is_writable: Muokkaako käsky tilin tietoja
AccountMeta
pub struct AccountMeta {
/// An account's public key.
pub pubkey: Pubkey,
/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.
pub is_signer: bool,
/// True if the account data or metadata may be mutated during program execution.
pub is_writable: bool,
}

Määrittämällä etukäteen, mitä tilejä käsky lukee tai kirjoittaa, tapahtumat, jotka eivät muokkaa samoja tilejä, voidaan suorittaa rinnakkain.

Tietääksesi, mitä tilejä käsky vaatii, mukaan lukien mitkä täytyy olla kirjoitettavissa, vain luettavissa tai allekirjoittaa tapahtuma, sinun täytyy tutustua käskyn toteutukseen sellaisena kuin ohjelma sen määrittelee.

Käytännössä sinun ei yleensä tarvitse rakentaa Instruction-objektia manuaalisesti. Useimmat ohjelmakehittäjät tarjoavat asiakaskirjastoja, joissa on apufunktioita, jotka luovat ohjeet puolestasi.

AccountMetaAccountMeta

Esimerkki ohjeen rakenteesta

Suorita alla olevat esimerkit nähdäksesi SOL-siirto-ohjeen rakenteen.

import { generateKeyPairSigner, lamports } from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
console.log(JSON.stringify(transferInstruction, null, 2));
Console
Click to execute the code.

Seuraavat esimerkit näyttävät edellisten koodinpätkien tulosteen. Tarkka muoto vaihtelee SDK:n mukaan, mutta jokainen Solana-ohje vaatii seuraavat tiedot:

  • Ohjelman tunnus: Ohjelman osoite, joka suorittaa ohjeen.
  • Tilit: Luettelo ohjeen vaatimista tileistä. Jokaiselle tilille ohjeen on määritettävä sen osoite, onko sen allekirjoitettava transaktio ja kirjoitetaanko siihen.
  • Data: Tavupuskuri, joka kertoo ohjelmalle, mikä ohje suoritetaan, ja sisältää kaikki ohjeen vaatimat argumentit.
{
"accounts": [
{
"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC",
"role": 3,
"signer": {
"address": "Hu28vRMGWpQXN56eaE7jRiDDRRz3vCXEs7EKHRfL6bC",
"keyPair": {
"privateKey": {},
"publicKey": {}
}
}
},
{
"address": "2mBY6CTgeyJNJDzo6d2Umipw2aGUquUA7hLdFttNEj7p",
"role": 1
}
],
"programAddress": "11111111111111111111111111111111",
"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
}
}

Transaktiot

Kun olet luonut ohjeet, jotka haluat kutsua, seuraava vaihe on luoda Transaction ja lisätä ohjeet transaktioon. Solana transaktio koostuu seuraavista:

  1. Allekirjoitukset: Taulukko allekirjoituksia kaikilta tileiltä, joita vaaditaan allekirjoittajiksi transaktion ohjeisiin. Allekirjoitus luodaan allekirjoittamalla transaktion message tilin yksityisellä avaimella.
  2. Viesti: Transaktion viesti sisältää luettelon ohjeista, jotka käsitellään atomisesti.
Transaction
pub struct Transaction {
#[wasm_bindgen(skip)]
#[serde(with = "short_vec")]
pub signatures: Vec<Signature>,
#[wasm_bindgen(skip)]
pub message: Message,
}

Transaktion muotoTransaktion muoto

Transaktioviestin rakenne koostuu seuraavista:

  • Viestin otsikko: Määrittää allekirjoittajien ja vain luku -tilien määrän.
  • Tilien osoitteet: Taulukko tilien osoitteista, joita transaktion ohjeet vaativat.
  • Viimeisin lohkohash: Toimii transaktion aikaleimana.
  • Ohjeet: Taulukko suoritettavista ohjeista.
Message
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>,
}

Transaktion koko

Solana-transaktioiden kokorajoitus on 1232 tavua. Tämä rajoitus tulee IPv6:n Maximum Transmission Unit (MTU) -koosta, joka on 1280 tavua, josta vähennetään 48 tavua verkko-otsikoita (40 tavua IPv6 + 8 tavua otsikko).

Transaktion kokonaiskoon (allekirjoitukset ja viesti) on pysyttävä tämän rajan alla ja se sisältää:

  • Allekirjoitukset: 64 tavua kukin
  • Viesti: Otsikko (3 tavua), tilien avaimet (32 tavua kukin), viimeisin lohkohash (32 tavua) ja ohjeet

Transaktion muotoTransaktion muoto

Viestin otsikko

Viestin otsikko määrittää transaktion tilien käyttöoikeudet. Se toimii yhdessä tiukasti järjestettyjen tilien osoitteiden kanssa määrittääkseen, mitkä tilit ovat allekirjoittajia ja mitkä ovat kirjoitettavia.

  1. Kaikkien transaktion ohjeiden vaatimien allekirjoitusten määrä.
  2. Allekirjoitettujen vain luku -tilien määrä.
  3. Allekirjoittamattomien vain luku -tilien määrä.
MessageHeader
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,
}

Viestin otsikkoViestin otsikko

Kompakti taulukkomuoto

Kompakti taulukko transaktioviestissä on taulukko, joka on sarjoitettu seuraavassa muodossa:

  1. Taulukon pituus (koodattu compact-u16-muodossa)
  2. Taulukon kohteet lueteltuna peräkkäin

Kompakti taulukkomuotoKompakti taulukkomuoto

Tätä muotoa käytetään koodaamaan tilien osoitteiden ja ohjeiden taulukoiden pituudet transaktioviesteissä.

Tilien osoitteiden taulukko

Transaktioviesti sisältää yhden listan kaikista tilien osoitteista, joita sen ohjeet vaativat. Taulukko alkaa compact-u16 -numerolla, joka ilmaisee kuinka monta osoitetta se sisältää.

Tilan säästämiseksi transaktio ei tallenna käyttöoikeuksia jokaiselle tilille erikseen. Sen sijaan se hyödyntää MessageHeader -tietoa ja tilien osoitteiden tarkkaa järjestystä käyttöoikeuksien määrittämiseen.

Osoitteet järjestetään aina seuraavalla tavalla:

  1. Tilit, jotka ovat kirjoitettavia ja allekirjoittajia
  2. Tilit, jotka ovat vain luettavia ja allekirjoittajia
  3. Tilit, jotka ovat kirjoitettavia mutta eivät allekirjoittajia
  4. Tilit, jotka ovat vain luettavia eivätkä allekirjoittajia

MessageHeader tarjoaa arvot, joita käytetään määrittämään tilien määrä kullekin käyttöoikeusryhmälle.

Tilien osoitteiden kompakti taulukkoTilien osoitteiden kompakti taulukko

Viimeisin lohkohash

Jokainen transaktio vaatii viimeisimmän lohkohashin, joka palvelee kahta tarkoitusta:

  1. Toimii aikaleimana sille, milloin transaktio luotiin
  2. Estää transaktioiden kahdentumisen

Lohkohash vanhenee 150 lohkon jälkeen (noin 1 minuutti olettaen 400ms lohkoajat), minkä jälkeen transaktio katsotaan vanhentuneeksi eikä sitä voida käsitellä.

Voit käyttää getLatestBlockhash RPC -metodia saadaksesi nykyisen lohkohashin ja viimeisen lohkon korkeuden, jossa lohkohash on vielä voimassa.

Ohjeiden taulukko

Transaktioviesti sisältää taulukon ohjeita CompiledInstruction -tyypissä. Ohjeet muunnetaan tähän tyyppiin, kun ne lisätään transaktioon.

Kuten tilien osoitteiden taulukko viestissä, se alkaa compact-u16 -pituudella, jota seuraa ohjedatan. Jokainen ohje sisältää:

  1. Ohjelman tunnus -indeksi: Indeksi, joka osoittaa ohjelman osoitteeseen tilien osoitteiden taulukossa. Tämä määrittää ohjelman, joka käsittelee ohjeen.
  2. Tilien indeksit: Taulukko indeksejä, jotka osoittavat tämän ohjeen vaatimiin tilien osoitteisiin.
  3. Instruction data: Tavutaulukko, joka määrittää mikä ohje suoritetaan ohjelmassa ja kaikki ohjeen vaatimat lisätiedot (esim. funktion argumentit).
CompiledInstruction
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>,
}

Ohjeiden kompakti taulukkoOhjeiden kompakti taulukko

Esimerkki transaktion rakenteesta

Suorita alla olevat esimerkit nähdäksesi transaktion rakenteen, jossa on yksi SOL-siirto-ohje.

import {
createSolanaRpc,
generateKeyPairSigner,
lamports,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
pipe,
signTransactionMessageWithSigners,
getCompiledTransactionMessageDecoder
} from "@solana/kit";
import { getTransferSolInstruction } from "@solana-program/system";
const rpc = createSolanaRpc("http://localhost:8899");
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Generate sender and recipient keypairs
const sender = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();
// Define the amount to transfer
const 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 recipient
const transferInstruction = getTransferSolInstruction({
source: sender,
destination: recipient.address,
amount: transferAmount
});
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(sender, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([transferInstruction], tx)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
// Decode the messageBytes
const compiledTransactionMessage =
getCompiledTransactionMessageDecoder().decode(signedTransaction.messageBytes);
console.log(JSON.stringify(compiledTransactionMessage, null, 2));
Console
Click to execute the code.

Seuraavat esimerkit näyttävät transaktion viestin tulosteen edellisistä koodiesimerkeistä. Tarkka muoto vaihtelee SDK:n mukaan, mutta sisältää samat 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
}
}
]
}

Transaktion lähettämisen jälkeen voit hakea sen tiedot käyttämällä getTransaction RPC-metodia. Vastaus on rakenteeltaan samankaltainen kuin seuraava esimerkki. Vaihtoehtoisesti voit tarkastella transaktiota Solana Explorerin avulla.

"Transaktion allekirjoitus" yksilöi transaktion Solanassa. Käytät tätä allekirjoitusta transaktion tietojen hakemiseen verkosta. Transaktion allekirjoitus on yksinkertaisesti ensimmäinen allekirjoitus transaktiossa. Huomaa, että ensimmäinen allekirjoitus on myös transaktion maksajan allekirjoitus.

Transaction Data
{
"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?

Sisällysluettelo

Muokkaa sivua