Writing a blockchain with RxJS

Have you heard about blockchain? I'm sure you have. Everyone is talking about blockchain these days, but not everyone truly understands how it works. No, it's not about bitcoin, it's not even about cryptocurrency at all; those are just based on blockchain. There are even cryptocurrencies that aren't based on blockchain.

So what is it? From the technical perspective, it's just a list of records stored somewhere, just like a database, but in a very particular way. Each modification (or transaction) is cryptographically secured, using information from the previous transaction or block (hence, the name chain). So, if you update an old block, you'll have to update all the subsequent transactions of that chain. This makes it very hard to crack.

Why did I decide to build a blockchain in RxJS? Simple, just for fun. I love Rx and the whole reactive programming paradigm. And if you take some time to consider it, observable' streams and blockchains make a lot of sense together. They both have values emitted over time, and they are supposed to be immutable. Also, one of the greatest features of a blockchain is that it can be distributed and decentralized. In order to make this happen you have to listen to, and react to changes, and Rx is simply awesome on this.


We need to explore some concepts before get our hands dirty with code

  • Hash is a cryptographic function that translates any input into a fixed representation of that input. Anytime you pass some input to the hash function, it will return the same hash result, if anything in the input changes, then the hash is completely different
  • Mining is the action of generating a hash for some input respecting the PoW with the purpose of getting a reward by doing so
  • PoW or proof-of-work is a test used in blockchain mining. The idea is to make the mining harder by adjusting the requirements of the accepted hash. As an example, a hash must have 4 leading zeros, so the miner need to generate a hash over and over until it finds one that passes the PoW test
  • Node is a single unit in a network. Usually these are the miners, connected to the Node's network. They act both as a client and a server, forwarding the messages

The challenge

We need to create a chain containing blocks that looks like this:

  • data is what we want to store
  • the block's index
  • the date and time of creation as a timestamp in milliseconds
  • hash inputting the block's information
  • previous block's hash (this is what makes it a chain)
  • nonce, the cycles' number to approve the PoW

We also need to create a pool of data, waiting to be added to the chain. We're gonna call this mine.

We're going to make a decentralized blockchain, so we'll need to make the chain and mine shareable.

Each chain and mine is part of a Node, so they will forward new blocks as well as mining requests. They will need to verify any new block sent, then validate the chain and include them in the chain. If at any time they read an invalid block, disconnect from that Node.


The Code

class Node {
static getEventHistory(replay$) {
const events = [];
    replay$.takeUntil(time(1))
.subscribe(event => events.push(event));
    return events;
}
  static getLastEvent(replay$) {
const events = Node.getEventHistory(replay$);
    return events.length ? events.pop() : Node.base;
}
  get lastBlock() {
return Node.getLastEvent(this.chain$);
}
  constructor(id) {
this.id = id;
this.chain$ = new ReplaySubject();
this.mine$ = new BehaviorSubject({});
}
}
Node.base = { hash: 0, index: -1 };

Why ReplaySubject and BehaviorSubject?

We need the chain to remember all the previous blocks to be able to validate the information, and of course, because that's where all the data is going to be stored. Thus, ReplaySubject is a perfect solution because they are meant to remember all the events broadcasted.

ReplaySubject is a perfect solution because they are meant to remember all the events broadcasted

For the mine we don't really need the history of mining requests. However, we will need the last mining request, just to ensure we won't get any "echo" when forwarding the events in the net.

We'll also need an identifier and a reference to the last block mined. Sadly, ReplaySubject doesn't have a getValues method (like BehaviorSubject) so we need a custom method to extract the last element. Those are the static methods getEventHistory and getLastEvent.

Mine Request

class Node {
...
  process(data) {
this.mine$.next({
data,
_ref: this.id,
_time: Date.now();
});
}
}

We're sending some meta info here, a reference to the node id and the current time. We'll talk more about this later.

Mining

class Node {
...
  listen() {
this.mine$
.filter(event => event && event.data)
.concatMap(({ data }) => this.mine(data))
.filter(block =>
Node.ValidateBlock(this.lastBlock, block))
.subscribe(this.chain$);
}
  mine(data){
const block = {
data,
nonce: 0,
minedBy: this.id,
prev: this.lastBlock.hash,
index: this.lastBlock.index + 1,
timestamp: Date.now
};
    return interval(0)
.do(nonce => block.nonce = nonce)
.map(() => SHA256(JSON.stringify(block)))
.first(hash => Node.validateDificulty(hash))
.map(hash => ({ ...block, hash: hash.toString() }));
}
}

Whenever a new event gets into the mine we'll immediately process it. First thing is to ensure we have some data to process

