Solana में multicall इन-बिल्ट है
Ethereum में, यदि हम कई transactions को एक साथ atomically batch करना चाहते हैं, तो हम multicall पैटर्न का उपयोग करते हैं। यदि एक विफल (fail) होता है, तो बाकी भी विफल हो जाते हैं।
Solana के रनटाइम (runtime) में यह पहले से मौजूद है, इसलिए हमें multicall को लागू (implement) करने की आवश्यकता नहीं है। नीचे दिए गए उदाहरण में, हम एक अकाउंट को initialize करते हैं और एक ही transaction में इसमें लिखते (write) हैं — init_if_needed का उपयोग किए बिना।
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Batch } from "../target/types/batch";
describe("batch", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Batch as Program<Batch>;
it("Is initialized!", async () => {
const wallet = anchor.workspace.Batch.provider.wallet.payer;
const [pda, _bump] = anchor.web3.PublicKey.findProgramAddressSync([], program.programId);
const initTx = await program.methods.initialize().accounts({pda: pda}).transaction();
// for u32, we don't need to use big numbers
const setTx = await program.methods.set(5).accounts({pda: pda}).transaction();
let transaction = new anchor.web3.Transaction();
transaction.add(initTx);
transaction.add(setTx);
await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, transaction, [wallet]);
const pdaAcc = await program.account.pda.fetch(pda);
console.log(pdaAcc.value); // prints 5
});
});
यहाँ इससे संबंधित Rust कोड दिया गया है:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("Ao9LdZtHdMAzrFUEfRNbKEb5H4nXvpRZC69kxeAGbTPE");
#[program]
pub mod batch {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, new_val: u32) -> Result<()> {
ctx.accounts.pda.value = new_val;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = size_of::<PDA>() + 8, seeds = [], bump)]
pub pda: Account<'info, PDA>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Set<'info> {
#[account(mut)]
pub pda: Account<'info, PDA>,
}
#[account]
pub struct PDA {
pub value: u32,
}
ऊपर दिए गए कोड पर कुछ टिप्पणियाँ (comments):
- Rust में
u32या उससे छोटी वैल्यू पास करते समय, हमें Javascript bignumber का उपयोग करने की आवश्यकता नहीं है। - एक transaction बनाने के लिए
await program.methods.initialize().accounts({pda: pda}).rpc()करने के बजाय हमawait program.methods.initialize().accounts({pda: pda}).transaction()करते हैं।
Solana Transaction Size Limit
किसी Solana transaction का कुल साइज़ 1232 bytes से अधिक नहीं हो सकता।
इसका तात्पर्य यह है कि आप “अनलिमिटेड (unlimited)” संख्या में transactions को batch नहीं कर पाएंगे और Ethereum की तरह केवल अधिक गैस (gas) का भुगतान करके काम नहीं चला सकते।
Batched transactions की atomicity का प्रदर्शन
आइए Rust में अपने set फ़ंक्शन को इस तरह बदलें कि वह हमेशा विफल (fail) हो जाए। इससे हमें यह देखने में मदद मिलेगी कि यदि बाद का कोई batched transaction विफल हो जाता है, तो initialize transaction रोल बैक (rolled back) हो जाता है।
निम्नलिखित Rust प्रोग्राम set को कॉल किए जाने पर हमेशा एक एरर (error) रिटर्न करता है:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("Ao9LdZtHdMAzrFUEfRNbKEb5H4nXvpRZC69kxeAGbTPE");
#[program]
pub mod batch {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, new_val: u32) -> Result<()> {
ctx.accounts.pda.value = new_val;
return err!(Error::AlwaysFails);
}
}
#[error_code]
pub enum Error {
#[msg(always fails)]
AlwaysFails,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = size_of::<PDA>() + 8, seeds = [], bump)]
pub pda: Account<'info, PDA>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Set<'info> {
#[account(mut)]
pub pda: Account<'info, PDA>,
}
#[account]
pub struct PDA {
pub value: u32,
}
निम्नलिखित Typescript कोड initialization और set का एक batch transaction भेजता है:
import * as anchor from "@coral-xyz/anchor";
import { Program, SystemProgram } from "@coral-xyz/anchor";
import { Batch } from "../target/types/batch";
describe("batch", () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Batch as Program<Batch>;
it("Set the number to 5, initializing if necessary", async () => {
const wallet = anchor.workspace.Batch.provider.wallet.payer;
const [pda, _bump] = anchor.web3.PublicKey.findProgramAddressSync([], program.programId);
// console.log the address of the pda
console.log(pda.toBase58());
let transaction = new anchor.web3.Transaction();
transaction.add(await program.methods.initialize().accounts({pda: pda}).transaction());
transaction.add(await program.methods.set(5).accounts({pda: pda}).transaction());
await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, transaction, [wallet]);
});
});
जब हम टेस्ट रन करते हैं, और फिर pda अकाउंट के लिए लोकल वैलिडेटर (local validator) को क्वेरी (query) करते हैं, तो हम देखते हैं कि यह मौजूद नहीं है। भले ही initialize transaction पहले था, उसके बाद आया set transaction रिवर्ट (revert) हो गया, जिससे पूरा transaction कैंसिल (cancelled) हो गया, और इस प्रकार कोई अकाउंट initialize नहीं हुआ।

