<commit_before>const async = require('async'),
  _ = require('underscore'),
  rlp = require('rlp'),
  Block = require('./block.js'),
  utils = require('./utils.js');

var Blockchain = module.exports = function(blockDB, detailsDB) {
  this.blockDB = blockDB;
  this.detailsDB = detailsDB;
};

/**
 * Fetches the meta info about the blockchain from the db. Meta info contains
 * the hash of the head block and the hash of the genisis block
 * @method init
 * @param {Function} callback
 */
Blockchain.prototype.init = function(callback) {
  var self = this;

  this.detailsDB.get('meta', {
      valueEncoding: 'json'
    },
    function(err, meta) {
      if (!err && meta) {
        self.meta = meta;
        self.genesisHash = meta.genesis;
        self.td = meta.td;
        self.blockDB.get(new Buffer(meta.head, 'hex'), {
          encoding: 'binary'
        }, function(err, head) {
          if (head) {
            self.head = new Block(rlp.decode(head));
          }
          callback(err);
        });
      } else {
        self.meta = {};
        callback();
      }
    });
};

/**
 * Adds a block to the blockchain
 * @method addBlock
 * @param {object} block -the block to be added to the block chain
 * @param {function} cb - a callback function
 */
Blockchain.prototype.addBlock = function(block, cb) {
  if (block.constructor !== Block) {
    block = new Block(block);
  }

  var self = this,
    blockHash = block.hash();

  async.auto({
    //look up the parent meta info
    parentInfo: function(cb2) {
      self.getBlockInfo(block.header.parentHash.toString('hex'), function(err, pd) {

        if (!err && pd || block.header.parentHash.toString('hex') === utils.zero256().toString('hex')) {
          cb2(null, pd);
        } else {
          cb2('parent hash not found');
        }
      });
    },
    //store the block
    storeBlock: function(cb2) {
      self.blockDB.put(blockHash, block.serialize(), {
        encoding: 'binary'
      }, cb2);
    },
    //mark the uncles headers as stored
    storeUncles: function(cb2) {
      async.each(block.uncleHeaders, function(uh, cb3) {
        //info get attached to the unlce header when its validated
        //if you not validating blocks, then there is no need to update info
        if (uh.info) {
          uh.info.uncle = true;
          self.detailsDB.put(uh.hash(), uh.info, {
            encoding: 'json'
          }, cb3);
        } else {
          cb3();
        }
      }, cb2);
    },
    //update and store the details
    storeParentDetails: ['parentInfo',
      function(cb2, results) {
        //calculate the total difficulty for this block
        var td = utils.bufferToInt(block.header.difficulty),
          parentDetails = results.parentInfo,
          dbOps = [];

        block.uncleHeaders.forEach(function(uncle) {
          td += utils.bufferToInt(uncle.difficulty);
        });
        //add this block as a child to the parent's block details
        if (parentDetails) {
          td += parentDetails.td;
          parentDetails.children.push({
            hash: blockHash.toString('hex'),
            td: td
          });
          //sort so the child with the highest difficulty always comes first.
          parentDetails.children = _.sortBy(parentDetails.children, function(child) {
            return child.td;
          });
          //save
          dbOps.push({
            type: 'put',
            key: block.header.parentHash.toString('hex'),
            valueEncoding: 'json',
            value: parentDetails
          });
        }

        //store the block details
        dbOps.push({
          type: 'put',
          key: blockHash.toString('hex'),
          valueEncoding: 'json',
          value: {
            parent: block.header.parentHash.toString('hex'),
            td: td,
            children: []
          }
        });

        //store the head block if this block has a bigger difficulty
        //than the prevous block
        if (!self.head || td > self.td) {
          self.meta.head = blockHash.toString('hex');
          self.height = self.meta.height = utils.bufferToInt(block.header.number);
          self.td = self.meta.td = td;

          if (!self.head) {
            self.meta.genesis = blockHash.toString('hex');
          }

          dbOps.push({
            type: 'put',
            key: 'meta',
            valueEncoding: 'json',
            value: self.meta
          });

          self.parentHead = self.head;
          self.head = block;
        }
        self.detailsDB.batch(dbOps, cb2);
      }
    ]
  }, cb);
};

