<commit_before>var sys = require('sys');
var logger = require('./logger');
var Binary = require('binary');
var Settings = require('./settings').Settings;
var Util = require('./util');
var BlockLocator = require('./blocklocator').BlockLocator;
var Step = require('step');

var BlockChain = exports.BlockChain = function BlockChain(storage, settings) {
	events.EventEmitter.call(this);
	if (!settings) settings = new Settings();

	this.storage = storage;
	this.cfg = settings;

	var self = this;

	var Block = this.storage.Block;
	var Transaction = this.storage.Transaction;
	var Account = this.storage.Account;

	var genesisBlock = null;
	var currentTopBlock = null;
	var lastRecvBlock = null;
	var orphanBlockFutures = {};
	var queueCount = 0;

	function createGenesisBlock(callback) {
		logger.info("Loading genesis block");

		try {
			genesisBlock = currentTopBlock = new Block(self.cfg.network.genesisBlock);
			genesisBlock.active = true;
			genesisBlock.setChainWork(genesisBlock.getWork());

			// A simple sanity check to make sure our constants are not
			// corrupted and our block hashing algorithm is working.
			if (!genesisBlock.checkHash()) {
				logger.error("Genesis block hash validation failed. There is something wrong with our constants or block hash validation code.");
				return;
			}

			var genesisTransaction = new Transaction(self.cfg.network.genesisBlockTx);

			self.emit('blockAdd', {block: genesisBlock, txs: [genesisTransaction]});
		} catch (e) {
			logger.error("Error while adding genesis block: "+(e.stack ? e.stack : e));
			return;
		}

		genesisBlock.save(function (err) {
			// It's faster to just ignore the duplicate key error than to
			// check beforehand
			if (err && err.message.indexOf("E11000") == -1) {
				logger.error(err);
				callback(err);
				return;
			}

			if (!genesisTransaction.checkHash()) {
				logger.error("Genesis tx hash validation failed. There is something wrong with our constants or tx hash validation code.");
				return;
			}

			genesisTransaction.block = genesisBlock.getHash();
			genesisTransaction.active = true;

			self.emit('txAdd', {block: genesisBlock, index: 0, tx: genesisTransaction, chain: self});

			genesisTransaction.save(function (err) {
				// It's faster to just ignore the duplicate key error than to
				// check beforehand
				if (err && err.message.indexOf("E11000") == -1) {
					logger.error(err);
					callback(err);
					return;
				}

				self.emit('txAdd', {block: genesisBlock, index: 0, tx: genesisTransaction, chain: self});
			});

			self.emit('blockSave', {block: genesisBlock, txs: [genesisTransaction]});
			callback();
		});
	}

	function loadTopBlock(callback) {
		Block.find().sort('height', -1).limit(1).exec(function (err, block) {
			if (err) {
				logger.error("Error while initializing block chain", err);
				return;
			}
			currentTopBlock = block[0];
			callback();
		});
	}

	this.getBlockByHash = function getBlockByHash(hash, callback) {
		Block.findOne({hash: hash}, callback);
	};

	this.getBlockByPrev = function getBlockByPrev(block, callback) {
		if ("object" == typeof block && block.hash) {
			block = block.hash;
		}

		Block.findOne({prev_hash: block}, function (err, block) {
			if (err) {
				callback(err);
				return;
			}

			callback(err, block);
		});
	};

	this.getGenesisBlock = function getGenesisBlock() {
		return genesisBlock;
	};

	this.getTopBlock = function getTopBlock() {
		return currentTopBlock;
	};

	this.getBlockLocator = function getBlockLocator(callback) {
		BlockLocator.createFromBlockChain(this, callback);
	};

	/**
	 * Get the last block we received.
	 *
	 * Very untrusted! This is only meant for continuously requesting
	 * blocks during a block chain download. Otherwise always use
	 * getTopBlock().
	 */
	this.getLastRecvBlock = function getLastRecvBlock () {
		return lastRecvBlock;
	};

	this.getQueueCount = function () {
		return queueCount;
	};

	this.add = function add(block, txs, callback) {
		var self = this;

		if (!block instanceof Block) {
			block = this.makeBlockObject(block);
		}

		function connectBlockToParentAndSave(parent) {
			// Our parent block is there, let's attach ourselves
			block.height = parent.height + 1;
			block.setChainWork(parent.getChainWork().add(block.getWork()));

			// Update top block field if this block is a child of it
			if (currentTopBlock.hash.compare(parent.hash) === 0) {
				logger.bchdbg('Adding block '+Util.formatHash(block.hash));
				currentTopBlock = block;
				block.active = true;
			} else {
				// Block belongs to a side chain, switch chains if side
				// chain has more work.
				block.active = false;
				if (block.moreWorkThan(currentTopBlock)) {
					// Start the reorg
					self.reorganize(currentTopBlock, block, function (err) {
						currentTopBlock = block;

						callback(err, block);
					});
					return;
				} else {
					logger.info('Adding block '+Util.formatHash(block.hash)+
								' on side chain');
				}
			}

			self.emit('blockAdd', {block: block, txs: txs, chain: self});

			block.save(function (err) {
				queueCount--;

				if (err) {
					// TODO: Handle if block is a duplicate
					return callback(err);
				}

				// Asynchronously store all of this block's transactions to the database
				self.addTransactions(block, txs);

				// This event will also trigger us saving all child blocks that
				// are currently waiting.
				self.emit('blockSave', {block: block, txs: txs, chain: self});

				callback(err, block);
			});
		}

		// Static checks
		try {
			block.checkBlock();
		} catch (e) {
			if (e.stack) {
				logger.error(e.stack);
			}
			callback('Check failed: ' + e, null);
		}

		lastRecvBlock = block;

		queueCount++;

		this.getBlockByHash(block.prev_hash, function (err, prevBlock) {
			// Let's see if we are able to connect into the chain
			if (!err && prevBlock && prevBlock.height >= 0) {
				// Our parent is in the chain, connect up and save
				connectBlockToParentAndSave(prevBlock);
			} else {
				// Our parent is not in the chain, create a future to be
				// executed when it is.
				var future = connectBlockToParentAndSave;
				if (!orphanBlockFutures[block.prev_hash]) {
					orphanBlockFutures[block.prev_hash] = {};
				}
				orphanBlockFutures[block.prev_hash][block.hash] = future;
			}
		});
	};

	this.addTransactions = function addTransactions(block, txs) {
		txs.forEach(function (tx, i) {
			if (!(tx instanceof Transaction)) {
				tx = new Transaction(tx);
			}

			tx.block = block.getHash();
			tx.active = block.active;

			// Calculate hash
			tx.getHash();

			self.emit('txAdd', {block: block, index: i, tx: tx, chain: self});

			tx.save(function (err) {
				if (err) {
					logger.warn(err);
					return;
				}

				self.emit('txSave', {block: block, index: i, tx: tx, chain: self});
			});
		});
	};

	this.findFork = function findFork(bOld, bNew, toDisconnect, toConnect, callback) {
		if ("function" == typeof toDisconnect) {
			callback = toDisconnect;
			toDisconnect = null;
		}

		toDisconnect = toDisconnect || [];
		toConnect = toConnect || [];

		if (bOld.getHash().compare(bNew.getHash()) === 0) {
			callback(null, toDisconnect, toConnect, bOld);
			return;
		}<commit_msg>Convert transaction save handle from closure to regular function.<commit_after>var sys = require('sys');
var logger = require('./logger');
var Binary = require('binary');
var Settings = require('./settings').Settings;
var Util = require('./util');
var BlockLocator = require('./blocklocator').BlockLocator;
var Step = require('step');

var BlockChain = exports.BlockChain = function BlockChain(storage, settings) {
	events.EventEmitter.call(this);
	if (!settings) settings = new Settings();

	this.storage = storage;
	this.cfg = settings;

	var self = this;

	var Block = this.storage.Block;
	var Transaction = this.storage.Transaction;
	var Account = this.storage.Account;

	var genesisBlock = null;
	var currentTopBlock = null;
	var lastRecvBlock = null;
	var orphanBlockFutures = {};
	var queueCount = 0;

	function createGenesisBlock(callback) {
		logger.info("Loading genesis block");

		try {
			genesisBlock = currentTopBlock = new Block(self.cfg.network.genesisBlock);
			genesisBlock.active = true;
			genesisBlock.setChainWork(genesisBlock.getWork());

			// A simple sanity check to make sure our constants are not
			// corrupted and our block hashing algorithm is working.
			if (!genesisBlock.checkHash()) {
				logger.error("Genesis block hash validation failed. There is something wrong with our constants or block hash validation code.");
				return;
			}

			var genesisTransaction = new Transaction(self.cfg.network.genesisBlockTx);

			self.emit('blockAdd', {block: genesisBlock, txs: [genesisTransaction]});
		} catch (e) {
			logger.error("Error while adding genesis block: "+(e.stack ? e.stack : e));
			return;
		}

		genesisBlock.save(function (err) {
			// It's faster to just ignore the duplicate key error than to
			// check beforehand
			if (err && err.message.indexOf("E11000") == -1) {
				logger.error(err);
				callback(err);
				return;
			}

			if (!genesisTransaction.checkHash()) {
				logger.error("Genesis tx hash validation failed. There is something wrong with our constants or tx hash validation code.");
				return;
			}

			genesisTransaction.block = genesisBlock.getHash();
			genesisTransaction.active = true;

			self.emit('txAdd', {block: genesisBlock, index: 0, tx: genesisTransaction, chain: self});

			genesisTransaction.save(function (err) {
				// It's faster to just ignore the duplicate key error than to
				// check beforehand
				if (err && err.message.indexOf("E11000") == -1) {
					logger.error(err);
					callback(err);
					return;
				}

				self.emit('txAdd', {block: genesisBlock, index: 0, tx: genesisTransaction, chain: self});
			});

			self.emit('blockSave', {block: genesisBlock, txs: [genesisTransaction]});
			callback();
		});
	}

	function loadTopBlock(callback) {
		Block.find().sort('height', -1).limit(1).exec(function (err, block) {
			if (err) {
				logger.error("Error while initializing block chain", err);
				return;
			}
			currentTopBlock = block[0];
			callback();
		});
	}

	this.getBlockByHash = function getBlockByHash(hash, callback) {
		Block.findOne({hash: hash}, callback);
	};

	this.getBlockByPrev = function getBlockByPrev(block, callback) {
		if ("object" == typeof block && block.hash) {
			block = block.hash;
		}

		Block.findOne({prev_hash: block}, function (err, block) {
			if (err) {
				callback(err);
				return;
			}

			callback(err, block);
		});
	};

	this.getGenesisBlock = function getGenesisBlock() {
		return genesisBlock;
	};

	this.getTopBlock = function getTopBlock() {
		return currentTopBlock;
	};

	this.getBlockLocator = function getBlockLocator(callback) {
		BlockLocator.createFromBlockChain(this, callback);
	};

	/**
	 * Get the last block we received.
	 *
	 * Very untrusted! This is only meant for continuously requesting
	 * blocks during a block chain download. Otherwise always use
	 * getTopBlock().
	 */
	this.getLastRecvBlock = function getLastRecvBlock () {
		return lastRecvBlock;
	};

	this.getQueueCount = function () {
		return queueCount;
	};

	this.add = function add(block, txs, callback) {
		var self = this;

		if (!block instanceof Block) {
			block = this.makeBlockObject(block);
		}

		function connectBlockToParentAndSave(parent) {
			// Our parent block is there, let's attach ourselves
			block.height = parent.height + 1;
			block.setChainWork(parent.getChainWork().add(block.getWork()));

			// Update top block field if this block is a child of it
			if (currentTopBlock.hash.compare(parent.hash) === 0) {
				logger.bchdbg('Adding block '+Util.formatHash(block.hash));
				currentTopBlock = block;
				block.active = true;
			} else {
				// Block belongs to a side chain, switch chains if side
				// chain has more work.
				block.active = false;
				if (block.moreWorkThan(currentTopBlock)) {
					// Start the reorg
					self.reorganize(currentTopBlock, block, function (err) {
						currentTopBlock = block;

						callback(err, block);
					});
					return;
				} else {
					logger.info('Adding block '+Util.formatHash(block.hash)+
								' on side chain');
				}
			}

			self.emit('blockAdd', {block: block, txs: txs, chain: self});

			block.save(function (err) {
				queueCount--;

				if (err) {
					// TODO: Handle if block is a duplicate
					return callback(err);
				}

				// Asynchronously store all of this block's transactions to the database
				self.addTransactions(block, txs);

				// This event will also trigger us saving all child blocks that
				// are currently waiting.
				self.emit('blockSave', {block: block, txs: txs, chain: self});

				callback(err, block);
			});
		}

		// Static checks
		try {
			block.checkBlock();
		} catch (e) {
			if (e.stack) {
				logger.error(e.stack);
			}
			callback('Check failed: ' + e, null);
		}

		lastRecvBlock = block;

		queueCount++;

		this.getBlockByHash(block.prev_hash, function (err, prevBlock) {
			// Let's see if we are able to connect into the chain
			if (!err && prevBlock && prevBlock.height >= 0) {
				// Our parent is in the chain, connect up and save
				connectBlockToParentAndSave(prevBlock);
			} else {
				// Our parent is not in the chain, create a future to be
				// executed when it is.
				var future = connectBlockToParentAndSave;
				if (!orphanBlockFutures[block.prev_hash]) {
					orphanBlockFutures[block.prev_hash] = {};
				}
				orphanBlockFutures[block.prev_hash][block.hash] = future;
			}
		});
	};

	this.addTransactions = function addTransactions(block, txs) {
		txs.forEach(function (tx, i) {
			if (!(tx instanceof Transaction)) {
				tx = new Transaction(tx);
			}

			tx.block = block.getHash();
			tx.active = block.active;

			// Calculate hash
			tx.getHash();

			self.emit('txAdd', {block: block, index: i, tx: tx, chain: self});

			tx.collection.insert(tx.toObject, {}, handleTxInsertResult.bind(self, block, i, tx));
		});
	};

	var handleTxInsertResult = function (block, i, tx, err) {
		if (err) {
			logger.warn(err);
			return;
		}

		self.emit('txSave', {block: block, index: i, tx: tx, chain: self});
	};

	this.findFork = function findFork(bOld, bNew, toDisconnect, toConnect, callback) {
		if ("function" == typeof toDisconnect) {
			callback = toDisconnect;
			toDisconnect = null;
		}

		toDisconnect = toDisconnect || [];
		toConnect = toConnect || [];

		if (bOld.getHash().compare(bNew.getHash()) === 0) {
			callback(null, toDisconnect, toConnect, bOld);
			return;
		}
{'fraction_non_alphanumeric': 0.13266566058444232, 'fraction_numerical': 0.003018246673069008, 'mean_word_length': 3.4272699665958095, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4394009', 'n_tokens_mistral': 5395, 'n_tokens_neox': 4452, 'n_words': 1240}