Next, the hash calculation. We start by creating the new block except for the hash. We use the last block in chain to get its hash and index.
We need to generate a hash that passes the PoW test, but if we input the same parameters to the hash function, it will return the same value, that's why we use a nonce that increments by 1 each time. So, we generate a new hash and request from RxJS the first event that passes the validation.

Continue with the mine pool, once we get a mined value, we run a validation. Why? Because while we were busy generating the hash, some other node could have found it already. If everything looks good, we add the block to the chain.

Validations

class Node {
...
  static validateChain(lastBlock, block) {
if (!lastBlock || !Node.validateBlock(lastBlock, block)) {
return;
}
    return block;
}
  static validateBlock(lastBlock, block) {
return (
Node.validateParent(lastBlock, block) &&
Node.validateDifficulty(block.hash) &&
Node.validateHash(block)
);
}
  static validateHash(block) {
const { hash } = block;
const tempBlock = { ...block };
delete tempBlock.hash;
    return hash === SHA256(JSON.stringify(tempBlock)).toString();
}
  static validateParent(lastBlock, block) {
return lastBlock.hash === block.prev &&
lastBlock.index + 1 === block.index;
}
  static validateDifficulty(hash) {
return /^00/.test(hash);
}
}

validateChain is meant to be used in a reduce-like method, where you take the previous result and the current result and return the new value. Here we return the current block only if it's valid.

validateParent ensures the lastBlock indeed is connected to the new block with the corresponding hash and index.

validateDifficulty checks the PoW, in this case we're only requesting 2 leading zeros. If we make it to match 4 instead of 2, it will take a good amount of time to mine each block.

validateHash simply does that, generates a hash from the block's content, and it should match the block's hash.

Connections

So far, our Node is ready to be used, it's capable of mining new items, generate the hash and, once validated, add them to the chain. But if we want to make it distributed we need to share and connect our chain and mine to other nodes.

class Node {
...
  connect(node) {
const history = Node.getEventHistory(node.chain$);
const isValid = history.reduce(Node.validateChain, Node.base);
    if (!isValid) {
this.invalidate(node.id);
}
    this.connectMine(node);
this.connectChain(node);
}
  connectMine(node) {
node.mine$
.skip(1)
.filter(event => event._ref !== this.id)
.filter(event => event._time > this.mine$.value._time)
.subscribe(this.mine$);
}
  connectChain(node) {
node.chain$
.distinctUntilKeyChanged('hash')
.scan((lastBlock, block) =>
Node.validateChain(lastBlock, block) ? block :
this.invalidate(node.id)
, Node.base)
.filter(block => block.index > this.lastBlock.index)
.subscribe(this.chain$);
}
  invalidate(id) {
throw new Error(`Disconnected from node ${id}`);
}
}

Before we attempt to connect our chain and mine with anyone else, we first need to ensure their chain is valid by traversing its history and validating each block.

To connect our mines, we skip the first one, this because it's a BehaviorSubject, so the first one will always be a past event. We need the _ref to know if the mine request was produced by us. In that case this event is an "echo" effect, which means we broadcasted the event and a network we were connected to sent it back to us. Some nodes might have some latency and they will send back previous events, so we protect against this using the _time reference from the mine request.
If everything looks good, we send the event to our mine and let anyone connected to us, attempt to mine the new block.

For the chain we first clear out the echo effect filtering by hash equality, then validate all the blocks using the previous block. Again, this chain might deliver an already mined block, so we skip this block if that's the case, otherwise just add it to our chain.


The Result

In the example, there's a single node with the Genesis block, this will be our first block in the chain. Any other node that wants to join our chain will contain all the blocks mined up to that point.

All the connected nodes will attempt to mine it, but only the fastest will be the winner

Notice that even if a mine request is sent to a specific node, it won't mean that node is going to be the one mining the block. All the connected nodes will attempt to mine it, but only the fastest will be the winner, and it will be added to everyone else's chain.


Conclusions

Blockchain is not magic, but it's a really nice way to solve certain problems, particularly when you need decentralization or distribution of content.

Now that we understand more about how a blockchain works, the difference between this and any cryptocurrency, like Bitcoin, is the data they carry in their blocks. Bitcoin has a special protocol for this, as do all the other coins. But you can really store images, texts, virtually anything, in a blockchain, although it doesn't mean it's ideal for all cases.

Link to the Spanish version of this article

About Ninjadevs

Ninjadevs is a group of developers focused on sharing awesome content related to web development. If you want to hear more about us, follow us on medium and Facebook @ninjadevs

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.