◎
2022-02-02 (Draft)
One can think of Solana as an unusually large band that is perpetually performing live music at a concert. Every song the band plays is composed by the band’s lead player from musical notes written on paper tokens. The tokens are given to the lead player by the spectators (users), the other band members have to validate that they can harmoniously play the song and only then is the song played and added to the band’s album (ledger).
It is the responsibility of the band leader to compose these notes in
a way that can be harmoniously played by the rest of the band.
The band members practice the same song from the notes they got from the band leader, if they are satisfied with the song (block) produced and it matches up with the song by the band leader (note composition) they cast their vote for the song. These votes are used by the band to decide wheter to play the song or not (add it to the album (ledger)).
A new leader is elected every 10 seconds.
Solana | Analogy |
---|---|
Cluster | Band |
Users | Spectators |
Node/ Validator | Band member |
Leader | Band Lead Player |
Ledger (Blockchain) | Album |
Block | Song |
Blockhash | Song title |
Transaction | Note |
Program(Smart contract) | Band conductors |
Instruction | Instructions for conductors |
Accounts | Concert Tables for spectators (Desks) |
SOL/lamports | Paper Tokens for writing the ‘Notes’ (Raffle tickets) |
A particular band that plays music together.
A cluster is a network of computers working together to validate transactions.
Cluster | Purpose | URL |
---|---|---|
Testnet (Rehersal in your garage) | Solana blockchain new feature tests | https://api.testnet.solana.com |
Devnet (Rehersal before the show) | Developer playground | https://api.devnet.solana.computers |
Mainet Beta (Performing a Live concert) | Real deal, real money | https://api.mainnet-beta.solana.com |
More info here.
The concert spectators are the ones who give notes to the band, the
band leader then composes a song that will be harmoniously
played by the rest of the band.
The spectators are the users of the cluster, they have
transactions(notes) that they want to be part of the band's album
(cluster's ledger). These transactions can only be included if
all nodes reach consensus via Proof of Stake and Proof of History.
Technically a Transaction
like everything else on a
computer is merely a string of bits, the size of a transaction cannot
exceed 9856 bits (1232 bytes). A
transaction consists of two parts Signatures
and a
Message
:
Solana Transactions can include multiple signatures. All the
Account
s that will be modified by the transaction should
sign the transaction before it is sent to the cluster.
Each digital signature is in the ed25519 binary format and consumes 64 bytes.
In the holy language of Rust this would be described as:
//...
pub signatures: Vec<Signature>,
//...
The message itself consists of 4 parts the:
Header
Account addresses
Blockhash
Instructions
# = number of
bits
+-----------------------------------------------+ \
8 | # required signatures | |
+-----------------------------------------------+ |
8 | # accounts to be read-only | |~Header
+-----------------------------------------------+ |
8 | # read-only accounts not requiring signatures | |
+-----------------------------------------------+ /
The first 8 bits of the header should correspond to the number of signatures that signed the transactions (owners of accounts to be modified).
The next 8 bits indicates the accounts that are not modified by the transaction but are read only.
The last 8 bits of the header is the number of read-only accounts not requiring signatures.
In Rust the message header is defined as:
pub struct MessageHeader {
pub num_required_signatures: u8,
pub num_readonly_signed_accounts: u8,
pub num_readonly_unsigned_accounts: u8,
}
This is an array, the accounts that have signed the transaction appear first followed by the others:
//...
pub account_keys: Vec<Pubkey, Global>,
//...
Before the music spectators suggests gives the band a note to play, the
spectator should make sure they have listened to the bands recent
songs this lets the band know that the spectator is a true fan.
A blockhash is the title of a specific song (block) by the band.
More concretely a blockhash is a 32-byte SHA-256 hash of a recent block.
//...
pub recent_blockhash: Hash,
//...
As we said before the band is told what to play by the spectators
based on the notes they give to the band. The band also has somewhat
dormantconductors. These conductors follow specific set of
instructions also given by the listeners.
The conductors are the Solana programs (smart contracts), the
instructions are hardcoded in the programs logic and users can access
these instructions via transactions.
A Instruction
consists of a index (pointer) to the one
of the accounts In the message’s Account
array, account
addresses which are multiple indexes to In the message’s
Account
array and an 8bit array of generic data.
var = varies
bits
+--------------------------+ \
8 | Program ID (Index) | |
+--------------------------+ |
var | Account Address Indexes | |~Instruction
+--------------------------+ |
8 | Opaque data | |
+--------------------------+ /
In the Rust it looks like this:
pub struct CompiledInstruction {
pub program_id_index: u8,
pub accounts: Vec<u8, Global>,
pub data: Vec<u8, Global>,
}
pub struct Transaction {
pub signatures: Vec<Signature>,
pub message: Message,
}
Every spectator at the concert sits at a specific table, each table
holds various objects one of them being a busket of paper tokens on
which the spectator writes down notes they want the band to play,
which they hand over to the band leader.
Users can have accounts (tables) with a 256-bit public key as the account’s address. These accounts hold state between transactions. Every account has an owner (Spectator sitting at the table). Every account also has an amount of lamports that they own (paper tokens on which notes are written).
Because this is a very popular band, and there is limited space at the
venue the concert is being held, the band came up with a brilliant
idea to allow every fan to get a chance to see them live and to
maximize their profits (:
Once a spectator's paper tokens finish the spectator is removed from
the table by the band. The table is cleaned up and room is made for
someone else waiting outside. To speed up things each band member
periodically collects tokens from the tables, unless it is a VIP
table (a table that has bought a substantial amount of tokens).
Once an account’s amount of lamports is 0 it will be removed from the cluster.
Accounts also pay rent for storage to validators, the validators do this by periodically collecting lamports from all the accounts in the cluster, except for accounts that hold at least 2 years worth of rent, those do not pay rent.
The spectator writes the notes they want to hear on a paper token, but
before they hand it over to the band leader the spectator has to sign
it with their unique signature. The signature allows the band to
verify that the notes are indeed from a the spectator sitted at the
specific table.
A transaction should include the account’s digital signature, this indicates that the transaction was sent by the owner of the account (holder of private key that corresponds to the account’s public key).
This is how an account is structured in RUST:
pub struct Account {
/// lamports in the account
pub lamports: u64,
/// data held in this account
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,
}
Not only does the band get notes to play from the concert spectators
but they can also get special instructions from multiple
band conductors, these instructions are also based on the notes from
the spectators.
The conductors are Solana programs aka smart contracts (Accounts marked as “executable”), which live on the blockchain, users can use these programs by sending special instructions to the cluster that call specific programs, these instructions are embedded in the transactions from the users.
Anchor is a Rust framework for writing Solana programs, we will use it to write Solana programs.
To use Anchor in you Rust project include it in your
Cargo.toml
file and then import its prelude:
// use this import to gain access to common anchor features
use anchor_lang::prelude::*;
Every conductor sits at a table from where it gives out instructions to
the band. The table has a unique ID.
In Anchor a program’s ID is declared using the
declare_id
macro:
// declare an id for your program
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
Each conductors has its own distinct list of instructions that it can
give to the band.
Programs can instruct the cluster using instructions.
The #[program]
macro is used on a Rust module that holds
a list of functions, these functions are the instruction handlers, they
can be called by Solana clients (Anchor Rust
Client, Anchor
Typescript Client):
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod my_program {
//! This module will contain a list of functions to handle
}
The #[account]
attribute is used on structs that hold
data owned by the program (the program identified with the ID declared
with the declareId
macro):
#[account]
#[derive(Default)]
pub struct MyAccount {
: u64
data}
Every call to an instruction should be accompanied by data that is
deserialzed from a struct marked with the
#[derive(Accounts)]
attribute:
#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>
}
Putting #[account]
, #[derive(Accounts)]
and
instructions together:
//...
#[program]
mod my_program {
use super::*;
pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
.accounts.my_account.data = data;
ctxOk(())
}
}
#[account]
#[derive(Default)]
pub struct MyAccount {
: u64
data}
#[derive(Accounts)]
pub struct SetData<'info> {
#[account(mut)]
pub my_account: Account<'info, MyAccount>
}
We can give various constraints to the
#[account]
attribute, here are a few commonly used
constraints:
Constraint | Description |
---|---|
#[account(mut)] |
The given account should be mutable |
#[account(constraint = <expr>)] |
Checks whether the given expression evaluates to true |
Field | Value |
---|---|
Network speed | 1 gbps network |
Max Throughput | 710k Transactions per seconds |
Native Token | SOL |
Fractional amount | lamports (0.000000001 SOL) |
Max transaction size | 1232 bytes |
Signature Format | ed25519 digital signature (64 bytes) |
blockhash | SHA-256 (32-byte) |
updated: 2022-12-31 14:16:42.893587258 UTC