DEV Community

Cover image for Decoding Starknet Part One: What is Blockchain, and why Starknet?
Cyndie Kamau
Cyndie Kamau

Posted on

Decoding Starknet Part One: What is Blockchain, and why Starknet?

_Hello Guys!!! Its Cyndie here... Ready to take you to the magical journey about blockchain, and introducing you to Starknet!!!

A Starknet Samurai!

So what is Blockchain?

Many experts define blockchain as "a decentralized ledger" which while accurate, sounds intimidating!! When I began my blockchain journey, that definition was quite confusing! Instead, I'm going to give you a practical rundown, the way I wish someone had explained it to me.

An illustration of a simple blockchain system

Welcome to Cyndie's Blockchain - a simple blockchain system I crafted using Rust. This illustration will help clarify concepts like Proof of Work, hashing, and how blocks are added to the chain.

Here's the Rust program I wrote for this purpose:

/*
=> This Rust program implements a simple version of a blockchain.

=> The 'Block' struct reps a block in the blockchain.
It contains an index, a timestamp, data, a hash of its own contents, and the hash of the previous block. 
The 'new_block' function is used to create new blocks, calculating their hash using the 'calculate_hash' function.

=> N.B! I added a mine functionality to allow me to add new blocks. 

Made by Cyndie :)  
*/



// Import SystemTime and UNIX_EPOCH from the std::time module
// These will be used to create timestamps for each block
use std::time::{SystemTime, UNIX_EPOCH};   
use sha2::{Sha256, Digest}; //for sha256 hashing

// I set the difficulty of my Proof of Work(POW) algorithm to 4
const DIFFICULTY: usize = 4;  

#[derive(Debug)]

//the structure of our blockchain
struct Block {
    index: u32,
    timestamp: u64,
    data: String,
    current_hash: String,
    previous_hash: String,
    nonce: u64,  // Add a nonce to your block
}

impl Block {
    fn new_block(index: u32, timestamp: u64, data: String, previous_hash: String) -> Block {
        let mut block = Block {
            index,
            timestamp,
            data,
            current_hash: String::new(),  // Will be set in `mine`
            previous_hash,
            nonce: 0,  // Initial nonce value
        };
        block.mine();
        block
    }

    //function to calculate the block's hash
    fn calculate_hash(&self) -> String {
        let input = format!("{}  {}  {}  {}  {}", self.index, self.timestamp, self.data, self.previous_hash, self.nonce);
        let mut s = Sha256::new();
        s.update(input);
        let result = s.finalize();
        format!("{:x}", result)
    }

    // Mining function 
    // Adds nonce and calculate block's hash till it finds a hash that will satisfy our difficulty level.
    fn mine(&mut self) {
        //loop iterating from 0 to max u64 value finding nonce to solve our puzzle
        for nonce_attempt in 0..(u64::MAX) { 
            self.nonce = nonce_attempt;  //each iteration nonce attempt is set as current nonce
            let hash = self.calculate_hash();  // we calc hash using the set nonce
            if self.is_hash_valid(&hash) {  //we then check if the hash is valid
                self.current_hash = hash;  // if hash valid, its set to the current block
                return;
            }
        }
    }


    //function to check whether the hash calculated with the attempted nonce is valid or not
    fn is_hash_valid(&self, hash: &String) -> bool {
        &hash[0..DIFFICULTY] == "0".repeat(DIFFICULTY) //checks if our hash has 4 0s as per difficulty
    }
}

fn main() {
    let mut blocks = Vec::new();

    let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
    let previous_hash = "0".to_string();
    let first_block = Block::new_block(0, timestamp, "First Block".to_string(), previous_hash);

    blocks.push(first_block);
    println!("{:?}", blocks[0]);  

    for i in 1..10 {
        println!("Type 'exit' to quit");

        println!("Please enter data for block number {}: ", i);

        let mut data = String::new();
        std::io::stdin().read_line(&mut data).expect("Unable to read your input");
        let data = data.trim().to_string();

        if data.trim() == "exit" {
            println!("See yah later!");
            break;
        }

        let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        let previous_hash = blocks[(i-1) as usize].current_hash.clone();
        let new_block = Block::new_block(i, timestamp, data, previous_hash);
        blocks.push(new_block);

        println!("{:?}", blocks[i as usize]); 
    }
}

Enter fullscreen mode Exit fullscreen mode

When executed, here's how the program runs:

hp@Cyndie:~/Desktop/rust/mine_new_block/src$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.10s
     Running `/home/hp/Desktop/rust/mine_new_block/target/debug/mine_new_block`
Block { index: 0, timestamp: 1691412688, data: "First Block", current_hash: "00002442db38232d2286f93228be32243a9c444abfa87aa31425333a0a0132a8", previous_hash: "0", nonce: 51494 }
Type 'exit' to quit
Please enter data for block number 1: 
My name is Cyndie    
Block { index: 1, timestamp: 1691412712, data: "My name is Cyndie", current_hash: "00005840c43d22ea43827e0f62796373ad3025f159ea1ae8b3a972ffc9cacb71", previous_hash: "00002442db38232d2286f93228be32243a9c444abfa87aa31425333a0a0132a8", nonce: 94541 }
Type 'exit' to quit
Please enter data for block number 2: 
12345567jjsnsns
Block { index: 2, timestamp: 1691412733, data: "12345567jjsnsns", current_hash: "0000fb44436fd1395119af65ea30be1919e6bfb052620de41e75ca5206f0bab9", previous_hash: "00005840c43d22ea43827e0f62796373ad3025f159ea1ae8b3a972ffc9cacb71", nonce: 7715 }
Type 'exit' to quit
Please enter data for block number 3: 
Alice sent to John 5 btc
Block { index: 3, timestamp: 1691412766, data: "Alice sent to John 5 btc", current_hash: "00004a61d991d3fa4310743f5b27fabb1854eff1461e034ac313b95ffee3afbb", previous_hash: "0000fb44436fd1395119af65ea30be1919e6bfb052620de41e75ca5206f0bab9", nonce: 55047 }
Type 'exit' to quit
Please enter data for block number 4: 
exit
See yah later!
Enter fullscreen mode Exit fullscreen mode

