यह लेख बताता है कि BPF Loader प्रोग्राम इंस्ट्रक्शन इनपुट्स को कैसे सीरियलाइज (serialize) करता है, entrypoint उन्हें कैसे प्राप्त करता है, और program_id, accounts, और instruction_data को पढ़ने के लिए प्रोग्राम उस इनपुट को कैसे डीसीरियलाइज (deserialize) करते हैं। इस लेख के दूसरे भाग में, हम कवर करेंगे कि प्रोग्राम आने वाले इंस्ट्रक्शंस को उचित हैंडलर्स तक कैसे रूट करते हैं और ऐसा संभव बनाने के लिए entrypoint किस सपोर्टिंग कोड को सेट अप करता है।
Solana BPF Loader
BPF Loader Solana के नेटिव प्रोग्राम्स में से एक है, जिसका इम्प्लीमेंटेशन यहाँ दिया गया है। यह ऑन-चेन BPF प्रोग्राम्स के डिप्लॉयमेंट, अपग्रेड्स, लोडिंग और एग्जीक्यूशन के लिए जिम्मेदार है।
नेटिव प्रोग्राम्स वे प्रोग्राम होते हैं जो वैलिडेटर (validator) इम्प्लीमेंटेशन का हिस्सा होते हैं और क्लस्टर अपग्रेड्स के हिस्से के रूप में अपग्रेड किए जा सकते हैं (उदाहरण के लिए बग्स को ठीक करने या फीचर्स जोड़ने के लिए)।
Solana में कई BPF Loaders हैं, लेकिन पुराने वाले (V1 और V2) अब इस्तेमाल नहीं (deprecated) होते हैं और नए डिप्लॉयमेंट्स के लिए उपयोग नहीं किए जाते हैं। BPF Loader Upgradeable वर्तमान मानक है, और जब तक अन्यथा न कहा जाए, इस लेख में “BPF Loader” का अर्थ यही है — जिसमें यहाँ बताया गया सीरियलाइजेशन फॉर्मेट भी शामिल है।
BPF Loader एग्जीक्यूशन एनवायरनमेंट सेट अप करता है और Solana वर्चुअल मशीन शुरू करता है। इसके बाद वर्चुअल मशीन प्रोग्राम entrypoint को कॉल करती है और प्रोग्राम एग्जीक्यूशन शुरू हो जाता है।
प्रोग्राम Entrypoint
याद करें कि पिछले लेख में, जब हमने एग्जीक्यूशन ट्रेस का विश्लेषण किया था, तब बाइटकोड एग्जीक्यूशन एक <entrypoint> लेबल से शुरू हुआ था, जो प्रोग्राम के entrypoint फ़ंक्शन को दर्शाने वाला सिंबल है। यहीं से एग्जीक्यूशन की शुरुआत होती है।
Anchor प्रोग्राम्स में, #[program] मैक्रो आपके लिए इस entrypoint को स्वचालित रूप से जनरेट करता है। यह मैक्रो entrypoint फ़ंक्शन बनाता है, इंस्ट्रक्शन डिस्पैचिंग (इंस्ट्रक्शन डेटा के पहले 8 बाइट्स के आधार पर आपके इंस्ट्रक्शन हैंडलर्स को रूट करना) सेट अप करता है, और इनपुट डीसीरियलाइजेशन और आउटपुट सीरियलाइजेशन को संभालता है। आप स्वयं कभी भी entrypoint को नहीं देखते या लिखते हैं।
नेटिव Rust प्रोग्राम्स में, आपको solana_program क्रेट से entrypoint! मैक्रो का उपयोग करके स्वयं entrypoint को परिभाषित करना होता है, फिर इंस्ट्रक्शन डिस्पैच को खुद हैंडल करना होता है।
entrypoint! मैक्रो वह कोड जनरेट करता है जो प्रोग्राम का एंट्री पॉइंट सेट अप करता है। यह समझने के लिए कि यह कोड कैसा दिखता है, आइए जनरेट हुए फ़ंक्शन सिग्नेचर पर एक नज़र डालें:
#[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64;
यह फ़ंक्शन केवल एक पैरामीटर (input) लेता है, जो VM की मेमोरी में स्टोर सीरियलाइज्ड इंस्ट्रक्शन इनपुट्स का एक पॉइंटर (pointer) है। इंस्ट्रक्शन इनपुट में तीन पैरामीटर्स होते हैं:
program_id: कॉल किए जा रहे प्रोग्राम की पब्लिक की (public key)accounts: उन अकाउंट्स का एक ऐरे (array) जिन्हें प्रोग्राम एक्सेस कर सकता हैinstruction_data: इंस्ट्रक्शन-विशिष्ट डेटा (Solidity में calldata के समान)
आप इन इंस्ट्रक्शन इनपुट पैरामीटर्स को बिल्कुल उसी डेटा के रूप में पहचान सकते हैं जो हमने पिछले लेख में एग्जीक्यूशन ट्रेस जनरेट करते समय अपनी instructions.json फ़ाइल में दिया था:
{
"accounts": [],
"program_id": "HTpqQdG7f44su3QsV3HHurraR1ZNjHAdArCy3qHKyKBC",
"instruction_data": [175, 175, 109, 31, 13, 152, 155, 237]
}
BPF Loader प्रोग्राम इंस्ट्रक्शन इनपुट्स को कैसे Serialize करता है
जब आप किसी Solana प्रोग्राम को इन्वोक (invoke) करते हैं, तो Solana रनटाइम (BPF Loader के माध्यम से) इंस्ट्रक्शन इनपुट्स (program_id, अकाउंट लिस्ट, और instruction_data) को प्रत्येक अकाउंट की ऑन-चेन स्टेट (उसका lamports बैलेंस, owner, डेटा बाइट्स, और अन्य मेटाडेटा) के साथ एक बाइट ऐरे में सीरियलाइज करता है और इसे VM की मेमोरी में स्टोर करता है। इस ऐरे की शुरुआत का एक पॉइंटर प्रोग्राम के entrypoint को input पैरामीटर के रूप में पास किया जाता है। फिर इन्वोक किया गया प्रोग्राम इस बाइट ऐरे को डीसीरियलाइज करता है ताकि वह एग्जीक्यूट करने के लिए आवश्यक program_id, अकाउंट्स और इंस्ट्रक्शन डेटा प्राप्त कर सके।
BPF Loader एक ज्ञात सीरियलाइजेशन फॉर्मेट का उपयोग करता है, और Solana प्रोग्राम्स को इंस्ट्रक्शन इनपुट्स को डीसीरियलाइज करते समय बिल्कुल उसी फॉर्मेट का पालन करना चाहिए (हम इस पर बाद में चर्चा करेंगे)।
Anchor प्रोग्राम्स में, #[program] मैक्रो स्वचालित रूप से वह डीसीरियलाइजेशन कोड जनरेट करता है जो इस फॉर्मेट का पालन करता है। नेटिव Rust प्रोग्राम्स में, solana_program क्रेट से entrypoint! मैक्रो एक deserialize इंटरनल फ़ंक्शन प्रदान करता है जो इस फॉर्मेट को लागू करता है। दोनों ही तरह से, डीसीरियलाइजेशन लॉजिक आपके लिए प्रदान किया जाता है, इसलिए आपको इसे शुरुआत (scratch) से इम्प्लीमेंट करने की आवश्यकता नहीं है।
अब हम देखेंगे कि BPF Loader मेमोरी में प्रोग्राम इनपुट डेटा को कैसे सीरियलाइज करता है। सभी वैल्यूज़ लिटिल-एंडियन (little-endian) में एनकोड की जाती हैं, इसलिए लेआउट की जांच करते समय इसे ध्यान में रखें। नीचे दिए गए सेक्शन बताते हैं कि इंस्ट्रक्शन इनपुट कैसे स्ट्रक्चर्ड होता है।
Account count
सीरियलाइज की जाने वाली सबसे पहली चीज़ यह है कि इस प्रोग्राम कॉल को कितने अकाउंट्स की आवश्यकता है। यह 8 बाइट्स लेता है।

