Solana Anchor में #[derive(Accounts)] structs के लिए एक attribute-like macro है जो उन सभी accounts के references रखता है जिन्हें function अपने execution के दौरान access करेगा।
Solana में, transaction द्वारा access किए जाने वाले हर account को पहले से specify करना आवश्यक है
Solana के इतना तेज़ होने का एक कारण यह है कि यह transactions को parallel में execute करता है। यानी, अगर Alice और Bob दोनों एक transaction करना चाहते हैं, तो Solana उनके transactions को एक साथ process करने की कोशिश करेगा। हालाँकि, अगर उनके transactions समान storage को access करके conflict करते हैं, तो एक समस्या उत्पन्न होती है। उदाहरण के लिए, मान लें कि Alice और Bob दोनों एक ही account में write करने का प्रयास कर रहे हैं। स्पष्ट रूप से उनके transactions parallel में रन नहीं किए जा सकते।
Solana को यह पता चलने के लिए कि Alice और Bob के transaction को parallelize नहीं किया जा सकता, Alice और Bob दोनों को पहले से उन सभी accounts को specify करना होगा जिन्हें उनका transaction update करेगा।
चूँकि Alice और Bob दोनों एक (storage) account specify करते हैं, Solana runtime यह अनुमान लगा सकता है कि दोनों transactions conflict करते हैं। किसी एक को चुना जाना चाहिए (संभवतः उसे जिसने अधिक priority fee का भुगतान किया हो), और दूसरा विफल हो जाएगा।
यही कारण है कि प्रत्येक function का अपना अलग #[derive(Accounts)] struct होता है। struct का प्रत्येक field एक account है जिसे program execution के दौरान access करने का इरादा रखता है (लेकिन ऐसा करना अनिवार्य नहीं है)।
कुछ Ethereum developers इस आवश्यकता और EIP 2930 access list transactions के बीच समानता पर ध्यान दे सकते हैं।
Account का प्रकार Anchor को यह संकेत देता है कि आप account के साथ किस तरह interact करने का इरादा रखते हैं।
वे Account types जिनका आप सबसे अधिक उपयोग करेंगे: Account, Unchecked Account, System Program, और Signer
Storage को initialize करने के हमारे code में, हमने तीन अलग-अलग “प्रकार” के accounts देखे:
AccountSignerProgram
यहाँ वह code फिर से दिया गया है:

और जब हमने किसी account का balance पढ़ा, तो हमने एक चौथा प्रकार देखा:
UncheckedAccount
यहाँ वह code है जिसका हमने उपयोग किया:

हरे बॉक्स के साथ हाईलाइट किए गए प्रत्येक आइटम को फ़ाइलों के शीर्ष पर anchor_lang::prelude::*; के माध्यम से import किया गया है।
Account, UncheckedAccount, Signer, और Program का उद्देश्य आगे बढ़ने से पहले पास किए गए account पर किसी प्रकार की जाँच (check) करना है, और साथ ही उन accounts के साथ interact करने के लिए functions को expose करना है।
हम निम्नलिखित अनुभागों (sections) में इन चारों प्रकारों में से प्रत्येक के बारे में विस्तार से बताएंगे।
Account
Account type यह चेक करेगा कि लोड किया जा रहा account वास्तव में program के स्वामित्व (owned) में है या नहीं। यदि owner मेल नहीं खाता है, तो यह लोड नहीं होगा। यह एक महत्वपूर्ण सुरक्षा उपाय (safety measure) के रूप में कार्य करता है ताकि गलती से ऐसा डेटा न पढ़ा जाए जिसे program ने नहीं बनाया है।
निम्नलिखित उदाहरण में, हम एक keypair account बनाते हैं और इसे foo को पास करने का प्रयास करते हैं। चूँकि account program के स्वामित्व में नहीं है, इसलिए transaction विफल (fail) हो जाता है।
Rust:
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");
#[program]
pub mod account_types {
use super::*;
pub fn foo(ctx: Context<Foo>) -> Result<()> {
// we don't do anything with the account SomeAccount
Ok(())
}
}
#[derive(Accounts)]
pub struct Foo<'info> {
some_account: Account<'info, SomeAccount>,
}
#[account]
pub struct SomeAccount {}
Typescript:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor
.getProvider()
.connection.requestAirdrop(
publicKey,
amount * anchor.web3.LAMPORTS_PER_SOL
);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor
.getProvider()
.connection.getLatestBlockhash();
await anchor
.getProvider()
.connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("Wrong owner with Account", async () => {
const newKeypair = anchor.web3.Keypair.generate();
await airdropSol(newKeypair.publicKey, 10);
await program.methods
.foo()
.accounts({someAccount: newKeypair
.publicKey}).rpc();
});
});
यहाँ tests को execute करने का आउटपुट दिया गया है:

यदि हम Account में एक init macro जोड़ते हैं, तो यह system program से इस program में ownership ट्रांसफर करने का प्रयास करेगा। हालाँकि, ऊपर दिए गए code में init macro नहीं है।
Account type के बारे में अधिक जानकारी docs में पाई जा सकती है: https://docs.rs/anchor-lang/latest/anchor_lang/accounts/account/struct.Account.html
UncheckedAccount या AccountInfo
UncheckedAccount, AccountInfo के लिए एक alias है। यह ownership की जाँच नहीं करता है, इसलिए सावधानी बरतनी चाहिए क्योंकि यह मनमाने (arbitrary) accounts को स्वीकार कर लेगा।
यहाँ UncheckedAccount का उपयोग करके उस account का डेटा पढ़ने का एक उदाहरण दिया गया है जिसका वह owner नहीं है।
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");
#[program]
pub mod account_types {
use super::*;
pub fn foo(ctx: Context<Foo>) -> Result<()> {
let data = &ctx.accounts.some_account.try_borrow_data()?;
msg!("{:?}", data);
Ok(())
}
}
#[derive(Accounts)]
pub struct Foo<'info> {
/// CHECK: we are just printing the data
some_account: AccountInfo<'info>,
}
यहाँ हमारा Typescript code है। ध्यान दें कि हम keypair account बनाने के लिए सीधे system program को कॉल कर रहे हैं ताकि हम 16 bytes का डेटा allocate कर सकें।
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
const wallet = anchor.workspace.AccountTypes.provider.wallet;
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("Load account with accountInfo", async () => {
// CREATE AN ACCOUNT NOT OWNED BY THE PROGRAM
const newKeypair = anchor.web3.Keypair.generate();
const tx = new anchor.web3.Transaction().add(
anchor.web3.SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: newKeypair.publicKey,
space: 16,
lamports: await anchor
.getProvider()
.connection
.getMinimumBalanceForRentExemption(32),
programId: program.programId,
})
);
await anchor.web3.sendAndConfirmTransaction(
anchor.getProvider().connection,
tx,
[wallet.payer, newKeypair]
);
// READ THE DATA IN THE ACCOUNT
await program.methods
.foo()
.accounts({ someAccount: newKeypair.publicKey })
.rpc();
});
});
Program के रन होने के बाद, हम देख सकते हैं कि इसने account में मौजूद डेटा को प्रिंट कर दिया है, जिसमें 16 शून्य बाइट्स (zero bytes) हैं:
Transaction executed in slot 14298:
Signature: 64fv6NqYB4tji9UfLpH8PgFDY1QV4vbMovrnnpw3271vStg7J5g1z1bm9YbE8Lobzozkc6y2YzLdgMjGdftCGKqv
Status: Ok
Log Messages:
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs invoke [1]
Program log: Instruction: Foo
Program log: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs consumed 5334 of 200000 compute units
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs success
जब हम एक arbitrary address पास कर रहे थे तब हमें इस account type का उपयोग करने की आवश्यकता थी, लेकिन डेटा का उपयोग कैसे किया जाता है, इस बारे में बहुत सावधान रहें, क्योंकि एक हैकर किसी account में malicious डेटा तैयार (craft) कर सकता है और फिर उसे Solana program को पास कर सकता है।
Signer
यह type यह चेक करेगा कि Signer account ने transaction पर sign किया है; यह जाँचता है कि signature account की public key से मेल खाता है या नहीं।
चूँकि एक signer भी एक account होता है, आप account में स्टोर किए गए Signer के balance या डेटा (यदि कोई हो) को पढ़ सकते हैं, हालाँकि इसका मुख्य उद्देश्य signatures को validate करना है।
Docs https://docs.rs/anchor-lang/latest/anchor_lang/accounts/signer/struct.Signer.html के अनुसार, Signer एक प्रकार (type) है जो यह validate करता है कि account ने transaction पर sign किया है। कोई अन्य ownership या type checks नहीं किए जाते हैं। यदि इसका उपयोग किया जाता है, तो अंतर्निहित (underlying) account डेटा को access करने का प्रयास नहीं करना चाहिए।
Rust उदाहरण:
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");#
[program]
pub mod account_types {
use super::*;
pub fn hello(ctx: Context<Hello>) -> Result<()> {
let lamports = ctx.accounts.signer.lamports();
let address = &ctx.accounts
.signer
.signer_key().unwrap();
msg!(
"hello {:?} you have {} lamports",
address,
lamports
);
Ok(())
}
}
#[derive(Accounts)]
pub struct Hello<'info> {
pub signer: Signer<'info>,
}
Typescript:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("Wrong owner with Account", async () => {
await program.methods.hello().rpc();
});
});
यहाँ program का आउटपुट दिया गया है:
Transaction executed in slot 11184:
Signature: 4xipobKHHp7a3N4durXN4YPGUesDAJNg7wsatBemdJAm7U1dXYG3gveLwnuY39iCTEZvaj6nnAViVJwDS8124uJJ
Status: Ok
Log Messages:
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs invoke [1]
Program log: Instruction: Hello
Program log: hello 5jmigjgt77kAfKsHri3MHpMMFPo6UuiAMF19VdDfrrTj you have 499999994602666000 lamports
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs consumed 13096 of 200000 compute units
Program ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs success
Program
यह स्वतः स्पष्ट (self-explanatory) होना चाहिए। यह Anchor को संकेत देता है कि account एक executable है, यानी एक program है, और आप इसे एक cross program invocation जारी कर सकते हैं। जिसका हम उपयोग कर रहे हैं वह system program है, हालाँकि बाद में हम अपने स्वयं के programs का उपयोग करेंगे।
अधिक जानें
हमारे Ethereum से Solana कोर्स में Learn Solana डेवलपमेंट।
मूल रूप से 6 अप्रैल, 2024 को प्रकाशित