Skip to content

Setup & Project Layout

Welcome to the workshop. In the overview we promised a small but complete blockchain you can run — one that mines blocks, signs payments with real secp256k1 keys, validates everything, and even gossips blocks between nodes. Before we write a single line of that, we need two things: a working Rust toolchain, and a clear mental map of how the project is laid out.

This chapter is deliberately light on code and heavy on orientation. Think of it as walking the floor plan before you start building rooms. Once you can picture where each piece lives and what each dependency does, every later chapter will feel like filling in a labeled blank.

Rust is installed through a tool called rustup, which manages the compiler (rustc), the build tool and package manager (cargo), and lets you keep everything up to date. On macOS or Linux, the one-liner from rustup.rs is:

Terminal window
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Follow the prompts (the defaults are fine), then restart your terminal so the new tools are on your PATH. Verify it worked:

Terminal window
rustc --version
cargo --version

You should see version numbers print out. If you do, you have everything you need — Rust ships its compiler, package manager, test runner, and documentation tool together, so there’s nothing else to install.

You can either clone the companion crate that ships with this book, or create your own from scratch and fill it in chapter by chapter. To make a fresh project, use cargo new:

Terminal window
cargo new --bin blockchain-from-scratch
cd blockchain-from-scratch

The --bin flag says “this is a binary crate” — a program with an entry point you can run — as opposed to --lib, a library crate meant to be used by other code. (Our finished project is actually both, and we’ll see why in a moment.) cargo new drops you a Cargo.toml file and a src/main.rs with a hello-world fn main.

Two words will follow you through this whole book, so let’s pin them down now.

A crate is the unit Rust compiles at once — roughly, “one project.” Our blockchain is one crate. The external libraries we depend on (like sha2 for hashing) are also crates; we just pull them in rather than write them.

A module is a namespace inside a crate that groups related code. Modules are how we keep a single crate from becoming one giant unreadable file. Each .rs file under src/ is its own module, and we declare them with pub mod. Our blockchain has one module per concept — hashing, blocks, transactions, and so on — which keeps each idea in its own tidy box.

Cargo.toml is the project’s manifest — its name, version, Rust edition, and dependencies. Here is the exact manifest for our crate:

[package]
name = "blockchain-from-scratch"
version = "0.1.0"
edition = "2024"
[dependencies]
sha2 = "0.10"
secp256k1 = "0.30"
rand = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
hex = "0.4"

The [package] section names the crate and pins it to edition 2024 — editions are Rust’s way of introducing new language conveniences without breaking old code, and we use the latest.

The [dependencies] section is the interesting part. Each line pulls in an external crate from crates.io, Rust’s package registry. The string is a version requirement ("0.10" means “0.10.x, the latest compatible patch”). cargo downloads and compiles these the first time you build. Here’s what each one does for us, and which chapter it shows up in:

CrateWhat it doesFirst used in
sha2SHA-256 hashing — the cryptographic fingerprint at the heart of everythingHashing
secp256k1The elliptic-curve signature scheme Bitcoin uses, for keys and signingKeys & Signatures
randA random number generator, used to create fresh wallet private keysKeys & Signatures
serdeA serialization framework; the derive feature auto-generates the boilerplatePersistence
serde_jsonTurns our data structures into JSON text (and back) for saving to diskPersistence
hexEncodes raw bytes as readable hexadecimal strings (the 0000... hashes you saw)Hashing

Notice serde is written differently:

serde = { version = "1", features = ["derive"] }

That table form lets us turn on a feature. The derive feature unlocks #[derive(Serialize, Deserialize)], an annotation that writes all the tedious “how to convert this struct to JSON” code for us. We’ll lean on it heavily when we get to saving the chain.

Most tiny projects have only main.rs. Ours has both src/lib.rs and src/main.rs, and the split is intentional:

  • src/lib.rs is the library — all the reusable logic of the blockchain. It declares the modules and is what our tests import.
  • src/main.rs is the binary — a thin program that wires the library into a command-line tool (the demo and serve commands you saw in the overview).