इसके बाद, accounts ऐरे के प्रत्येक अकाउंट को सीरियलाइज किया जाता है
अकाउंट काउंट के बाद, लोडर प्रत्येक अकाउंट को एक-एक करके सीरियलाइज करता है। प्रत्येक अकाउंट 1-बाइट के डुप्लिकेट मार्कर (duplicate marker) से शुरू होता है जो यह दर्शाता है कि क्या यह अकाउंट accounts ऐरे में पहली बार आ रहा है या एक डुप्लिकेट है (जिसका अर्थ है कि वही अकाउंट accounts ऐरे में कई बार दिखाई देता है)।

Duplicate accounts
यदि अकाउंट डुप्लिकेट है (यानी यह अकाउंट पहले ही accounts ऐरे में सीरियलाइज किया जा चुका था), तो लोडर इसके सभी डेटा को फिर से सीरियलाइज नहीं करता है। इसके बजाय, 1-बाइट मार्कर में उस इंडेक्स को रखा जाता है जहाँ यह अकाउंट पहली बार दिखाई दिया था, जिसके बाद 7 बाइट्स की पैडिंग (padding) होती है। इसलिए, डुप्लिकेट के लिए कुल साइज़ 8 बाइट्स (1-बाइट इंडेक्स + 7 बाइट्स पैडिंग) होता है।

