Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    A blockchain-backed Redux clone in 105 lines of code

    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 keeps a chain of immutable data forever. What if we backed that chain with a blockchain? What would happen? 🤔

    Blockchain in a nutshell

    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.

    Image borrowed from Lauri Hartikka's post
    Image borrowed from Lauri Hartikka's post

    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 🤓

    Published on December 18th, 2017 in blockchain, Livecoding, redux, Technical

    Did you enjoy this article?

    Continue reading about A blockchain-backed Redux clone in 105 lines of code

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    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 ❤️

    Created by Swizec with ❤️