Build Your Own Blockchain From Scratch in Go
Language
- unknown
by James Smith (Golang Project Structure Admin)
Sometimes “blockchain” can seem like nothing more than a hyped-up buzzword.
Yet, despite the hype, it’s useful to understand the fundamental concept that lies behind it, which is really quite simple and elegant.
The blockchain data structure is at the heart of important decentralized systems like Bitcoin and other cryptocurrencies — and it can be used to help solve a wide range of problems, with its combination of security, transparency and immutability.
This blog post provides a basic introduction to blockchain. We will then work step-by-step through the process of creating our own blockchain program in the Go programming language.
Table of Contents
What Even Is a Blockchain?
A blockchain is a distributed ledger consisting of blocks that are linked together.
Each block contains data, which is the information stored in the block, such as transactions.
It also contains a hash, which is a unique fingerprint of the block.
Finally, each block includes a previous hash, which is the hash of the preceding block. This is what creates a chain of blocks, hence the term blockchain.
The blockchain structure ensures immutability — in other words, if someone alters any block within the chain, any subsequent blocks will automatically be invalidated.
Why Is Blockchain Useful?
One of the primary advantages of blockchain is its ability to foster trust in environments where parties may not inherently have confidence in each other.
By using cryptographic algorithms and consensus mechanisms, blockchain ensures that all participants can rely on the accuracy of the data without needing intermediaries.
For example, in financial transactions, blockchain can eliminate the need for third-party verification, reducing costs and speeding up the process.
Another critical feature of blockchain is its transparency.
In supply-chain management, blockchain can provide a verifiable audit trail of a product’s journey, from raw materials to final delivery.
This not only improves accountability but also empowers consumers to make more informed decisions based on the provenance of goods.
Additionally, blockchain’s transparency is instrumental in reducing fraud and ensuring compliance with statutory regulations.
Blockchain also excels in enhancing security. Its distributed nature makes it highly resilient to cyberattacks.
Even if one node is compromised, the remaining nodes are able to maintain the integrity of the ledger, ensuring that data remains safe and consistent.
This is particularly relevant for applications involving digital identity, where personal data needs robust protection against unauthorized access and misuse.
To summarize, blockchain’s usefulness lies in its ability to provide trust, transparency and security in a decentralized manner.
How Do Cryptocurrencies Use Blockchain?
Cryptocurrencies are perhaps the most well-known and widely appreciated application of blockchain technology.
At the heart of every cryptocurrency is a blockchain — a distributed ledger that records all transactions in a way that is both chronological and immutable.
This ensures that every unit of the cryptocurrency is accounted for and prevents issues like double-spending.
In cryptocurrencies like Bitcoin and Ethereum, blockchain serves as a public ledger that’s accessible to anyone in the network.
Each transaction is grouped into a block and validated through consensus mechanisms, such as Proof of Work (PoW) or Proof of Stake (PoS), before being appended to the chain.
This decentralized process eliminates the need for intermediaries, such as banks or other financial institutions, and allows peer-to-peer transactions to take place on a global scale.
Every transaction in a cryptocurrency network is recorded on the blockchain and can be viewed by anyone with access.
While this provides a level of accountability, many cryptocurrencies also offer mechanisms to protect user privacy, such as pseudonymous addresses or advanced cryptographic techniques like zk-SNARKs.
If a hacker wanted to alter a transaction, they would need to control a majority of the network’s nodes — a feat that is computationally and economically impractical for most blockchains.
Smart contract-enabled cryptocurrencies, like Ethereum, push the capabilities of blockchain even further.
By embedding self-executing code into the blockchain, these platforms enable decentralized applications (dApps) and automated agreements without the need for centralized oversight.
Who Developed Blockchain?
Blockchain technology was first conceptualized by an individual (or possibly a group) operating under the pseudonym Satoshi Nakamoto.
In 2008, Satoshi introduced the idea as the underlying technology for Bitcoin, which was presented as a revolutionary peer-to-peer digital currency.
This innovation was detailed in the white paper titled “Bitcoin: A Peer-to-Peer Electronic Cash System”.
Satoshi Nakamoto’s work not only enabled Bitcoin’s functionality but also laid the foundation for a much broader use of blockchain technology.
Setting Up the Go Project
Now that we understand the fundamental principles behind blockchain, and how it has been used, it’s time to work on some code of our own.
As always when beginning a new project, let’s create a new directory to store our code and initialize a Go module within it:
mkdir go-blockchain
cd go-blockchain
go mod init go-blockchain
The blockchain that we’ll be creating will just run on one computer. It won’t be distributed across multiple machines, but if we wanted to distribute it, we would only need a mechanism to copy the blockchain from one location to another and another mechanism to achieve consensus between the distributed versions of the blockchain.
Defining the Block Structure
As we mentioned earlier, a block in the blockchain contains three items: data, a hash and the previous block’s hash.
So let’s create a file to define such a structure in Go:
block.go
package main
import (
"crypto/sha256"
"encoding/hex"
"strconv"
)
type Block struct {
Index int
Timestamp string
Data string
PreviousHash string
Hash string
}
// CalculateHash generates the current hash of the block
func (b *Block) CalculateHash() string {
hashData := strconv.Itoa(b.Index) + ":" + b.Timestamp + ":" + b.Data + ":" + b.PreviousHash
hash := sha256.Sum256([]byte(hashData))
return hex.EncodeToString(hash[:])
}
In the code above, the Index
field uniquely identifies the block in the chain, Timestamp
records when the block was created, Data
stores information such as transactions, PreviousHash
connects the block to the chain and, finally, Hash
ensures integrity.
The CalculateHash
method is vital for ensuring the block’s security and integrity within the blockchain. This function generates the block’s hash, which acts as a unique cryptographic fingerprint for each block based on its content.
It does this by combining the block’s index, timestamp and data, as well as the hash of the previous block, into a single string.
This string is then hashed using the SHA-256 algorithm, which produces a fixed-size, 256-bit cryptographic hash.
The result is converted into a human-readable hexadecimal string and returned.
Using this method ensures that even a minor change in the block’s contents results in a completely different hash, thereby allowing us to easily maintain the integrity and immutability of the blockchain.
Creating a Constructor Function for the Block
Now let’s create a factory function that we can call to create new blocks.
Just add the following code to the bottom of the previous file that we created:
func NewBlock(previousBlock *Block, data string) *Block {
block := Block{
Timestamp: time.Now().String(),
Data: data,
}
if previousBlock != nil {
block.Index = previousBlock.Index + 1
block.PreviousHash = previousBlock.Hash
} else {
block.PreviousHash = "-"
}
block.Hash = block.CalculateHash()
return &block
}
The NewBlock
function begins by initializing a Block
instance, setting the Timestamp
field to represent the current time and populating the Data
field with the provided input.
If a previous block has been given to the function, it assigns the new block’s Index
as one more than the previous block’s Index
and sets the PreviousHash
field to the hash of the previous block. This step establishes a cryptographic link between the blocks.
If no previous block has been given — i.e. for the so-called genesis block, which comes first in the blockchain, meaning that there is no block before it — then a placeholder value, "-"
, is assigned to the PreviousHash
field.
Also, if there is no previous block, then the value of the Index
field will be 0
by default, even though we don’t explicitly set it as such.
Finally, the function computes the hash of the new block by calling its CalculateHash
method and assigns the result to the Hash
field. This ensures that the block has a unique identifier based on its content and order in the chain.
Once all of its fields have been initialized, the function returns a pointer to the newly created Block
.
Building the Blockchain
As we discussed earlier, a blockchain is nothing more than a collection of blocks that are connected together by their hashes.
Let’s create a new file to hold the code for our blockchain structure:
blockchain.go
package main
type Blockchain struct {
Blocks []*Block
}
func NewGenesisBlock() *Block {
genesisBlock := NewBlock(nil, "Genesis Block")
return genesisBlock
}
func NewBlockchain() *Blockchain {
blockchain := Blockchain{}
genesisBlock := NewGenesisBlock()
blockchain.Blocks = append(blockchain.Blocks, genesisBlock)
return &blockchain
}
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.Blocks[len(bc.Blocks)-1]
newBlock := NewBlock(prevBlock, data)
bc.Blocks = append(bc.Blocks, newBlock)
}
The NewGenesisBlock
function creates the first block, known as the genesis block, which serves as the starting point for the chain.
The NewBlockchain
function returns a blockchain with a genesis block already added, to which further blocks can be added.
In order to do this, we can use the AddBlock
method. This function is responsible for generating a new block, linking it to the existing chain by referencing the hash of the previous block (which is done within the NewBlock
function we defined earlier) and, finally, appending the new block to the chain.
Validating the Blockchain
To maintain the integrity of the data stored within the blockchain, we must be able to validate the chain.
So let’s add the following method to our blockchain.go file:
func (bc *Blockchain) IsValid() bool {
for i := 1; i < len(bc.Blocks); i++ {
currentBlock := bc.Blocks[i]
previousBlock := bc.Blocks[i-1]
if currentBlock.Hash != currentBlock.CalculateHash() {
return false
}
if currentBlock.PreviousHash != previousBlock.Hash {
return false
}
}
return true
}
This method above verifies the integrity of the blockchain by ensuring that each block’s hash correctly matches its content.
It also checks that the PreviousHash
field in each block aligns with the hash of the preceding block, as expected.
If both of these tests pass for each block in the chain (except the genesis block, which we skip, because it stores no important data and, also, because there is no previous block to compare the genesis block with), then we can be confident that we are maintaining the chain’s consistency and linkage.
Adding a Command-Line Interface to the Blockchain
In order to make it easier to interact with a blockchain, we’ll create a CLI to the code that we've already written.
Let's create a main.go file to hold the functionality for the CLI, adding the code below to the file:
package main
import (
"bufio"
"fmt"
"os"
fp "path/filepath"
"strings"
)
func main() {
blockchain := NewBlockchain()
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Println("Enter command (add <data> / validate / save <filepath> / exit):")
scanner.Scan()
input := scanner.Text()
command := strings.Fields(input)
if len(command) == 0 {
continue
}
switch command[0] {
case "add":
if len(command) < 2 {
fmt.Println("Usage: add <data>")
continue
}
blockData := strings.Join(command[1:], " ")
blockchain.AddBlock(blockData)
fmt.Println("Block added.")
case "validate":
if blockchain.IsValid() {
fmt.Println("Blockchain is valid.")
} else {
fmt.Println("Blockchain is invalid.")
}
case "save":
if len(command) < 2 {
fmt.Println("Usage: save <filepath>")
continue
}
filepath := fp.Join(command[1:]...)
if fp.Ext(filepath) != ".json" {
filepath += ".json"
}
blockchainData, err := json.MarshalIndent(blockchain, "", "\t")
if err != nil {
panic(err)
}
if err := os.WriteFile(filepath, blockchainData, 0666); err != nil {
panic(err)
}
fmt.Printf("Blockchain saved [%s].\n", filepath)
case "exit":
os.Exit(0)
default:
fmt.Println("Unknown command")
}
}
}
This program now allows users to interact with the blockchain through four commands.
The add
command lets users add new blocks to the blockchain by providing data.
The validate
command checks the blockchain’s integrity to ensure that all blocks are correctly linked and unaltered.
The save
command allows users to store the blockchain data to a file. By specifying a file path, users can save the blockchain in JSON format, making it easy to back up or share. If the appropriate file extension has not been provided by the user, the program automatically appends it.
Finally, the exit
command terminates the command-line interface and exits the program.
Running the Blockchain CLI
We can now run the program by giving the Go compiler the three files that we've written and letting it execute the code:
go run main.go blockchain.go block.go
So let's try starting up the CLI and give it the following commands to handle:
add Hello, world!
add Hi there, everyone!
add May you be happy and well.
validate
save output.json
exit
The blockchain should pass the validation test successfully and the output.json file that is produced by the save command should contain content that looks like this:
{
"Blocks": [
{
"Index": 0,
"Timestamp": "2024-12-18 21:16:20.0872041 +0000 GMT m=+0.000000001",
"Data": "Genesis Block",
"PreviousHash": "-",
"Hash": "e4f93c0b78d3cfdfad21bc8ab27e948226cd6f8ff6a580ed5f804ffcb953f96a"
},
{
"Index": 1,
"Timestamp": "2024-12-18 21:16:23.2359677 +0000 GMT m=+3.148763601",
"Data": "Hello, world!",
"PreviousHash": "e4f93c0b78d3cfdfad21bc8ab27e948226cd6f8ff6a580ed5f804ffcb953f96a",
"Hash": "9ace2cd616554d8208269409b289a7a87b26c7655f791f19dfe3fda3f29f9b0b"
},
{
"Index": 2,
"Timestamp": "2024-12-18 21:16:23.2372176 +0000 GMT m=+3.150013501",
"Data": "Hi there, everyone!",
"PreviousHash": "9ace2cd616554d8208269409b289a7a87b26c7655f791f19dfe3fda3f29f9b0b",
"Hash": "ba1798739ba04d976a253b34887d48888f6bf0da82e573477c163cae6ee49751"
},
{
"Index": 3,
"Timestamp": "2024-12-18 21:16:23.2435775 +0000 GMT m=+3.156373401",
"Data": "May you be happy and well.",
"PreviousHash": "ba1798739ba04d976a253b34887d48888f6bf0da82e573477c163cae6ee49751",
"Hash": "c10fc392e3b03986ea046849124b9db6805b627cdd9f504a9692e76a244a06f7"
}
]
}
By looking at the JSON above, it's easy to confirm for ourselves that the value of each block's PreviousHash
field is equivalent to value of the previous block's Hash
field (except in the case of the genesis block), which is as we would expect in a valid blockchain.