Breaking It Down

In blockchain, think of each block as a page in a ledger or diary. When you have a new piece of data, like a transaction, you add it to a new page or block. The first page, known as the 'Genesis Block', looks something like this:

Block { index: 0, timestamp: 1691412688, data: "First Block", current_hash: "00002442db38232d2286f93228be32243a9c444abfa87aa31425333a0a0132a8", previous_hash: "0", nonce: 51494 }
Enter fullscreen mode Exit fullscreen mode

This block doesn't refer to any prior data, which is why the previous_hash: "0".

Now, before adding this block, we need a unique digital signature for the data, known as a 'hash'. This hash is generated through algorithms like Proof of Work or Proof of Stake. Example, our Genesis Block's hash is 00002442db38232d2286f93228be32243a9c444abfa87aa31425333a0a0132a8.

For instance, in my Rust program, the difficulty level is set to 4:

const DIFFICULTY: usize = 4; 
Enter fullscreen mode Exit fullscreen mode

To get this, our computer has to solve a riddle, setting the stage for what we call mining. In our Rust example, the computer must find a number (nonce) that, when combined with our data, produces a hash with a specific pattern - in this case, starting with four zeros!

But why do they do this? In real-world blockchains like Bitcoin, the first to solve the riddle gets to process the block's transactions and pockets some cool transaction fees. 💸

This encourages folks to lend their computer power to maintain the network, ensuring the decentralization of blockchain.

Within the blockchain universe, there are primary networks referred to as Layer 1s, with Bitcoin and Ethereum being paramount. In this piece, we'll focus our attention on Ethereum. For those curious about the Proof of Stake mechanism, you can read more here.

Why Starknet?

Ethereum is currently grappling with what's termed as the L1 Scaling Challenge, and here's why:

Starknet illustration of Ethereum validators

So as previously explained above, blockchains are immutable to the sense that if the input changes, the entire block's hash changes. So if you need to update say transactions, a new block has to be generated.

Imagine having 6 fresh transactions for the Ethereum mainnet. Normally, the block generator has to "broadcast" the prior block's state, process the 6 transactions, and then declare the current block's state.

Validators in the network validate these transactions by re-executing them, ensuring the block generator's assertion is accurate, and subsequently add the block to the chain. That's how blockchain maintains data integrity.

But now the problem arises when more transactions are occuring in the network. Picture a million transactions queuing up for processing. Validators would need considerable time to rerun these transactions for verification.

Concurrently, higher gas fees become inevitable as validators will choose to process transactions with higher gas fees so they can earn more money. So in essense, the more users in the network, the slower and more expensive it becomes.

That's where Layer 2 blockchains come in. They help in scalability, where more transactions are processed faster. For now, I'll focus on starknet as a Layer 2 network.

Cartoon illustration of a prover vs verifier in starknet

Starknet introduces an innovative solution to scalability by creating provable programs. Within Starknet, transactions undergo off-chain processing before their eventual upload to the mainnet. Here, the traditional block generator is replaced by a sequencer.

So the sequencer essentially declares that "Hey! The block's state was x, I then processed 100 transactions using a program P, then I have achieved the new block's state y".

These parameters (x, 100 transactions, P, y) together craft a sort of "sudoku puzzle."

An unsolved sudoku puzzle

Once the puzzle is constructed, validators, acting as verifiers, challenge the sequencer to solve it, ensuring proof accuracy.

Solved sudoku puzzle

So the prover (our sequencer) presents the proof to the verifier (validators) on why the block should be added to the chain.

Now what's fascinating is, the validators don't have to re-run all the transactions to verify that the proof is correct. They just have to take a small sample and test it against the set parameters, and when the answer is correct they can be sure all the transactions are valid, so they can be uploaded to the Ethereum mainnet.

Snippet of the solved sudoku puzzle

In my Starknet series, we'll delve deeply into these proofs, called STARK proofs. What's really interesting is that Starknet uses the concept of Arithmetization, where the challenge started as a general computation challenge , but now its transformed to a mathematical challenge to be solved, involving polynomials. We'll look at it on my next series.

So in a nutshell, you can see how Starknet as a layer 2, and what we technically call a validity rollup, solves the challenge of scalability, and high gas fees. In my next article, we'll delve deeper into the Starknet Architecture,consisting of;

  • Sequencers in Starknet: The backbone ensuring off-chain transactional efficiency.

  • The SHARP (Shared Prover) model: An innovative method enabling communal proof sharing, maximizing resource efficiency.

  • Ethereum Verifiers: solidity smart contracts.

We'll also have a look at why Starknet is a validity rollup, and not a Zero Knowledge (ZK) rollup. We can then have a taste of Cairo, the programming language used in Starknet!

Have a Starkful day!! 💃💃

A black female programmer illustration

_

Top comments (2)

Collapse
 
njagigitari profile image
Andrew Njagi Gitari

An indepth article. Niiiice.

Collapse
 
ianmaguithi profile image
Ian Maguithi

Very informative article, I was struggling to understand the scaling problem but now I do, thanks.