Solidity में, किसी अन्य कॉन्ट्रैक्ट के स्टोरेज को पढ़ने के लिए view फ़ंक्शन को कॉल करने या स्टोरेज वेरिएबल के पब्लिक होने की आवश्यकता होती है। Solana में, एक ऑफ़-चेन क्लाइंट सीधे स्टोरेज अकाउंट को पढ़ सकता है। यह ट्यूटोरियल दिखाता है कि कैसे एक ऑन-चेन Solana प्रोग्राम उस अकाउंट का डेटा पढ़ सकता है जिसका वह ओनर नहीं है।
हम दो प्रोग्राम सेट अप करेंगे: data_holder और data_reader। data_holder उस डेटा के साथ एक PDA को इनिशियलाइज़ करेगा और उसका ओनर होगा जिसे data_reader पढ़ेगा।
डेटा स्टोर करने वाले data_holder प्रोग्राम को सेट अप करना: Shell 1
निम्नलिखित कोड एक बेसिक Solana प्रोग्राम है जो u64 फ़ील्ड x के साथ अकाउंट Storage को इनिशियलाइज़ करता है और इनिशियलाइज़ेशन के समय इसमें वैल्यू 9 स्टोर करता है:
Typescript कोड:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { DataHolder } from "../target/types/data_holder";
describe("data-holder", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.DataHolder as Program<DataHolder>;
it("Is initialized!", async () => {
const seeds = [];
const [storage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(
seeds,
program.programId
);
await program.methods
.initialize()
.accounts({ storage: storage })
.rpc();
let storageStruct = await program.account.storage.fetch(
storage
);
console.log("The value of x is: ",storageStruct.x.toString());
console.log("Storage account address: ", storage.toBase58());
});
});
टेस्ट PDA का एड्रेस प्रिंट करेगा, हम कुछ ही देर में इस एड्रेस का संदर्भ लेंगे:

Reader
data_reader द्वारा किसी अन्य अकाउंट को पढ़ने के लिए, उस अकाउंट की पब्लिक की (public key) को Context स्ट्रक्ट के माध्यम से ट्रांज़ैक्शन के हिस्से के रूप में पास किया जाना चाहिए। यह किसी अन्य प्रकार के अकाउंट को पास करने से अलग नहीं है।
अकाउंट्स में डेटा सीरियलाइज़्ड बाइट्स (serialized bytes) के रूप में स्टोर होता है। अकाउंट को डीसीरियलाइज़ (deserialize) करने के लिए, data_reader प्रोग्राम को उस स्ट्रक्ट की Rust डेफ़िनिशन की आवश्यकता होती है जिसे वह पढ़ रहा है। हमें data_reader के लिए निम्नलिखित अकाउंट डेफ़िनिशन उपलब्ध करानी होगी, जो data_holder में Storage स्ट्रक्ट के बिल्कुल समान है:
#[account]
pub struct Storage {
x: u64,
}
यह स्ट्रक्ट data_reader वाले स्ट्रक्ट के बिल्कुल समान है — यहाँ तक कि नाम भी समान होना चाहिए (हम बाद में इसके डिटेल्स में जाएँगे कि ऐसा क्यों है)। अकाउंट को पढ़ने का कोड निम्नलिखित दो लाइनों में है:
let mut data_slice: &[u8] = &data_account.data.borrow();
let data_struct: Storage =
AccountDeserialize::try_deserialize(
&mut data_slice,
)?;
data_slice अकाउंट में मौजूद डेटा के रॉ बाइट्स (raw bytes) हैं। यदि आप solana account <pda address> रन करते हैं (जब हमने data_holder डिप्लॉय किया था तब जनरेट हुए PDA एड्रेस का उपयोग करके) तो आप वहां डेटा देख सकते हैं, जिसमें लाल बॉक्स में स्टोर किया गया नंबर 9 भी शामिल है:

