Solana Algo-Rythm

Last updated: 2022-02-20

·

9 min read

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.

Analogy Table

SolanaAnalogy
ClusterBand
UsersSpectators
Node/ ValidatorBand member
LeaderBand Lead Player
Ledger (Blockchain)Album
BlockSong
BlockhashSong title
TransactionNote
Program(Smart contract)Band conductors
InstructionInstructions for conductors
AccountsConcert Tables for spectators (Desks)
SOL/lamportsPaper Tokens for writing the 'Notes' (Raffle tickets)

Bands (Clusters)

A particular band that plays music together.

A cluster is a network of computers working together to validate transactions.

ClusterPurposeURL
Testnet (Rehersal in your garage)Solana blockchain new feature testshttps://api.testnet.solana.com
Devnet (Rehersal before the show)Developer playgroundhttps://api.devnet.solana.computers
Mainet Beta (Performing a Live concert)Real deal, real moneyhttps://api.mainnet-beta.solana.com

More info here.


Notes (Transactions)

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:

Signatures

Solana Transactions can include multiple signatures. All the Accounts 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.

~ Official docs

In the holy language of Rust this would be described as:

//...
pub signatures: Vec<Signature>,
//...

Message

The message itself consists of 4 parts the:

  • Header
  • Account addresses
  • a recent Blockhash
  • Instructions

Header (Metadata)

 # = 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,
}

source

Accounts

This is an array, the accounts that have signed the transaction appear first followed by the others:

//...
 pub account_keys: Vec<Pubkey, Global>,
//...

source

Blockhash

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,
//...

>

source

Instructions

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>,
}

source

Putting it together

pub struct Transaction {
    pub signatures: Vec<Signature>,
    pub message: Message,
}

source


Accounts

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).

Rent

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.

Signing

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).

Account Structure

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,
}

source


Conductors (Programs)

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::*;

(Conductors Table ID) Program ID

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");

source

Program Logic

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 
}

Program Instruction and Accounts

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 {
    data: u64
}

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 {
        ctx.accounts.my_account.data = data;
        Ok(())
    }
}

#[account]
#[derive(Default)]
pub struct MyAccount {
    data: u64
}

#[derive(Accounts)]
pub struct SetData<'info> {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>
}

Constraints

We can give various constraints to the #[account] attribute, here are a few commonly used constraints:

ConstraintDescription
#[account(mut)]The given account should be mutable
#[account(constraint = <expr>)]Checks whether the given expression evaluates to true

More Info


Specs

FieldValue
Network speed1 gbps network
Max Throughput710k Transactions per seconds
Native TokenSOL
Fractional amountlamports (0.000000001 SOL)
Max transaction size1232 bytes
Signature Formated25519 digital signature (64 bytes)
blockhashSHA-256 (32-byte)

References