फ्रंटएंड पर “Init if needed”
आप एक अलग initialize फ़ंक्शन रखते हुए फ्रंटएंड कोड का उपयोग करके init_if_needed के व्यवहार (behavior) को सिमुलेट (simulate) कर सकते हैं। हालांकि, यूज़र के दृष्टिकोण से यह सब आसान हो जाएगा क्योंकि उन्हें पहली बार किसी अकाउंट का उपयोग करते समय कई transactions जारी (issue) नहीं करने पड़ेंगे।
यह निर्धारित करने के लिए कि क्या किसी अकाउंट को initialize करने की आवश्यकता है, हम चेक करते हैं कि क्या इसमें शून्य lamports हैं या यह सिस्टम प्रोग्राम (system program) के स्वामित्व (owned) में है। यहाँ बताया गया है कि हम Typescript में ऐसा कैसे कर सकते हैं:
import * as anchor from "@coral-xyz/anchor";
import { Program, SystemProgram } from "@coral-xyz/anchor";
import { Batch } from "../target/types/batch";
describe("batch", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Batch as Program<Batch>;
it("Set the number to 5, initializing if necessary", async () => {
const wallet = anchor.workspace.Batch.provider.wallet.payer;
const [pda, _bump] = anchor.web3.PublicKey.findProgramAddressSync([], program.programId);
let accountInfo = await anchor.getProvider().connection.getAccountInfo(pda);
let transaction = new anchor.web3.Transaction();
if (accountInfo == null || accountInfo.lamports == 0 || accountInfo.owner == anchor.web3.SystemProgram.programId) {
console.log("need to initialize");
const initTx = await program.methods.initialize().accounts({pda: pda}).transaction();
transaction.add(initTx);
}
else {
console.log("no need to initialize");
}
// we're going to set the number anyway
const setTx = await program.methods.set(5).accounts({pda: pda}).transaction();
transaction.add(setTx);
await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, transaction, [wallet]);
const pdaAcc = await program.account.pda.fetch(pda);
console.log(pdaAcc.value);
});
});
हमें अपने Rust कोड को भी मॉडिफाई करना होगा ताकि यह set ऑपरेशन पर जबरन (forcibly) विफल न हो।
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("Ao9LdZtHdMAzrFUEfRNbKEb5H4nXvpRZC69kxeAGbTPE");
#[program]
pub mod batch {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, new_val: u32) -> Result<()> {
ctx.accounts.pda.value = new_val;
Ok(()) // ERROR HAS BEEN REMOVED
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = size_of::<PDA>() + 8, seeds = [], bump)]
pub pda: Account<'info, PDA>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Set<'info> {
#[account(mut)]
pub pda: Account<'info, PDA>,
}
#[account]
pub struct PDA {
pub value: u32,
}
यदि हम एक ही लोकल वैलिडेटर इंस्टेंस (local validator instance) पर टेस्ट को दो बार रन करते हैं, तो हमें निम्नलिखित आउटपुट मिलेंगे:
पहला टेस्ट रन:

दूसरा टेस्ट रन:

Solana 1232 bytes से बड़े प्रोग्राम कैसे डिप्लॉय (deploy) करता है?
यदि आप एक नया Solana प्रोग्राम बनाते हैं और anchor deploy (या anchor test) रन करते हैं, तो आप लॉग्स में देखेंगे कि BFPLoaderUpgradeable के लिए कई transactions हैं:
Transaction executed in slot 65695:
Signature: 62Zu3NPyjjaEoH4XSc7kULtuoszLPctM1PTrLiC7A3CiaGJEzYscQ5c9SKbN3UUoqctyrdzW2upDXnSC4VnMjyfZ
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: 3cD19SGmdfd991NjcGHpYcnjhZ3FYqEWnHMJALQ95X5fvwHVhB3Cw9PwqSDwziiCMQHcZ8iuxXqg3UDJmp7gJHd3
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: 5apuTjqCMKGdyYGRZ9sCLDapPCKqjyJMyqWMC24EsW4pLzHhM3YUgnf5Q2sqXSLVTxjKaSgZ3fcCkZrAah32uzh2
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: HJ8XaErydn8ojxaEknZsg43pGA9mC8TBqV4zwSrZgXFvi5UqgZjNU65TQKqb6DyEZFtHecytt1k7U4N9Vw52rur
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: 3uY9beX23VdRXeEqUSP4cpAuTevdcjHDZ8K3pwKVpw51mwX1jLGQ7LYB7d68dWSe571TeAoxq33eoUU7c8gTDgic
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: 666r5LcQaH1ZcZWhrHFUFEqjHXEE1QUyh27HFRkWsDQihM7FYtyz3v4eJgVkQwhJuMDSYHJZHDRrSsNVbCFrEkV9
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65696:
Signature: 2QmPZFkDN9WsKiNjHFdaNLuaYbQFXtN8yRgHTDC3Ce2z28483LNVyuE1AnwgsRisiKeiKe5Wu9WTbkTbAwmodPTC
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65696:
Signature: EsTiuCn6PGA158Xi43XwGtYf2tDJTbgxRJehHS9AQ9AcW4qraxWuNPzdD7Wk4yeL65oaaa1G8WMqkjYbJcGzhv1V
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65696:
Signature: 3PZSv4dnggW52C3FL9E1JPvwueBp7E342o9aM29mH2CnfGsGLDBRJcN64EQeJEkc57hgGyZsiz8J1fSV1Qquz8zx
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65696:
Signature: 4ynMY9ioELf4xxtBpHeM1q2fuWM5usa1w8dXQhLhjstR8U6LmpYHTJs7Gc82XkVyMXywPrsbu3EDCAcpoFj7qwkJ
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65698:
Signature: 5rs38HHbWF2ZrsgDCux1X9FRvkrhTdrEimdhidd2EYbaeezAmy9Tv5AFULgsarPtJCft8uZmsvhpYKwHGxnLf2sG
Status: Ok
Log Messages:
Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Deployed program Ao9LdZtHdMAzrFUEfRNbKEb5H4nXvpRZC69kxeAGbTPE
Program BFPLoaderUpgradeab1e11111111111111111111111 success
यहाँ, Anchor बाइटकोड (bytecode) की डिप्लॉयमेंट को कई transactions में विभाजित (split) कर रहा है क्योंकि पूरे बाइटकोड को एक साथ डिप्लॉय करना एक सिंगल transaction में फिट नहीं होगा। हम लॉग्स को एक फ़ाइल में निर्देशित (directing) करके और हुए transactions की संख्या गिनकर देख सकते हैं कि इसमें कितने transactions लगे:
solana logs > logs.txt
# run `anchor deploy` in another shell
grep "Transaction executed" logs.txt | wc -l
यह मोटे तौर पर उस संख्या से मेल खाएगा जो anchor test या anchor deploy कमांड के बाद अस्थायी (temporarily) रूप से दिखाई देती है:

Transactions को कैसे batch किया जाता है, इसकी सटीक प्रक्रिया का वर्णन Solana’s Documentation: How solana program deploy works में किया गया है।
Transactions की यह सूची अलग-अलग transactions हैं, न कि कोई batched transaction। यदि इसे batch किया गया होता, तो यह 1232 बाइट की लिमिट को पार कर जाता।
RareSkills के साथ और जानें
अधिक Solana ट्यूटोरियल्स के लिए हमारा Solana dev course देखें।
मूल रूप से 10 मार्च, 2024 को प्रकाशित