Skip to Content
Course content

162: Building a Simple Blockchain Simulator in Scala

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've spent a lot of time on functional purity and type safety, but now I want to show you how those concepts hold up when we build something with a bit more "gravity." We're going to build a simple blockchain simulator. Now, we aren't building a global financial system here, but the core logic is the same: an immutable chain of blocks where each block is cryptographically linked to the one before it.

Defining the Block Structure

In Scala, when I think of a "record" of data that shouldn't change once it's created, my mind goes straight to case classes. A block needs a few things: an index, the data it's carrying, the hash of the previous block, and its own hash. I'll start with a basic structure like this:

case class Block(
  index: Int, 
  data: String, 
  previousHash: String, 
  hash: String
)

The hash is the "fingerprint" of the block. If a single character in the data changes, the hash changes entirely, which is what makes blockchains tamper-evident.

Implementing the Hashing Logic

Scala doesn't have a built-in SHA-256 function in the standard library, so we'll lean on java.security.MessageDigest. I like to wrap this in a helper object to keep the main logic clean. I'll create a method that takes the block's contents and returns a hex string.

import java.security.MessageDigest

object CryptoUtils {
  def applySha256(input: String): String = {
    val digest = MessageDigest.getInstance("SHA-256")
    val hashBytes = digest.digest(input.getBytes("UTF-8"))
    hashBytes.map("%02x".format(_)).mkString
  }
}

Connecting the Blocks

Now we need a way to actually build the chain. I'll create a Blockchain class. Since we want this to be functional, I'm going to represent the chain as a List[Block]. To add a new block, we'll take the last block in the list, grab its hash, and use it as the previousHash for the new one.

class Blockchain {
  private var chain: List[Block] = List(createGenesisBlock())

  def createGenesisBlock(): Block = {
    val genesisData = "Genesis Block"
    val genesisHash = CryptoUtils.applySha256(s"0$genesisData""0")
    Block(0, genesisData, "0", genesisHash)
  }

  def addBlock(data: String): Unit = {
    val prevBlock = chain.last
    val prevHash = prevBlock.hash
    val newIndex = prevBlock.index + 1
    
    // Combine fields to create a unique string for hashing
    val hashInput = s"$newIndex$data$prevHash"
    val newHash = CryptoUtils.applySha256(hashInput)
    
    chain = chain :+ Block(newIndex, data, prevHash, newHash)
  }

  def getChain: List[Block] = chain
}

Correcting a Critical Oversight

I just ran a quick test with this code, and I hit a problem. I added two blocks with the exact same data: "Pay Bob $10" and then "Pay Bob $10" again. While the hashes were different (because the index and previous hash changed), if I were to implement a system where blocks are mined or validated based on content, I realized I had no way to distinguish between two identical transactions happening at the same time. More importantly, if I ever wanted to implement a "Proof of Work" system, my current hash input is too predictable.

I forgot to include a timestamp. In a real blockchain, the time the block was created is vital for ordering and uniqueness. Let me fix the Block class and the addBlock method.

case class Block(
  index: Int, 
  timestamp: Long, // Added this
  data: String, 
  previousHash: String, 
  hash: String
)

// ... inside Blockchain class ...
def addBlock(data: String): Unit = {
  val prevBlock = chain.last
  val timestamp = System.currentTimeMillis() // Capture the moment
  val prevHash = prevBlock.hash
  val newIndex = prevBlock.index + 1
  
  val hashInput = s"$newIndex$timestamp$data$prevHash"
  val newHash = CryptoUtils.applySha256(hashInput)
  
  chain = chain :+ Block(newIndex, timestamp, data, prevHash, newHash)
}

Now, even if the data is identical, the timestamp ensures the hash is unique to that specific moment in time. It's a small addition, but it's the difference between a toy and a simulator that actually mimics how these systems behave.




📋 Practical Task

Implement a Chain Validation Method

Your task is to extend the Blockchain class by adding a method called isChainValid(). This method should return a Boolean.

The method must iterate through the chain (starting from the second block) and verify two things for every block:

  • The block's previousHash field matches the hash of the block immediately preceding it.
  • The block's own hash is correct by re-calculating the SHA-256 hash of its index, timestamp, data, and previousHash.

If any block has been tampered with or the link is broken, the method should return false. Otherwise, return true.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.