Big fat disclaimer: This is Part 1 of an experiment. It is not yet secure, it does not yet share the blockchain between nodes, it still needs a proof-of-work or proof-of-stake algorithm.
With that out of the way, let us begin.
Redux is great because it does immutability right?
— Swizec Teller (@Swizec) December 8, 2017
Now imagjne this: Blockchain backed Redux. Every state change gets recorded to the blockchain. Immutable forever. Pure as dew 👌
Redux is great because it keeps a chain of immutable data forever. What if we backed that chain with a blockchain? What would happen? 🤔
So I fired up this amazing Medium post on building a blockchain in 200 lines of code and built a blockchain-backed Redux clone in about 2 hours.
Mine is just 105 lines tho 😛
You can see the code on Github. It's not quite useful yet. The blockchain doesn't get shared between nodes, and there's no proof-of-stake or proof-of-work algorithm.
That's all coming soon. First, I wanted to make sure I understood both blockchains and Redux enough to make this work.
Blockchain
Turns out blockchains are pretty simple. It's just a reversed linked list where every node holds some data, a hash, and points to the previous node.
We use the hashes to verify our chain. Verification is important when we start sharing chains between nodes.
Our blocks are JavaScript objects.
class Block {
constructor({ previousBlock, data }) {
this.index = previousBlock.index + 1;
this.previousHash = previousBlock.hash.toString();
this.timestamp = new Date().getTime() / 1000;
this.data = data;
this.hash = Block.calculateHash(this);
}
set data(data) {
this._data = JSON.stringify(data);
}
get data() {
return JSON.parse(this._data);
}
// this is where a Proof-of-Work or Proof-of-Stake algo comes in, I think
static calculateHash(block) {
return CryptoJS.SHA256(
block.index + block.previousHash + block.timestamp + block._data
).toString();
}
}
The constructor
doubles as our block generator. It takes the previousBlock
and some data
, adds some metadata, and calculates a hash
.
A data
setter and getter help us JSONify data transparently. Users can pass simple objects as data, and the blockchain will handle it.
The calculateHash
function right now just uses SHA256, but I think this is where a proof of work or a proof of stake algorithm would come into play in a real blockchain. Haven't figured that part out yet :)
Kind of surprisingly, that's all we need to build a blockchain. You could build it like this new Block({ previousBlock: new Block({ previousBlock: genesisBlock, data: {hai: "world"}}), data: {hai2: "world2"}})
But that's cumbersome, so let's add a Redux.
Redux
Redux, if you don't know, is a state management approach based on the functional concept of a reducer
. At each change, we take the current state and a change descriptor, and produce the next state.
A tiny Redux implementation that maintains history would look something like this 👇
function createStore(initialState, reducer) {
let state = [initialState];
function getState() {
return state[state.length - 1];
}
function dispatch(action) {
state.push(reducer(getState(), action));
}
return {
getState: getState,
dispatch: dispatch,
};
}
👆 That's Redux in a nutshell. Unlike the real Redux, this one maintains history. We'll expand that into a blockchain.
getState
returns the latest state and dispatch
takes an action
and calculates the new state using the reducer
function.
Using this Redux to implement a counter looks like this 👇
const store = createStore({ counter: 0 }, rootReducer);
function rootReducer(state, action) {
switch (action.type) {
case "inc":
return { counter: state.counter + 1 };
case "dec":
return { counter: state.counter - 1 };
default:
return state;
}
}
We can use this to verify that it works. Using tape in this case because LukeEd05 on the livestream suggested it.
test("count to 5", (t) => {
for (let i = 0; i < 5; i++) {
store.dispatch({ type: "inc" });
}
t.equal(store.getState().counter, 5);
t.end();
});
Works 👌
blockchain-redux
Now, how do we add the blockchain? 🤔
Well, instead of putting initialState
directly into our state, we put a genesis Block with that state. And instead of pushing new state calculations directly, we add blocks.
function createStore(initialState, reducer) {
let blockchain = [
new Block({
previousBlock: {
index: 0,
hash: "0",
timestamp: new Date().getTime(),
},
data: initialState,
}),
];
function getLastBlock() {
return blockchain[blockchain.length - 1];
}
function dispatch(action) {
const lastBlock = getLastBlock();
const nextData = reducer(lastBlock.data, action);
addBlock(new Block({ previousBlock: lastBlock, data: nextData }));
}
function addBlock(newBlock) {
if (isValidNewBlock(newBlock, getLastBlock())) {
blockchain.push(newBlock);
}
}
function isValidNewBlock(newBlock, previousBlock) {
if (previousBlock.index + 1 !== newBlock.index) {
console.log("invalid index");
return false;
} else if (previousBlock.hash !== newBlock.previousHash) {
console.log("invalid previoushash");
return false;
} else if (Block.calculateHash(newBlock) !== newBlock.hash) {
console.log(
"invalid hash: ",
Block.calculateHash(newBlock),
newBlock.hash
);
return false;
}
return true;
}
function isValidChain(blockchain) {
for (let i = 0; i < blockchain.length - 1; i++) {
if (!isValidNewBlock(blockchain[i + 1], blockchain[i])) {
return false;
}
}
return true;
}
function replaceChain(newBlocks) {
if (isValidChain(newBlocks) && newBlocks.length > blockchain.length) {
blockchain = newBlocks;
// tell others here
}
}
return {
getState: () => getLastBlock().data,
getLastBlock: getLastBlock,
dispatch: dispatch,
addBlock: addBlock,
replaceChain: replaceChain, // primarily used when starting up to take latest available blockchain
_blockchain: blockchain,
};
}
Okay that's plenty of code to spring on you. Let's go through it function by function.
getState
function getLastBlock() {
return blockchain[blockchain.length - 1];
}
Takes last block from the blockchain and returns it. That's our current state. We export a helper getState
that does getLastBlock().data
to avoid changing external APIs.
Dispatch
function dispatch(action) {
const lastBlock = getLastBlock();
const nextData = reducer(lastBlock.data, action);
addBlock(new Block({ previousBlock: lastBlock, data: nextData }));
}
Same as dispatch
before. Takes current state and builds new state with the reducer
. Then it uses addBlock
to insert a newly generated block into the chain.
isValidNewBlock
function isValidNewBlock(newBlock, previousBlock) {
if (previousBlock.index + 1 !== newBlock.index) {
console.log("invalid index");
return false;
} else if (previousBlock.hash !== newBlock.previousHash) {
console.log("invalid previoushash");
return false;
} else if (Block.calculateHash(newBlock) !== newBlock.hash) {
console.log("invalid hash: ", Block.calculateHash(newBlock), newBlock.hash);
return false;
}
return true;
}
Every new block has to be validated. Especially because they can come from other nodes.
This checks that indexes and hashes all match up so we can avoid conflicts.
isValidChain
function isValidChain(blockchain) {
for (let i = 0; i < blockchain.length - 1; i++) {
if (!isValidNewBlock(blockchain[i + 1], blockchain[i])) {
return false;
}
}
return true;
}
This will be used later on in conflict resolution. It goes through an entire proposed chain and validates every block.
replaceChain
function replaceChain(newBlocks) {
if (isValidChain(newBlocks) && newBlocks.length > blockchain.length) {
blockchain = newBlocks;
// tell others here
}
}
Another part of conflict resolution and node communication is replacing the whole chain. If a new valid chain comes in that's longer than what we already have, we replace our internal state with the new chain.
This will be particularly useful when we boot up a new client and it needs to get the whole chain.
Fin
That's pretty much it. A naive blockchain implementation that gives you a Redux-like API to store and manipulate data.
Next I'm going to add communication between nodes, probably through Firebase, and a proof-of-stake algorithm. Then we can start building something interesting.
And maybe you now understand blockchains a little better 🤓
Continue reading about A blockchain-backed Redux clone in 105 lines of code
Semantically similar articles hand-picked by GPT-4
- Blockchain Redux experiment, part 2
- Blockchain-redux becomes useful, part 1
- Livecoding recap: Blockchain-redux now shares blocks between clients in real-time
- Learning WebRTC peer-to-peer communication, part 1
- Livecoding #25: Adding MobX to a vanilla React project
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"
Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.
Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.
Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.
Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev
Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization
Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections
Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog
Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com
By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️