2

I have this following javascript script. I'm trying to build a sample script for how blockchain works. I'm storing all block data in level DB.

const SHA256 = require('crypto-js/sha256');
var levelDB = require('./levelSandbox');

class Block{
    constructor(data){
    this.hash = "",
    this.height = 0,
    this.body = data,
    this.time = 0,
    this.previousBlockHash = ""
    }
}

class Blockchain{

    constructor(levelDB){
        this.levelDB = levelDB;
        this.blocksInLevelDB = {};
        this.chain = {};

        this.getBlockHeight()
            .then(height => {
                if(!height){
                    this.addBlock(new Block("First block in the chain - Genesis block"));
                }
            })        
    }

    // Method for adding new block
    addBlock(newBlock){
        this.getBlockHeight()
            .then(height => {
                console.log('block height is: ', height);
                console.log('blocksInLevelDB: ', this.blocksInLevelDB);

                // Set block hight
                newBlock.height = height;

                // Set UTC timestamp
                newBlock.time = new Date().getTime().toString().slice(0,-3);

                // Set previous block hash
                if(height > 0){
                    let blockData = JSON.parse(this.blocksInLevelDB[height-1]);
                    newBlock.previousBlockHash = blockData.hash;
                }

                // Block hash with SHA256 using newBlock and converting to a string
                newBlock.hash = SHA256(JSON.stringify(newBlock)).toString();

                console.log('newBlock obj: ',newBlock);

                // Adding block object to chain & save in level db
                this.levelDB.addLevelDBData(height, JSON.stringify(newBlock))
                    .then(console.log('Block added to level db'))
                    .catch(function(err){console.log('Error in adding new block: ', err)})


            })
    }

    // Get block height
    getBlockHeight(){
        return this.levelDB.getAllDataFromLevelDB()      
            .then(data => {
                let blockHeight = data.length;
                for (const elem of data){
                    this.blocksInLevelDB[elem.key] = elem.value;
                }
                return blockHeight;
            })
            .catch(function(err){
                return err;
            });
    }
}

Everything is working fine in the normal case except at a couple of cases like -

Case.1 - When level DB has no data(empty) then if I run following code only one record is inserting to the level DB rather than two.

var blockChain = new Blockchain(levelDB);
blockChain.addBlock(new Block('Second block in the chain'));

But, if I modified above code just like this -

var blockChain = new Blockchain(levelDB);
setTimeout(function(){ 
    blockChain.addBlock(new Block('Second block in the chain'));
}, 3000);

then it's working fine(inserting two records).

Case.2 - When Level DB has more than one records & if I try to add one more record it's working fine. Ex.

var blockChain = new Blockchain(levelDB);
blockChain.addBlock(new Block('Third block in the chain'));

But the moment I try to add more than one records, only one record get inserted(the last one). Ex.

var blockChain = new Blockchain(levelDB);
blockChain.addBlock(new Block('Third block in the chain'));
blockChain.addBlock(new Block('Fourth block in the chain'));
blockChain.addBlock(new Block('Fifth block in the chain'));

I know it's happening because of the promise(async nature) where all my calls to getBlockHeight() executing asynchronously & getting the same number of records(Blockchain height). Because of which, at the time of adding new Block all records are inserting at with the same key & overriding each other. But, can't able to understand what's the right way to manage such a situation.

Suresh
  • 5,687
  • 12
  • 51
  • 80
  • Note you wouldn't need to `bind(this)` if used arrow functions – charlietfl Dec 02 '18 at 13:10
  • I'm not so much familiar with latest cool js tricks. So, managing everything with the old school way. Thanks for this tips though. I'll update my code with arrow function. – Suresh Dec 02 '18 at 13:36
  • `addBlock` should probably `return` its promise. – Bergi Dec 02 '18 at 13:58
  • I recommend [not to do promise stuff in the constructor](https://stackoverflow.com/a/24686979/1048572) – Bergi Dec 02 '18 at 13:58
  • @Bergi - Noted down. – Suresh Dec 02 '18 at 14:06
  • If you don't want to make your caller wait for everything and call methods sequentially but have the instance deal with the sequencing of db requests, I recommend wrapping a promise in the instance. See [here](https://stackoverflow.com/a/35828316/1048572) or [there](https://stackoverflow.com/a/43880581/1048572) for examples – Bergi Dec 02 '18 at 14:17

1 Answers1

1

Okay this is rough order of execution:

new Blockchain(levelDB);
this.levelDB = levelDB;      // constructor
this.blocksInLevelDB = {};   // constructor
this.chain = {};             // constructor
this.getBlockHeight()        // ASYNC, constructor
|    blockChain.addBlock(new Block('Second block in the chain'));
|    this.getBlockHeight()   // ASYNC, called by ^
|    |
L----+--- if (!height)       // true
     |    this.addBlock(new Block("First blo..
     |    this.getBlockHeight()                              // ASYNC
     |    |
     L--- +---> ... this.levelDB.addLevelDBData(height, ...) // ASYNC
          |         // ^ second block is added, at height 0
          |
          L---> ... this.levelDB.addLevelDBData(height, ...)
                    // ^ first block is added, again at height 0

To avoid this, you should probably use an async pattern for your BlockChain class too.

Something like:

let blockChain = new BlockChain(levelDB);

blockChain.init()
    .then(() =>
        blockChain.addBlock(
            new Block('Second block in the chain')))
    .catch(...);

For this, you will need to define an init() method that checks height, inserts the first block and returns a Promise.

aravindanve
  • 979
  • 7
  • 14