/**
 *Gets a block by its hash
 * @method getBlock
 * @param {String} hash - the sha256 hash of the rlp encoding of the block
 * @param {Function} cb - the callback function
 */
Blockchain.prototype.getBlock = function(hash, cb) {

  hash = Buffer.isBuffer(hash) ? hash : new Buffer(hash, 'hex');
  this.blockDB.get(hash, {
    encoding: 'binary'
  }, function(err, value) {
    //run callback
    if (typeof(cb) === 'function') {
      var block = null;
      if (!err) {
        block = new Block(rlp.decode(value));
      }
      cb(err, block);
    }
  });
};

/**
 * fetches blocks from the db
 * @method getBlocks
 * @param {Array.<Buffer>} hashes
 * @param {Function} cb
 * @return {Array.<Block>}
 */
Blockchain.prototype.getBlocks = function(hashes, cb) {
  var self = this;

  async.mapSeries(hashes, function(hash, done) {
    self.getBlock(hash, done);  
  }, cb);
};

/**
 * Gets a block by its hash
 * @method getBlockInfo
 * @param {String} hash - the sha256 hash of the rlp encoding of the block
 * @param {Function} cb - the callback function
 */
Blockchain.prototype.getBlockInfo = function(hash, cb) {
  if (Buffer.isBuffer(hash)) {
    hash = hash.toString('hex');
  }

  this.detailsDB.get(hash, {
    valueEncoding: 'json'
  }, cb);
};

/**
 * Gets a segment of the blockchain given the parent hash and `count `
 * returns the hashes of the blocks
 * @method getBlockChain<commit_msg>eslint<commit_after>const async = require('async'),
  _ = require('underscore'),
  rlp = require('rlp'),
  Block = require('./block.js'),
  utils = require('./utils.js');

var Blockchain = module.exports = function(blockDB, detailsDB) {
  this.blockDB = blockDB;
  this.detailsDB = detailsDB;
};

/**
 * Fetches the meta info about the blockchain from the db. Meta info contains
 * the hash of the head block and the hash of the genisis block
 * @method init
 * @param {Function} callback
 */
Blockchain.prototype.init = function(callback) {
  var self = this;

  this.detailsDB.get('meta', {
      valueEncoding: 'json'
    },
    function(err, meta) {
      if (!err && meta) {
        self.meta = meta;
        self.genesisHash = meta.genesis;
        self.td = meta.td;
        self.blockDB.get(new Buffer(meta.head, 'hex'), {
          encoding: 'binary'
        }, function(err, head) {
          if (head) {
            self.head = new Block(rlp.decode(head));
          }
          callback(err);
        });
      } else {
        self.meta = {};
        callback();
      }
    });
};

/**
 * Adds a block to the blockchain
 * @method addBlock
 * @param {object} block -the block to be added to the block chain
 * @param {function} cb - a callback function
 */