Non-duplicate accounts
यदि ऐरे में यह इस अकाउंट की पहली उपस्थिति है, तो लोडर इसकी सभी जानकारी को सीरियलाइज करता है। यहीं पर पूरे सीरियलाइज्ड डेटा का साइज़ वेरिएबल (variable) हो जाता है क्योंकि अलग-अलग अकाउंट्स में डेटा की मात्रा अलग-अलग होती है।
लोडर प्रत्येक अकाउंट को एक सीक्वेंस में लिखता है। यह 1-बाइट नॉन-डुप्लिकेट मार्कर (0xff) से शुरू होता है, उसके बाद दो 1-बाइट फ्लैग्स होते हैं जो यह बताते हैं कि अकाउंट एक साइनेर (signer) है या राइटेबल (writable), फिर 4 बाइट्स की पैडिंग होती है — इस शुरुआती सेक्शन के लिए कुल 7 बाइट्स। इसके बाद, यह अकाउंट की पब्लिक की (32 बाइट्स), lamports फील्ड (8 बाइट्स), और डेटा लेंथ (8 बाइट्स) लिखता है। किसी भी अकाउंट डेटा से पहले ये फील्ड्स कुल 55 बाइट्स बनाते हैं।
आगे यह अकाउंट के डेटा बाइट्स (x बाइट्स) लिखता है। एक बार लिखे जाने के बाद, लोडर 10,240 बाइट्स का रिज़र्व्ड स्पेस जोड़ता है (ताकि अकाउंट एग्जीक्यूशन के दौरान री-एलोकेशन के बिना बढ़ सके) और साथ में y अलाइनमेंट पैडिंग बाइट्स जोड़ता है — जहाँ y वह मात्रा है जो अगले फील्ड को 16-बाइट बाउंड्री पर अलाइन करने के लिए आवश्यक है (serialization सोर्स कोड में BPF_ALIGN_OF_U128 के रूप में परिभाषित)। पैडिंग के बाद, यह अकाउंट ओनर की पब्लिक की (32 बाइट्स), 1-बाइट एक्ज़ीक्यूटेबल (executable) फ्लैग और 8-बाइट रेंट इपोक (rent epoch) लिखता है।
इसलिए, एक नॉन-डुप्लिकेट अकाउंट के लिए कुल साइज़: 7 (हेडर) + 32 (pubkey) + 8 (lamports) + 8 (data_len) + x (डेटा) + 10,240 + y (रिज़र्व्ड + अलाइनमेंट) + 32 (owner) + 1 (एक्ज़ीक्यूटेबल) + 8 (रेंट इपोक) = 10,336 + x + y बाइट्स।