पीले बॉक्स में पहले 8 बाइट्स अकाउंट डिस्क्रिमिनेटर (account discriminator) हैं, जिसका वर्णन हम बाद में करेंगे।
डीसीरियलाइज़ेशन इस स्टेप पर होता है:
let data_struct: Storage =
AccountDeserialize::try_deserialize(
&mut data_slice,
)?;
यहाँ Storage टाइप (वही स्ट्रक्ट जिसे हमने ऊपर डिफाइन किया है) पास करने से Solana को पता चलता है कि डेटा को डीसीरियलाइज़ करने का (प्रयास) कैसे करना है।
अब एक नए फ़ोल्डर में एक अलग anchor प्रोजेक्ट anchor new data_reader बनाते हैं।
यहाँ पूरा Rust कोड है:
use anchor_lang::prelude::*;
declare_id!("HjJ1Rqsth5uxA6HKNGy8VVRvwK4W7aFgmQsss7UxePBw");
#[program]
pub mod data_reader {
use super::*;
pub fn read_other_data(
ctx: Context<ReadOtherData>,
) -> Result<()> {
let data_account = &ctx.accounts.other_data;
if data_account.data_is_empty() {
return err!(MyError::NoData);
}
let mut data_slice: &[u8] = &data_account.data.borrow();
let data_struct: Storage =
AccountDeserialize::try_deserialize(
&mut data_slice,
)?;
msg!("The value of x is: {}", data_struct.x);
Ok(())
}
}
#[error_code]
pub enum MyError {
#[msg("No data")]
NoData,
}
#[derive(Accounts)]
pub struct ReadOtherData<'info> {
/// CHECK: We do not own this account so
// we must be very cautious with how we
// use the data
other_data: UncheckedAccount<'info>,
}
#[account]
pub struct Storage {
x: u64,
}
और इसे रन करने के लिए यहाँ टेस्ट कोड दिया गया है। नीचे दिए गए कोड में PDA का एड्रेस ज़रूर बदल लें:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { DataReader } from "../target/types/data_reader";
describe("data-reader", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace
.DataReader as Program<DataReader>;
it("Is initialized!", async () => {
// CHANGE THIS TO THE ADDRESS OF THE PDA OF
// DATA ACCOUNT HOLDER
const otherStorageAddress ="HRGqGCLXxLryZav2SeKJKqBWYs8Ne7ppJxf3MLM3Y71E";
const pub_key_other_storage = new anchor.web3.PublicKey(
otherStorageAddress
);
const tx = await program.methods
.readOtherData()
.accounts({ otherData: pub_key_other_storage })
.rpc();
});
});
किसी अन्य अकाउंट के डेटा को पढ़ने का टेस्ट करने के लिए:
- बैकग्राउंड में चल रहे
solana-test-validatorके साथdata_holderटेस्ट रन करें। Storageअकाउंट की प्रिंट की गई पब्लिक की (public key) को कॉपी और पेस्ट करें।- उस पब्लिक की को
data_readerके टेस्ट केotherStorageAddressमें डालें। - एक अन्य शेल (shell) में Solana लॉग्स रन करें।
- डेटा पढ़ने के लिए
data_readerका टेस्ट रन करें।
Solana लॉग्स में निम्नलिखित दिखाई देना चाहिए:

क्या होगा यदि हम स्ट्रक्ट्स को समान नाम न दें?
यदि आप data_reader में Storage स्ट्रक्ट का नाम बदलकर Storage के अलावा कुछ और कर देते हैं, मान लीजिए Storage2 और अकाउंट पढ़ने का प्रयास करते हैं, तो निम्नलिखित एरर आएगी:

Anchor द्वारा कंप्यूट किया गया अकाउंट डिस्क्रिमिनेटर स्ट्रक्ट नाम के sha256 के पहले आठ बाइट्स होता है। अकाउंट डिस्क्रिमिनेटर स्ट्रक्ट के वेरिएबल्स पर निर्भर नहीं करता है।
जब Anchor अकाउंट पढ़ता है, तो यह पहले आठ बाइट्स (अकाउंट डिस्क्रिमिनेटर) को यह देखने के लिए चेक करता है कि क्या यह उस स्ट्रक्ट डेफ़िनिशन के अकाउंट डिस्क्रिमिनेटर से मेल खाता है जिसका वह डेटा को डीसीरियलाइज़ करने के लिए लोकली उपयोग कर रहा है। यदि वे मेल नहीं खाते हैं, तो Anchor डेटा को डीसीरियलाइज़ नहीं करेगा।
अकाउंट डिस्क्रिमिनेटर को चेक करना क्लाइंट द्वारा गलती से गलत अकाउंट या ऐसे अकाउंट को पास करने से रोकने के लिए एक सुरक्षा उपाय (safeguard) है जिसका डेटा उस फ़ॉर्मेट में नहीं है जिसकी Anchor उम्मीद कर रहा है।
एक बड़े स्ट्रक्ट को पार्स करने पर डीसीरियलाइज़ेशन रिवर्ट (revert) नहीं होगा
Anchor केवल यह चेक करता है कि अकाउंट डिस्क्रिमिनेटर मेल खाता है या नहीं — यह पढ़े जा रहे अकाउंट के अंदर मौजूद फ़ील्ड्स को वैलिडेट नहीं करता है।
Case 1: Anchor यह चेक नहीं करता कि स्ट्रक्ट फ़ील्ड का नाम मेल खाता है या नहीं
आइए data_reader में Storage स्ट्रक्ट के x फ़ील्ड को बदलकर y कर दें, और data_holder में Storage स्ट्रक्ट को बिना बदले छोड़ दें:
// data_reader
#[account]
pub struct Storage {
y: u64,
}
हमें लॉग लाइन को भी इस प्रकार बदलना होगा:
msg!("The value of y is: {}", data_struct.y);
जब हम टेस्ट को फिर से रन करते हैं, तो यह सफलतापूर्वक डेटा पढ़ लेता है:
Program log: Instruction: ReadOtherData
Program log: The value of y is: 9
Case 2: Anchor डेटा टाइप चेक नहीं करता है
अब data_reader में Storage के y का डेटा टाइप बदलकर u32 कर देते हैं, भले ही ओरिजिनल स्ट्रक्ट u64 है।
// data_reader
#[account]
pub struct Storage {
y: u32,
}
जब हम टेस्ट रन करते हैं, तब भी Anchor सफलतापूर्वक अकाउंट डेटा को पार्स कर लेता है।
Program log: Instruction: ReadOtherData
Program log: The value of y using u32 is: 9
इसके “सफल” होने का कारण डेटा के लेआउट का तरीका है:

7 में 9 पहले बाइट्स में उपलब्ध है — एक u32 पहले 4 बाइट्स में डेटा ढूंढेगा इसलिए यह 9 को “देख” पाएगा।
बेशक, अगर हम x में ऐसी कोई वैल्यू स्टोर करते हैं जिसे u32 होल्ड नहीं कर सकता, जैसे कि , तो हमारा पढ़ने वाला प्रोग्राम गलत नंबर प्रिंट करेगा।
एक्सरसाइज़ (Exercise): वैलिडेटर को रीसेट करें और वैल्यू के साथ data_holder को फिर से डिप्लॉय करें। Rust में पावर (power) निकालने का तरीका let result = u64::pow(base, exponent) है। उदाहरण के लिए, let result = u64::pow(2, 32);। देखें कि data_reader द्वारा कौन सी वैल्यू लॉग की जाती है।
Case 3: मौजूद डेटा से ज़्यादा पार्स करना
स्टोरेज अकाउंट 16 बाइट्स बड़ा है। यह अकाउंट डिस्क्रिमिनेटर के लिए 8 बाइट्स, और u64 वेरिएबल के लिए 8 बाइट्स होल्ड करता है। यदि हम मौजूद डेटा से अधिक पढ़ने का प्रयास करते हैं, जैसे कि उन वैल्यूज़ के साथ एक स्ट्रक्ट को डिफाइन करके जिन्हें होल्ड करने के लिए 16 बाइट्स से अधिक की आवश्यकता होती है, तो पढ़ते समय डीसीरियलाइज़ेशन फेल हो जाएगा:
#[account]
pub struct Storage {
y: u64,
z: u64,
}
ऊपर दिए गए स्ट्रक्ट को y और z को स्टोर करने के लिए 16 बाइट्स की आवश्यकता होती है, लेकिन अकाउंट डिस्क्रिमिनेटर को होल्ड करने के लिए अतिरिक्त 8 बाइट्स की आवश्यकता होती है, जिससे अकाउंट 24 बाइट्स का हो जाता है।

Anchor अकाउंट डेटा पार्सिंग समरी (Summary)
किसी बाहरी अकाउंट से डेटा पढ़ते समय, Anchor यह चेक करेगा कि अकाउंट डिस्क्रिमिनेटर मेल खाता है या नहीं और क्या अकाउंट में इतना डेटा है जिसे try_deserialize के टाइप के रूप में उपयोग किए गए स्ट्रक्ट में डीसीरियलाइज़ किया जा सके:
let data_struct: Storage =
AccountDeserialize::try_deserialize(
&mut data_slice,
)?;
Anchor वेरिएबल्स के नाम या उनकी लंबाई चेक नहीं करता है।
अंडर द हुड (Under the hood), Anchor अकाउंट में डेटा को इंटरप्रेट (interpret) करने के लिए कोई मेटाडेटा (metadata) स्टोर नहीं करता है। यह केवल एंड-टू-एंड (end-to-end) स्टोर किए गए वेरिएबल्स के बाइट्स हैं।
सभी डेटा अकाउंट्स Anchor के कन्वेंशन (convention) का पालन नहीं करते हैं
Solana को अकाउंट डिस्क्रिमिनेटर्स के उपयोग की आवश्यकता नहीं है। रॉ (raw) Rust में लिखे गए Solana प्रोग्राम — Anchor फ्रेमवर्क के बिना — संभवतः अपना डेटा इस तरह से स्टोर करेंगे जो सीधे तौर पर Anchor के उस सीरियलाइज़ेशन तरीके के अनुकूल नहीं है जिसे AccountDeserialize::try_deserialize इम्प्लीमेंट करता है। नॉन-anchor डेटा को डीसीरियलाइज़ करने के लिए, डेवलपर को पहले से इस्तेमाल किए गए सीरियलाइज़ेशन तरीके का पता होना चाहिए — Solana इकोसिस्टम में कोई लागू किया गया यूनिवर्सल कन्वेंशन नहीं है।
किसी भी आर्बिट्रेरी (arbitrary) अकाउंट से डेटा पढ़ते समय सावधानी बरतें
Solana प्रोग्राम डिफ़ॉल्ट रूप से अपग्रेडेबल (upgradeable) होते हैं। जिस तरीके से वे अपने अकाउंट्स में डेटा स्टोर करते हैं वह कभी भी बदल सकता है, जो उस प्रोग्राम को ब्रेक कर सकता है जो उनसे डेटा पढ़ रहा है।
किसी भी आर्बिट्रेरी अकाउंट से डेटा स्वीकार करना खतरनाक है — डेटा पढ़ने से पहले आमतौर पर यह चेक करना चाहिए कि अकाउंट किसी ट्रस्टेड प्रोग्राम के स्वामित्व (ownership) में है या नहीं।
मूल रूप से प्रकाशित 7 मई, 2024