Define Goals and Create Project
Project: Let's build Bitcoin
Welcome to the guide on how to build Bitcoin from scratch.
Bitcoin is built on top of a very interesting technology called blockchain. In this project, we will build a Bitcoin-like cryptocurrency from scratch, and learn how a blockchain works during this process.
In this blog post, we will:
- Discuss why one should learn how a blockchain works;
- Why I’ve chosen Rust for this project;
- Create a new project called Learn Coin;
- Implement placeholder CLI tools
Without further ado, let’s begin!
Why should I learn how Bitcoin works?
A lot of people would say that it’s a big trend in the industry right now. Being a blockchain developer is a high-paying job nowadays. I think it’s essential to understand how blockchain works if you’d like to be a blockchain developer.
However, not all of us want to be blockchain developers. We simply like learning about new technologies. Blockchain is a very cool technology worth studying, as it teaches you about distributed systems, data security & data integrity, and cryptogrpahy. It’s an excellent way to improve our knowledge, which makes us better software engineers.
Why Rust?
Rust is a language that I started learning about recently, so this project is an excellent opportunity to play with it. Furthermore, it’s enjoyable to write Rust code, and usually, once the code compiles, “it just works”.
With that said, this is not a tutorial on how to write proper Rust code. I don’t have experience writing Rust code in production, so please let me know if you have any suggestions on improving certain aspects of the code. The code aims to be readable and easy to understand – we are happy to sacrifice performance to achieve that.
What if I don’t speak Rust?
Don’t worry if you don’t know Rust. You should still be able to follow along and implement the same functionality using a language of your choice. We will use very few third-party libraries, primarily for common problems, e.g., to implement CLI. To begin with, we will use a library to calculate the SHA-256 hash, which is available nowadays in any language. However, we will implement our SHA-256 by the end of the project to better understand the magic behind digital signatures.
If you run into any problems, leave a comment, and I’ll do my best to help you out!
Create a project
Let’s create a new Rust project using cargo.
$ cargo new learncoin
Created binary (application) `learncoin` package
I prefer to control the library and binary paths explicitly, so I can name them differently from the soruce files. Let’s do that first.
# learncoin/Cargo.toml
[package]
name = "learncoin"
version = "0.1.0"
authors = ["Nikola Stojiljkovic <nikolavla@gmail.com>"]
edition = "2018"
[dependencies]clap = "3.0.0-beta.2"
[lib]
name = "learncoin_lib"
path = "src/lib.rs"
[[bin]]
name = "learncoin"
path = "src/main.rs"
Introduce CLI commands
We are going to use a library called clap to provide CLI functionality. Our tool should support two sub-commands:
- server - runs a node in the LearnCoin network responsible for routing and validation;
- miner - runs a mining process in the LearnCoin network responsible for mining;
- client - a CLI interface to interact with a server.
We would like to have a command that starts a server and connects to its peers as follows:
./learncoin server --address 127.0.0.1:8334 --peers "127.0.0.1:8333,127.0.0.1:8332"
Let’s implement the command that starts a server.
// learncoin/commands/server_command.rs
use clap::{App, Arg, ArgMatches};
use std::error::Error;
struct ServerCliOptions {
address: String,
peers: Vec<String>,
}
impl ServerCliOptions {
pub fn parse(matches: &ArgMatches) -> Result<Self, Box<dyn Error>> {
let peers = matches
.values_of("peers")
.map(|v| v.collect())
.unwrap_or_else(|| vec![])
.iter()
.map(|s| s.to_string())
.collect();
Ok(Self {
address: matches.value_of("address").unwrap().to_string(),
peers,
})
}
}
pub fn server_command() -> App<'static> {
App::new("server")
.version("0.1")
.about("LearnCoin server process.")
.arg(
Arg::new("address")
.long("address")
.value_name("HOSTNAME:PORT")
.about("Address at which the server runs.")
.takes_value(true)
.required(true),
)
.arg(
Arg::new("peers")
.long("peers")
.value_name("[HOSTNAME:PORT...]")
.about("List of peers to which the node connects to.")
.multiple_occurrences(true)
.use_delimiter(true)
.takes_value(true)
.default_values(vec![].as_slice())
.required(false),
)
}
pub fn run_server_command(matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!();
}
The code is hopefully self-explanatory. It defines a server CLI command that takes a hostname and port where the server listens and a list of peer addresses to which the server connects.
We’d like to execute RPCs on the server and display the result in some format. For example, we may want to get a block from the server and print it to the console, or we may want to get a complete blockchain and visualize it as a graph. These actions will be done by the client CLI tool. We will also use the client to send raw transactions to a node.
./learncoin client --server "127.0.0.1:8334" getblock 9961b01dcd9b5263716e858b7a059570037642c469a5e097fe11c0a2763805e2
Let’s implement the command to run the client tool.
// learncoin/commands/client_command.rs
use clap::{App, Arg, ArgMatches};
use std::error::Error;
use std::time::Duration;
struct ClientCliOptions {
server: String,
timeout: Duration,
}
impl ClientCliOptions {
pub fn parse(matches: &ArgMatches) -> Result<Self, Box<dyn Error>> {
Ok(Self {
server: matches.value_of("server").unwrap().to_string(),
timeout: matches
.value_of_t::<u64>("timeout")
.map(Duration::from_secs)?,
})
}
}
pub fn client_command() -> App<'static> {
App::new("client")
.version("0.1")
.about("LearnCoin client to interact with the server.")
.arg(
Arg::new("server")
.short('s')
.long("server")
.value_name("HOSTNAME:PORT")
.about("Address of the server that the client cli talks to.")
.takes_value(true)
.required(true),
)
.arg(
Arg::new("timeout")
.short('t')
.long("timeout")
.value_name("TIMEOUT")
.about("Time to wait for the response.")
.takes_value(true)
.required(false)
.default_value("5"),
)
}
pub fn run_client_command(matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
unimplemented!()
}
A client command would have multiple sub-commands (e.g., getblock, sendrawtransaction, etc.). The client sends a request to the server and prints out the response. Note that we have a timeout parameter to terminate the request if it takes too long.
Let’s wrap everything in the main LearnCoin tool.
// learncoin/src/main.rs
use clap::{App, AppSettings};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
let matches = App::new("learncoin")
.about("LearnCoin blockchain CLI tools.")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(learncoin_lib::commands::server_command())
.subcommand(learncoin_lib::commands::client_command())
.get_matches();
if let Some(ref matches) = matches.subcommand_matches("server") {
learncoin_lib::commands::run_server_command(&matches)
} else if let Some(ref matches) = matches.subcommand_matches("client") {
learncoin_lib::commands::run_client_command(&matches)
} else {
panic!("Should report help.");
}
}
Now we can run the server, the miner, and the client commands. However, they would panic because we haven’t implemented them yet. But don’t worry, we will be writing actual blockchain code soon.
Summary
In this chapter, we have outlined the plan that we will be following to implement the fully working blockchain. We have also briefly described the API of the CLI tools that we will produce as our final product.
Here’s the source code for this chapter.
What’s next?
In the next blog post, we will talk about hash functions and transactions.