यह पैटर्न accounts ऐरे के प्रत्येक नॉन-डुप्लिकेट अकाउंट के लिए दोहराया जाता है।
सभी अकाउंट्स सीरियलाइज होने के बाद (नॉन-डुप्लिकेट या डुप्लिकेट), इंस्ट्रक्शन डेटा सीरियलाइज किया जाता है
एक बार जब सभी अकाउंट्स सीरियलाइज हो जाते हैं, तो लोडर इंस्ट्रक्शन डेटा को सीरियलाइज करता है। सबसे पहले, यह इंस्ट्रक्शन डेटा की लेंथ को 8-बाइट अनसाइन्ड इंटीजर (u64) के रूप में लिखता है, फिर वास्तविक इंस्ट्रक्शन डेटा स्वयं (z बाइट्स, जो हर इंस्ट्रक्शन के अनुसार अलग-अलग होता है)। कुल: 8 + z बाइट्स।

अंत में, प्रोग्राम ID को रखा जाता है
अंतिम चीज़ जो सीरियलाइज की जाती है वह प्रोग्राम ID (कॉल किए जा रहे प्रोग्राम की पब्लिक की) है। यह हमेशा 32 बाइट्स की होती है।

यदि हम एक सिंगल नॉन-डुप्लिकेट अकाउंट के साथ एक सीरियलाइज्ड इनपुट पर विचार करें, तो संपूर्ण सीरियलाइज्ड इनपुट का कुल साइज़ होगा: 8 बाइट्स (अकाउंट काउंट) + 10,336 बाइट्स (फिक्स्ड अकाउंट फील्ड्स + रिज़र्व्ड स्पेस + रेंट इपोक) + x बाइट्स (वास्तविक अकाउंट डेटा) + y बाइट्स (अलाइनमेंट पैडिंग) + 8 बाइट्स (इंस्ट्रक्शन डेटा लेंथ) + z बाइट्स (वास्तविक इंस्ट्रक्शन डेटा) + 32 बाइट्स (प्रोग्राम ID) = कुल 10,384 + x + y + z बाइट्स। यदि आपके पास कई अकाउंट्स हैं, तो आप प्रत्येक अकाउंट के सीरियलाइज्ड डेटा के साइज़ को एक साथ जोड़ते हैं।

नीचे दिया गया डायग्राम संपूर्ण सीरियलाइज्ड इनपुट स्ट्रक्चर (नॉन-डुप्लिकेट अकाउंट) को दर्शाता है:

यह डायग्राम दिखाता है कि मेमोरी में विभिन्न कंपोनेंट्स को कैसे रखा गया है। पीला रंग अकाउंट काउंट दिखाता है, हरा हेडर फ्लैग्स दिखाता है, बैंगनी पब्लिक कीज़ (प्रोग्राम ID सहित) दिखाता है, गुलाबी न्यूमेरिक (numeric) फील्ड्स जैसे lamports और डेटा लेंथ को इंगित करता है, नारंगी अकाउंट डेटा दिखाता है, नीला इंस्ट्रक्शन डेटा दिखाता है, और ग्रे पैडिंग और रिज़र्व्ड सेक्शन्स का प्रतिनिधित्व करता है।
जैसा कि पहले उल्लेख किया गया है, यदि accounts ऐरे में कई अकाउंट्स हैं, तो पैटर्न दोहराया जाता है। पहले अकाउंट के डेटा के बाद, हमारे पास दूसरे अकाउंट के लिए एक और 1-बाइट डुप्लिकेट फ्लैग होगा। यदि यह डुप्लिकेट नहीं है (0xff), तो संपूर्ण अकाउंट स्ट्रक्चर आता है। यदि यह एक डुप्लिकेट है, तो 1-बाइट वैल्यू में accounts ऐरे में इसकी पहली उपस्थिति का इंडेक्स होता है, जिसके बाद 7 बाइट्स की पैडिंग होती है।
ध्यान दें कि सीरियलाइज्ड डेटा का कुल साइज़ फिक्स्ड (fixed) नहीं है; यह इस बात पर निर्भर करता है कि आप कितने अकाउंट्स पास कर रहे हैं, प्रत्येक अकाउंट में कितना डेटा है, और आपके इंस्ट्रक्शन डेटा का साइज़ क्या है।
प्रोग्राम्स इंस्ट्रक्शन इनपुट्स को कैसे Deserialize करते हैं
अब जब हमने देख लिया है कि BPF Loader इंस्ट्रक्शन इनपुट्स को एक बाइट ऐरे में कैसे सीरियलाइज करता है, तो आइए देखें कि आपका प्रोग्राम इस बाइट ऐरे को कैसे डीसीरियलाइज करता है।
जब रनटाइम प्रोग्राम entrypoint को इन्वोक करता है, तो यह एक deserialize फ़ंक्शन को कॉल करता है जो कच्चे (raw) इनपुट बाइट ऐरे को एग्जीक्यूशन के लिए आवश्यक प्रोग्राम ID, अकाउंट्स और इंस्ट्रक्शन डेटा में परिवर्तित करता है।
यह डीसीरियलाइजेशन फ़ंक्शन Solana SDK में मौजूद होता है, और इसे Solana क्रेट के entrypoint! मैक्रो द्वारा इन्वोक किया जाता है। नेटिव Rust Solana प्रोग्राम्स में, यह मैक्रो प्रोग्राम के entrypoint को परिभाषित करता है और इंस्ट्रक्शन प्रोसेसर को कंट्रोल सौंपने से पहले SDK डीसीरियलाइज़र को कॉल करता है (हम अगले सेक्शन में इस पर चर्चा करेंगे)।
डीसीरियलाइजेशन फ़ंक्शन BPF Loader के सीरियलाइज्ड बाइट ऐरे को पढ़ता है और प्रोग्राम ID (एक Pubkey के रूप में), अकाउंट्स (AccountInfo के एक वेक्टर (vector) के रूप में), और इंस्ट्रक्शन डेटा (एक बाइट स्लाइस (byte slice) के रूप में) को निकालता (extract करता) है।
pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
let mut offset: usize = 0;
// Number of accounts present
#[allow(clippy::cast_ptr_alignment)]
let num_accounts = *(input.add(offset) as *const u64) as usize;
offset += size_of::<u64>();
// Account Infos
let mut accounts = Vec::with_capacity(num_accounts);
for _ in 0..num_accounts {
let dup_info = *(input.add(offset) as *const u8);
offset += size_of::<u8>();
if dup_info == NON_DUP_MARKER {
let (account_info, new_offset) = deserialize_account_info(input, offset);
offset = new_offset;
accounts.push(account_info);
} else {
offset += 7; // padding
// Duplicate account, clone the original
accounts.push(accounts[dup_info as usize].clone());
}
}
// Instruction data
let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
offset = new_offset;
// Program Id
let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
(program_id, accounts, instruction_data)
}
अब आइए इस कोड ब्लॉक को स्टेप-बाय-स्टेप (step-by-step) समझते हैं।
Deserialization प्रक्रिया को समझना
फ़ंक्शन सिग्नेचर (Function Signature)
pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec<AccountInfo<'a>>, &'a [u8]) {
deserialize फ़ंक्शन को unsafe मार्क किया गया है क्योंकि इसमें रॉ पॉइंटर मैनिपुलेशन (raw pointer manipulation) शामिल है। यह input लेता है, जो मेमोरी में सीरियलाइज्ड बाइट्स का एक पॉइंटर है, और तीन वैल्यूज़ लौटाता है:
- प्रोग्राम के
Pubkeyका एक रेफरेंस (reference) AccountInfoस्ट्रक्ट्स (structs) का एक वेक्टर (लेन-देन में उपयोग किए गए अकाउंट्स)- इंस्ट्रक्शन डेटा वाला एक बाइट स्लाइस
वर्तमान पोजीशन को ट्रैक करना
सबसे पहले, deserialization फ़ंक्शन input बाइट ऐरे में अपनी वर्तमान स्थिति को ट्रैक करने के लिए एक offset वेरिएबल बनाता है। जैसा कि हम जानते हैं कि BPF Loader इस डेटा को कैसे सीरियलाइज करता है, पहले 8 बाइट्स (एक u64) अकाउंट्स की संख्या को निर्दिष्ट करते हैं। फ़ंक्शन इस वैल्यू को num_accounts में पढ़ता है और फिर ऑफसेट को 8 बाइट्स आगे बढ़ाता है।
let mut offset: usize = 0;
// Number of accounts present
#[allow(clippy::cast_ptr_alignment)]
let num_accounts = *(input.add(offset) as *const u64) as usize;
offset += size_of::<u64>();
Accounts वेक्टर को इनिशियलाइज़ करना
इसके बाद, deserialize फ़ंक्शन num_accounts तत्वों (elements) की कैपेसिटी के साथ एक वेक्टर बनाता है। यह वेक्टर को डायनामिक रूप से बढ़ने देने से अधिक एफ्फिसिएंट (efficient) है क्योंकि भरते समय Rust को संपूर्ण वेक्टर को रीएलोकेट और कॉपी करने की आवश्यकता नहीं होगी।
let mut accounts = Vec::with_capacity(num_accounts);
प्रत्येक अकाउंट को Deserialize करना
डीसीरियलाइज किए जाने वाले अकाउंट्स को रखने के लिए एक वेक्टर बनाने के बाद, फ़ंक्शन प्रत्येक अकाउंट के माध्यम से लूप करता है और डुप्लिकेट फ्लैग की जांच करता है। यदि यह NON_DUP_MARKER (0xff) है, तो इसका मतलब है कि यह एक यूनीक (unique) अकाउंट है, इसलिए यह पूर्ण अकाउंट स्ट्रक्चर को पढ़ने के लिए deserialize_account_info को कॉल करता है (हम इसे आगे समझाते हैं) और उसे accounts वेक्टर वेरिएबल में डालता है। यदि यह एक डुप्लिकेट है (फ्लैग में इसके बजाय एक इंडेक्स होता है), तो यह बस 7 पैडिंग बाइट्स को स्किप कर देता है और उस अकाउंट की कॉपी कर लेता है जो उस इंडेक्स पर पहले ही डीसीरियलाइज किया जा चुका था।
for _ in 0..num_accounts {
// 1 byte indicating if this is a duplicate account
// If not a duplicate, the value is 0xff (NON_DUP_MARKER)
// Otherwise, the value is the index of the account it duplicates
let dup_info = *(input.add(offset) as *const u8);
offset += size_of::<u8>();
if dup_info == NON_DUP_MARKER {
// Not a duplicate: deserialize the full account
let (account_info, new_offset) = deserialize_account_info(input, offset);
offset = new_offset;
accounts.push(account_info);
} else {
// Duplicate account: skip 7 bytes of padding and copy the original
offset += 7; // padding
accounts.push(accounts[dup_info as usize].clone());
}
}
deserialize_account_info हेल्पर फ़ंक्शन को इस प्रकार परिभाषित किया गया है:
यह हेल्पर फ़ंक्शन सीरियलाइज्ड बाइट्स से उसी क्रम में गुजरता है जिस क्रम में BPF Loader ने उन्हें लिखा था। यह तीन बूलियन (boolean) फ्लैग्स (is_signer, is_writable, executable) को पढ़ता है, 4-बाइट पैडिंग को स्किप करता है, दो पब्लिक कीज़ (अकाउंट की और ओनर) पढ़ता है, lamports और डेटा लेंथ पढ़ता है, फिर अकाउंट डेटा को पॉइंट करने वाला एक स्लाइस बनाता है। उसके बाद, यह ऑफसेट को अकाउंट डेटा, 10,240 बाइट्स के रिज़र्व्ड स्पेस, और 8-बाइट रेंट इपोक (जो वास्तव में AccountInfo स्ट्रक्ट में स्टोर नहीं होता है—इसे बस स्किप कर दिया जाता है) के पार आगे बढ़ाता है। अंत में, यह अलाइनमेंट पैडिंग जोड़ता है ताकि यह सुनिश्चित हो सके कि अगला अकाउंट 8-बाइट अलाइन्ड एड्रेस (aligned address) से शुरू हो।
unsafe fn deserialize_account_info<'a>(input: *mut u8, mut offset: usize) -> (AccountInfo<'a>, usize) {
// 1 byte boolean, true if account is a signer
let is_signer = *(input.add(offset) as *const u8) != 0;
offset += size_of::<u8>();
// 1 byte boolean, true if account is writable
let is_writable = *(input.add(offset) as *const u8) != 0;
offset += size_of::<u8>();
// 1 byte boolean, true if account is executable
let executable = *(input.add(offset) as *const u8) != 0;
offset += size_of::<u8>();
// 4 bytes of padding
offset += size_of::<u32>();
// 32 bytes of the account's public key
let key: &Pubkey = &*(input.add(offset) as *const Pubkey);
offset += size_of::<Pubkey>();
// 32 bytes of the account owner's public key
let owner: &Pubkey = &*(input.add(offset) as *const Pubkey);
offset += size_of::<Pubkey>();
// 8 bytes unsigned number of lamports owned by the account
let lamports = &mut *(input.add(offset) as *mut u64);
offset += size_of::<u64>();
// 8 bytes unsigned number of bytes of account data
let data_len = *(input.add(offset) as *const u64) as usize;
offset += size_of::<u64>();
// x bytes of account data (variable length)
let data = from_raw_parts_mut(input.add(offset), data_len);
offset += data_len;
// 10,240 bytes of reserved space (for account data growth)
offset += MAX_PERMITTED_DATA_INCREASE;
// 8 bytes rent epoch (skipped, not deserialized into AccountInfo)
offset += size_of::<u64>();
// Alignment padding to ensure next account starts at 8-byte boundary
offset += (offset as *const u8).align_offset(BPF_ALIGN_OF_U128);
(AccountInfo::new(key, is_signer, is_writable, lamports, data, owner, executable), offset)
}
Instruction Data और Program ID को Deserialize करना
सभी अकाउंट्स को डीसीरियलाइज करने के बाद, deserialize फ़ंक्शन इंस्ट्रक्शन डेटा पर आगे बढ़ता है। यह deserialize_instruction_data हेल्पर फ़ंक्शन को कॉल करता है। यह हेल्पर फ़ंक्शन इस प्रकार परिभाषित है:
// Instruction data
let (instruction_data, new_offset) = deserialize_instruction_data(input, offset);
offset = new_offset;
यह हेल्पर फ़ंक्शन 8-बाइट इंस्ट्रक्शन डेटा लेंथ को पढ़ता है, इंस्ट्रक्शन डेटा बाइट्स को पॉइंट करने वाला एक स्लाइस बनाता है, और स्लाइस व अपडेटेड ऑफसेट दोनों लौटाता है।
unsafe fn deserialize_instruction_data<'a>(input: *mut u8, mut offset: usize) -> (&'a [u8], usize) {
// Read the length of the instruction data (first 8 bytes at the current offset)
let instruction_data_len = *(input.add(offset) as *const u64) as usize;
// Move the offset past the u64 length field, so it now points to the start of the instruction data
offset += size_of::<u64>();
// Create a slice pointing to the instruction data bytes
let instruction_data = from_raw_parts(input.add(offset), instruction_data_len);
// Move the offset past the instruction data itself
offset += instruction_data_len;
// Return the instruction data slice and the updated offset
(instruction_data, offset)
}
अंत में, मुख्य डीसीरियलाइजेशन फ़ंक्शन (deserialize) प्रोग्राम ID (अंतिम 32 बाइट्स) निकालता है और वे तीनों कंपोनेंट्स लौटाता है जिनकी आपके प्रोग्राम को आवश्यकता होती है: प्रोग्राम ID, अकाउंट्स वेक्टर, और इंस्ट्रक्शन डेटा।
// Program Id
let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey);
हमने यह एक्सप्लोर किया कि Solana BPF Loader प्रोग्राम इंस्ट्रक्शन इनपुट्स को बाइट ऐरे में कैसे सीरियलाइज करता है और प्रोग्राम्स उन्हें प्रोग्राम ID, अकाउंट्स और इंस्ट्रक्शन डेटा में कैसे डीसीरियलाइज करते हैं। इस लेख के अगले भाग में, हम देखेंगे कि एक बार प्रोग्राम तक पहुँचने के बाद इन इनपुट्स का क्या होता है।
यह लेख Solana development पर एक ट्यूटोरियल सीरीज़ का हिस्सा है