Einführung in Solana
Bevor Sie auf Solana entwickeln, müssen Sie mehrere grundlegende Konzepte verstehen, die Solana einzigartig machen. Dieser Leitfaden behandelt Konten, Transaktionen, Programme und deren Interaktionen.
Konten auf Solana
Solanas Architektur konzentriert sich auf Konten: Datencontainer, die Informationen auf der Blockchain speichern. Stellen Sie sich Konten als einzelne Dateien in einem Dateisystem vor, wobei jede Datei spezifische Eigenschaften und einen Besitzer hat, der sie kontrolliert.
Jedes Solana-Konto hat die gleiche Grundstruktur:
pub struct Account {
/// lamports in the account
pub lamports: u64,
/// data held in this account
#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]
pub data: Vec<u8>,
/// the program that owns this account. If executable, the program that loads this account.
pub owner: Pubkey,
/// this account's data contains a loaded program (and is now read-only)
pub executable: bool,
/// the epoch at which this account will next owe rent
pub rent_epoch: Epoch,
}Jedes Konto hat eine eindeutige 32-Byte-Adresse, die als Base58-kodierte Zeichenfolge dargestellt wird (z.B. 14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5). Diese Adresse dient als Kennung des Kontos auf der Blockchain und ist der Weg, wie Sie bestimmte Daten lokalisieren.
Konten können bis zu 10 MiB Daten speichern, die entweder ausführbaren Programmcode oder programmspezifische Daten enthalten können.
Alle Konten benötigen eine Lamport-Einlage proportional zu ihrer Datengröße, um "mietfrei" zu werden. Der Begriff "Miete" ist historisch bedingt, da Lamports ursprünglich in jeder Epoche von Konten abgezogen wurden, aber diese Funktion ist jetzt deaktiviert. Heute funktioniert die Einlage eher wie eine rückzahlbare Kaution. Solange Ihr Konto das Mindestguthaben für seine Datengröße beibehält, bleibt es mietfrei und existiert auf unbestimmte Zeit. Wenn Sie ein Konto nicht mehr benötigen, können Sie es schließen und diese Einlage vollständig zurückerhalten.
Jedes Konto gehört einem Programm, und nur dieses besitzende Programm kann die Daten des Kontos ändern oder seine Lamports abheben. Allerdings kann jeder das Lamport-Guthaben eines Kontos erhöhen, was nützlich ist, um Operationen zu finanzieren oder Miete zu zahlen, ohne sich auf den Aufruf des Programms selbst verlassen zu müssen.
Die Signierungsberechtigung funktioniert je nach Eigentumsverhältnis unterschiedlich. Konten, die dem System-Programm gehören, können Transaktionen signieren, um ihre eigenen Daten zu ändern, das Eigentum zu übertragen oder gespeicherte Lamports zurückzufordern. Sobald das Eigentum auf ein anderes Programm übertragen wird, erhält dieses Programm die vollständige Kontrolle über das Konto, unabhängig davon, ob du noch im Besitz des privaten Schlüssels bist. Diese Kontrollübertragung ist dauerhaft und unumkehrbar.
Kontotypen
Der häufigste Kontotyp ist das System-Konto, das Lamports (die kleinste Einheit von SOL) speichert und dem System-Programm gehört. Diese funktionieren als grundlegende Wallet-Konten, mit denen Benutzer direkt zum Senden und Empfangen von SOL interagieren.
Token-Konten dienen einem speziellen Zweck, sie speichern SPL-Token-Informationen, einschließlich Eigentum und Token-Metadaten. Das Token-Programm besitzt diese Konten und verwaltet alle tokenbezogenen Operationen im gesamten Solana-Ökosystem. Token-Konten sind Datenkonten.
Datenkonten speichern anwendungsspezifische Informationen und gehören benutzerdefinierten Programmen. Diese Konten enthalten den Status deiner Anwendung und können beliebig strukturiert werden, je nach Anforderungen deines Programms, von einfachen Benutzerprofilen bis hin zu komplexen Finanzdaten.
Schließlich enthalten Programmkonten den ausführbaren Code, der auf Solana läuft, in dem der Smart Contract lebt. Diese Konten sind als executable: true markiert und speichern die Programmlogik, die Anweisungen verarbeitet und den Status verwaltet.
Arbeiten mit Kontodaten
So interagieren Programme mit Kontodaten:
#[derive(BorshSerialize, BorshDeserialize)]
pub struct UserAccount {
pub name: String,
pub balance: u64,
pub posts: Vec<u32>,
}
pub fn update_user_data(accounts: &[AccountInfo], new_name: String) -> ProgramResult {
let user_account = &accounts[0];
// Deserialize existing data
let mut user_data = UserAccount::try_from_slice(&user_account.data.borrow())?;
// Modify the data
user_data.name = new_name;
// Serialize back to account
user_data.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
Ok(())
}Im Gegensatz zu Datenbanken, in denen du einfach Datensätze einfügst, müssen Solana-Konten vor der Verwendung explizit erstellt und finanziert werden.
Transaktionen auf Solana
Solana-Transaktionen sind atomare Operationen, die mehrere Anweisungen enthalten können. Alle Anweisungen innerhalb einer Transaktion werden entweder gemeinsam erfolgreich ausgeführt oder schlagen gemeinsam fehl: Es gibt keine teilweise Ausführung.
Eine Transaktion besteht aus:
Anweisungen: Einzelne auszuführende Operationen
Konten: Spezifische Konten, von denen jede Anweisung liest oder auf die sie schreibt
Unterzeichner: Konten, die die Transaktion autorisieren
Transaction {
instructions: [
// Instruction 1: Transfer SOL
system_program::transfer(from_wallet, to_wallet, amount),
// Instruction 2: Update user profile
my_program::update_profile(user_account, new_name),
// Instruction 3: Log activity
my_program::log_activity(activity_account, "transfer", amount),
],
accounts: [from_wallet, to_wallet, user_account, activity_account]
signers: [user_keypair],
}Transaktionsanforderungen und Gebühren
Transaktionen sind auf insgesamt 1.232 Bytes begrenzt, was einschränkt, wie viele Anweisungen und Konten du einbeziehen kannst.
Jede Anweisung innerhalb einer Transaktion erfordert drei wesentliche Komponenten: die Programmadresse, die aufgerufen werden soll, alle Konten, von denen die Anweisung lesen oder auf die sie schreiben wird, und alle zusätzlichen Daten wie Funktionsargumente.
Anweisungen werden sequentiell in der von dir angegebenen Reihenfolge innerhalb der Transaktion ausgeführt.
Für jede Transaktion fällt eine Grundgebühr von 5.000 Lamports pro Signatur an, um Validatoren für die Verarbeitung deiner Transaktion zu entschädigen.
Du kannst auch eine optionale Priorisierungsgebühr zahlen, um die Wahrscheinlichkeit zu erhöhen, dass der aktuelle Leader deine Transaktion schnell verarbeitet. Diese Priorisierungsgebühr wird als dein Compute-Unit-Limit multipliziert mit deinem Compute-Unit-Preis (gemessen in Mikro-Lamports) berechnet.
prioritization_fee = compute_unit_limit × compute_unit_pricePrograms on Solana
Programme auf Solana sind grundsätzlich zustandslos, was bedeutet, dass sie zwischen Funktionsaufrufen keinen internen Zustand beibehalten. Stattdessen erhalten sie Konten als Eingabe, verarbeiten die Daten innerhalb dieser Konten und geben die modifizierten Ergebnisse zurück.
Dieses zustandslose Design gewährleistet vorhersehbares Verhalten und ermöglicht leistungsstarke Kompositionsmuster.
Die Programme selbst werden in speziellen Konten gespeichert, die als executable: true markiert sind und den kompilierten Binärcode enthalten, der bei Aufruf ausgeführt wird.
Benutzer interagieren mit diesen Programmen, indem sie Transaktionen senden, die spezifische Anweisungen enthalten, die jeweils bestimmte Programmfunktionen mit den notwendigen Kontodaten und Parametern ansprechen.
use solana_program::prelude::*;
#[derive(BorshSerialize, BorshDeserialize)]
pub struct User {
pub name: String,
pub created_at: i64,
}
pub fn create_user(
accounts: &[AccountInfo],
name: String,
) -> ProgramResult {
let user_account = &accounts[0];
let user = User {
name,
created_at: Clock::get()?.unix_timestamp,
};
user.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
Ok(())
}Programme können durch ihre festgelegte Upgrade-Autorität aktualisiert werden, was Entwicklern ermöglicht, Fehler zu beheben und Funktionen nach der Bereitstellung hinzuzufügen. Das Entfernen dieser Upgrade-Autorität macht das Programm jedoch dauerhaft unveränderlich und bietet Benutzern die Garantie, dass sich der Code niemals ändern wird.
Für Transparenz und Sicherheit können Benutzer überprüfen, ob On-Chain-Programme mit ihrem öffentlichen Quellcode übereinstimmen, indem sie verifizierbare Builds verwenden, die sicherstellen, dass der bereitgestellte Bytecode genau dem veröffentlichten Quellcode entspricht.
Program Derived Addresses (PDAs)
PDAs sind deterministisch generierte Adressen, die leistungsstarke Programmiermuster ermöglichen. Sie werden mithilfe von Seeds und einer Programm-ID erstellt und erzeugen Adressen ohne entsprechende private Schlüssel.
PDAs verwenden SHA-256 Hashing mit spezifischen Eingaben, darunter Ihre benutzerdefinierten Seeds, einen Bump-Wert, um sicherzustellen, dass das Ergebnis außerhalb der Kurve liegt, die Programm-ID, die den PDA besitzen wird, und eine konstante Markierung.
Wenn der Hash eine On-Curve-Adresse erzeugt (was etwa 50% der Zeit passiert), iteriert das System von Bump 255 abwärts zu 254, 253 und so weiter, bis ein Off-Curve-Ergebnis gefunden wird.
use solana_nostd_sha256::hashv;
const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
let pda = hashv(&[
seed_data.as_ref(), // Your custom seeds
&[bump], // Bump to ensure off-curve
program_id.as_ref(), // Program that owns this PDA
PDA_MARKER,
]);Vorteile
Die deterministische Natur von PDAs beseitigt die Notwendigkeit, Adressen zu speichern: Sie können sie bei Bedarf aus denselben Seeds neu generieren.
Dies schafft vorhersehbare Adressierungsschemata, die wie Hashmap-Strukturen on-chain funktionieren. Noch wichtiger ist, dass Programme für ihre eigenen PDAs signieren können, was eine autonome Vermögensverwaltung ermöglicht, ohne private Schlüssel preiszugeben:
let seeds = &[b"vault", user.as_ref(), &[bump]];
invoke_signed(
&transfer_instruction,
&[from_pda, to_account, token_program],
&[&seeds[..]], // Program proves PDA control
)?;Cross Program Invocation (CPI)
CPIs ermöglichen es Programmen, andere Programme innerhalb derselben Transaktion aufzurufen, was eine echte Komponierbarkeit schafft, bei der mehrere Programme atomar interagieren können, ohne externe Koordination.
Dies ermöglicht Entwicklern, komplexe Anwendungen zu erstellen, indem sie bestehende Programme kombinieren, anstatt Funktionalität von Grund auf neu zu entwickeln.
CPIs folgen dem gleichen Muster wie reguläre Anweisungen und erfordern die Angabe des Zielprogramms, der benötigten Konten und der Anweisungsdaten. Der Hauptunterschied besteht darin, dass sie innerhalb anderer Programme ausgeführt werden können.
Das aufrufende Programm behält die Kontrolle über den Ablauf, während es bestimmte Operationen an spezialisierte Programme delegiert:
let cpi_accounts = TransferAccounts {
from: source_account.clone(),
to: destination_account.clone(),
authority: authority_account.clone(),
};
let cpi_ctx = CpiContext::new(token_program.clone(), cpi_accounts);
token_program::cpi::transfer(cpi_ctx, amount)?;Einschränkungen und Fähigkeiten
Die ursprünglichen Transaktionsunterzeichner behalten ihre Autorität über die gesamte CPI-Kette hinweg, wodurch Programme nahtlos im Namen von Benutzern handeln können.
Programme können jedoch nur CPIs bis zu einer Tiefe von 4 Ebenen (A → B → C → D) ausführen, um unendliche Rekursion zu verhindern. Programme können auch für ihre PDAs in CPIs mit CpiContext::new_with_signer signieren, was komplexe autonome Operationen ermöglicht.
Diese Komponierbarkeit ermöglicht komplexe Operationen über mehrere Programme hinweg innerhalb einer einzigen atomaren Transaktion, wodurch Solana-Anwendungen hochgradig modular und interoperabel werden.