Blockchain.prototype.addBlock = function(block, cb) {
  if (block.constructor !== Block) {
    block = new Block(block);
  }

  var self = this,
    blockHash = block.hash();

  async.auto({
    //look up the parent meta info
    parentInfo: function(cb2) {
      self.getBlockInfo(block.header.parentHash.toString('hex'), function(err, pd) {

        if (!err && pd || block.header.parentHash.toString('hex') === utils.zero256().toString('hex')) {
          cb2(null, pd);
        } else {
          cb2('parent hash not found');
        }
      });
    },
    //store the block
    storeBlock: function(cb2) {
      self.blockDB.put(blockHash, block.serialize(), {
        encoding: 'binary'
      }, cb2);
    },
    //mark the uncles headers as stored
    storeUncles: function(cb2) {
      async.each(block.uncleHeaders, function(uh, cb3) {
        //info get attached to the unlce header when its validated
        //if you not validating blocks, then there is no need to update info
        if (uh.info) {
          uh.info.uncle = true;
          self.detailsDB.put(uh.hash(), uh.info, {
            encoding: 'json'
          }, cb3);
        } else {
          cb3();
        }
      }, cb2);
    },
    //update and store the details
    storeParentDetails: ['parentInfo',
      function(cb2, results) {
        //calculate the total difficulty for this block
        var td = utils.bufferToInt(block.header.difficulty),
          parentDetails = results.parentInfo,
          dbOps = [];

        block.uncleHeaders.forEach(function(uncle) {
          td += utils.bufferToInt(uncle.difficulty);
        });
        //add this block as a child to the parent's block details
        if (parentDetails) {
          td += parentDetails.td;
          parentDetails.children.push({
            hash: blockHash.toString('hex'),
            td: td
          });
          //sort so the child with the highest difficulty always comes first.
          parentDetails.children = _.sortBy(parentDetails.children, function(child) {
            return child.td;
          });
          //save
          dbOps.push({
            type: 'put',
            key: block.header.parentHash.toString('hex'),
            valueEncoding: 'json',
            value: parentDetails
          });
        }

        //store the block details
        dbOps.push({
          type: 'put',
          key: blockHash.toString('hex'),
          valueEncoding: 'json',
          value: {
            parent: block.header.parentHash.toString('hex'),
            td: td,
            children: []
          }
        });

        //store the head block if this block has a bigger difficulty
        //than the prevous block
        if (!self.head || td > self.td) {
          self.meta.head = blockHash.toString('hex');
          self.height = self.meta.height = utils.bufferToInt(block.header.number);
          self.td = self.meta.td = td;

          if (!self.head) {
            self.meta.genesis = blockHash.toString('hex');
          }

          dbOps.push({
            type: 'put',
            key: 'meta',
            valueEncoding: 'json',
            value: self.meta
          });

          self.parentHead = self.head;
          self.head = block;
        }
        self.detailsDB.batch(dbOps, cb2);
      }
    ]
  }, cb);
};

/**
 *Gets a block by its hash
 * @method getBlock
 * @param {String} hash - the sha256 hash of the rlp encoding of the block
 * @param {Function} cb - the callback function
 */
Blockchain.prototype.getBlock = function(hash, cb) {

  hash = Buffer.isBuffer(hash) ? hash : new Buffer(hash, 'hex');
  this.blockDB.get(hash, {
    encoding: 'binary'
  }, function(err, value) {
    //run callback
    if (typeof(cb) === 'function') {
      var block = null;
      if (!err) {
        block = new Block(rlp.decode(value));
      }
      cb(err, block);
    }
  });
};

/**
 * fetches blocks from the db
 * @method getBlocks
 * @param {Array.<Buffer>} hashes
 * @param {Function} cb
 * @return {Array.<Block>}
 */
Blockchain.prototype.getBlocks = function(hashes, cb) {
  var self = this;

  async.mapSeries(hashes, function(hash, done) {
    self.getBlock(hash, done);
  }, cb);
};

/**
 * Gets a block by its hash
 * @method getBlockInfo
 * @param {String} hash - the sha256 hash of the rlp encoding of the block
 * @param {Function} cb - the callback function
 */
Blockchain.prototype.getBlockInfo = function(hash, cb) {
  if (Buffer.isBuffer(hash)) {
    hash = hash.toString('hex');
  }

  this.detailsDB.get(hash, {
    valueEncoding: 'json'
  }, cb);
};

/**
 * Gets a segment of the blockchain given the parent hash and `count `
 * returns the hashes of the blocks
 * @method getBlockChain
{'fraction_non_alphanumeric': 0.13426970989047737, 'fraction_numerical': 0.003511412089290193, 'mean_word_length': 2.0758549755721267, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6371167', 'n_tokens_mistral': 3571, 'n_tokens_neox': 3510, 'n_words': 961}