This is a common and healthy Rust pattern: keep the real logic in a library so it can be tested and reused, and keep main.rs as a slim front door. Here is our complete src/lib.rs:

//! A minimal but complete blockchain, built from scratch in Rust to learn how
//! the pieces fit together. Companion code for Part 11 of *Bitcoin · First
//! Principles*.
pub mod block;
pub mod blockchain;
pub mod hashing;
pub mod merkle;
pub mod message;
pub mod net;
pub mod node;
pub mod pow;
pub mod storage;
pub mod transaction;
pub mod utxo;
pub mod wallet;

That’s the whole file — and it’s basically a table of contents for the rest of the book. The //! lines at the top are a module-level doc comment: documentation that describes the file it sits in (regular /// comments document the next item; //! documents the enclosing one).

Each pub mod line tells Rust “there’s a public module here, find it in the matching .rs file.” So pub mod hashing; pulls in src/hashing.rs, the very next thing we’ll write. Reading the list top to bottom is reading our roadmap: block, blockchain, hashing, merkle, the peer-to-peer trio (message, net, node), pow, storage, transaction, utxo, and wallet.

Here’s the same map with each module’s job, drawn from the crate’s own README:

FileResponsibility
hashing.rsSHA-256 / double-SHA-256 helpers
transaction.rsTransaction, inputs/outputs, txid & sighash
merkle.rsMerkle root + inclusion proofs
block.rsBlockHeader, Block, mining
pow.rsProof-of-Work difficulty check
wallet.rssecp256k1 keys, signing, verification, addresses
utxo.rsthe unspent-output set (current state)
blockchain.rsthe chain, coinbase, validation, coin selection
storage.rssave/load the chain as JSON
message.rs / node.rs / net.rsthe peer-to-peer layer
main.rsthe demo and serve CLI

Three cargo commands cover almost everything you’ll do:

  • cargo build — compile the project (and download dependencies the first time). It produces an executable but doesn’t run it. The first build is slow because it compiles every dependency; later builds only recompile what changed.
  • cargo run — build and then run the binary. Anything after -- is passed to your program, not to cargo. That’s how cargo run -- demo tells our CLI to run the demo command.
  • cargo test — build and run every test in the crate. This is how we’ll prove the chain rejects cheating: forged signatures, double-spends, and inflated payouts all become failing-on-purpose tests that confirm our rules hold.

With the toolchain installed and the floor plan in your head, you’re ready to lay the foundation. The very first module — and the thing every other piece depends on — is hashing.

If you cloned the companion crate, prove the whole thing builds and its rules hold before you change anything:

Terminal window
cd rust/blockchain-from-scratch
cargo test

The first run will download and compile the six dependencies above (give it a minute), then run the test suite. A line ending in test result: ok. means your toolchain is healthy and you’re ready for the next chapter.

  1. What is the difference between a crate and a module?
  2. Why does this project have both a src/lib.rs and a src/main.rs instead of just main.rs?
  3. Which dependency do we use to create random private keys, and which chapter introduces it?
  4. What does the features = ["derive"] part of the serde line turn on, and why do we want it?
  5. What does the -- in cargo run -- demo do?
Show answers
  1. A crate is the unit Rust compiles at once — roughly “one project” (our whole blockchain, or each external library like sha2). A module is a namespace inside a crate that groups related code; each .rs file under src/ is a module, declared with pub mod, controlling visibility and keeping the crate from becoming one giant file.
  2. To keep the reusable logic in a library (lib.rs) that can be tested and reused, with main.rs as a thin binary front door wiring it into the demo/serve CLI. The same Blockchain and Node types then power the demo, the tests, and the networked node with no copy-paste.
  3. The rand crate creates random private keys, and it’s introduced in Keys, Signatures & Addresses.
  4. It turns on the derive feature, which unlocks #[derive(Serialize, Deserialize)] — auto-generating the tedious “convert this struct to/from JSON” code. We want it because it makes saving the chain (persistence) a one-line annotation per struct instead of hand-written boilerplate.
  5. The -- tells Cargo that everything after it goes to your program, not to Cargo — so demo is passed as an argument your CLI reads (as args[1]), rather than being interpreted as a Cargo flag.