text: 'use strict';
  
  var should = require('chai').should();
  var sinon = require('sinon');
  var EventEmitter = require('events').EventEmitter;
  var proxyquire = require('proxyquire');
  var index = require('../../');
  var DB = index.services.DB;
  var blockData = require('../data/livenet-345003.json');
  var bitcore = require('psiacore');
  var Networks = bitcore.Networks;
  var Block = bitcore.Block;
  var BufferUtil = bitcore.util.buffer;
  var transactionData = require('../data/bitcoin-transactions.json');
  var chainHashes = require('../data/hashes.json');
  var chainData = require('../data/testnet-blocks.json');
  var errors = index.errors;
  var memdown = require('memdown');
  var levelup = require('levelup');
  var bitcore = require('psiacore');
  var Transaction = bitcore.Transaction;
  
  describe('DB Service', function() {
  
    function hexlebuf(hexString){
      return BufferUtil.reverse(new Buffer(hexString, 'hex'));
    }
  
    function lebufhex(buf) {
      return BufferUtil.reverse(buf).toString('hex');
    }
  
    var baseConfig = {
      node: {
        network: Networks.testnet,
        datadir: 'testdir'
      },
      store: memdown
    };
  
    var genesisBuffer = new Buffer('0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000', 'hex');
  
  
    describe('#_setDataPath', function() {
      it('should set the database path', function() {
        var config = {
          node: {
            network: Networks.livenet,
            datadir: process.env.HOME + '/.bitcoin'
          },
          store: memdown
        };
        var db = new DB(config);
        db.dataPath.should.equal(process.env.HOME + '/.bitcoin/bitcore-node.db');
      });
      it('should load the db for testnet', function() {
        var config = {
          node: {
            network: Networks.testnet,
            datadir: process.env.HOME + '/.bitcoin'
          },
          store: memdown
        };
        var db = new DB(config);
        db.dataPath.should.equal(process.env.HOME + '/.bitcoin/testnet3/bitcore-node.db');
      });
      it('error with unknown network', function() {
        var config = {
          node: {
            network: 'unknown',
            datadir: process.env.HOME + '/.bitcoin'
          },
          store: memdown
        };
        (function() {
          var db = new DB(config);
        }).should.throw('Unknown network');
      });
      it('should load the db with regtest', function() {
        // Switch to use regtest
        // Networks.remove(Networks.testnet);
        Networks.add({
          name: 'regtest',
          alias: 'regtest',
          pubkeyhash: 0x6f,
          privatekey: 0xef,
          scripthash: 0xc4,
          xpubkey: 0x043587cf,
          xprivkey: 0x04358394,
          networkMagic: 0xfabfb5da,
          port: 18444,
          dnsSeeds: [ ]
        });
        var regtest = Networks.get('regtest');
        var config = {
          node: {
            network: regtest,
            datadir: process.env.HOME + '/.bitcoin'
          },
          store: memdown
        };
        var db = new DB(config);
        db.dataPath.should.equal(process.env.HOME + '/.bitcoin/regtest/bitcore-node.db');
        Networks.remove(regtest);
      });
    });
  
    describe('#start', function() {
      var TestDB;
  
      before(function() {
        TestDB = proxyquire('../../lib/services/db', {
          fs: {
            existsSync: sinon.stub().returns(true)
          },
          levelup: sinon.stub()
        });
      });
  
      it('should emit ready', function(done) {
        var db = new TestDB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          on: sinon.spy(),
          genesisBuffer: genesisBuffer
        };
        db.loadTip = sinon.stub().callsArg(0);
        db.connectBlock = sinon.stub().callsArg(1);
        db.sync = sinon.stub();
        var readyFired = false;
        db.on('ready', function() {
          readyFired = true;
        });
        db.start(function() {
          readyFired.should.equal(true);
          done();
        });
      });
  
      it('will call sync when there is a new tip', function(done) {
        var db = new TestDB(baseConfig);
        db.node.services = {};
        db.node.services.bitcoind = new EventEmitter();
        db.node.services.bitcoind.genesisBuffer = genesisBuffer;
        db.loadTip = sinon.stub().callsArg(0);
        db.connectBlock = sinon.stub().callsArg(1);
        db.sync = sinon.stub();
        db.start(function() {
          db.sync = function() {
            done();
          };
          db.node.services.bitcoind.emit('tip', 10);
        });
      });
  
      it('will not call sync when there is a new tip and shutting down', function(done) {
        var db = new TestDB(baseConfig);
        db.node.services = {};
        db.node.services.bitcoind = new EventEmitter();
        db.node.services.bitcoind.syncPercentage = sinon.spy();
        db.node.services.bitcoind.genesisBuffer = genesisBuffer;
        db.loadTip = sinon.stub().callsArg(0);
        db.connectBlock = sinon.stub().callsArg(1);
        db.node.stopping = true;
        db.sync = sinon.stub();
        db.start(function() {
          db.sync.callCount.should.equal(1);
          db.node.services.bitcoind.once('tip', function() {
            db.sync.callCount.should.equal(1);
            done();
          });
          db.node.services.bitcoind.emit('tip', 10);
        });
      });
  
    });
  
    describe('#stop', function() {
      it('should wait until db has stopped syncing before closing leveldb', function(done) {
        var db = new DB(baseConfig);
        db.store = {
          close: sinon.stub().callsArg(0)
        };
        db.bitcoindSyncing = true;
  
        db.stop(function(err) {
          should.not.exist(err);
          done();
        });
  
        setTimeout(function() {
          db.bitcoindSyncing = false;
        }, 15);
      });
    });
  
    describe('#getTransaction', function() {
      it('will return a NotFound error', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          getTransaction: sinon.stub().callsArgWith(2, null, null)
        };
        var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
        db.getTransaction(txid, true, function(err) {
          err.should.be.instanceof(errors.Transaction.NotFound);
          done();
        });
      });
      it('will return an error from bitcoind', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          getTransaction: sinon.stub().callsArgWith(2, new Error('test error'))
        };
        var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
        db.getTransaction(txid, true, function(err) {
          err.message.should.equal('test error');
          done();
        });
      });
      it('will return an error from bitcoind', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          getTransaction: sinon.stub().callsArgWith(2, null, new Buffer(transactionData[0].hex, 'hex'))
        };
        var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623';
        db.getTransaction(txid, true, function(err, tx) {
          if (err) {
            throw err;
          }
          should.exist(tx);
          done();
        });
      });
    });
  
    describe('#loadTip', function() {
      it('genesis block if no metadata is found in the db', function(done) {
        var db = new DB(baseConfig);
        db.genesis = Block.fromBuffer(genesisBuffer);
        db.store = {
          get: sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError())
        };
        db.connectBlock = sinon.stub().callsArg(1);
        db.sync = sinon.stub();
        db.loadTip(function() {
          should.exist(db.tip);
          db.tip.hash.should.equal('00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206');
          done();
        });
      });
  
      it('tip from the database if it exists', function(done) {
        var node = {
          network: Networks.testnet,
          datadir: 'testdir',
          services: {
            bitcoind: {
              genesisBuffer: genesisBuffer,
              on: sinon.stub(),
              getBlockIndex: sinon.stub().returns({height: 1})
            }
          }
        };
        var tipHash = '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206';
        var tip = Block.fromBuffer(genesisBuffer);
        var db = new DB({node: node});
        db.store = {
          get: sinon.stub().callsArgWith(2, null, new Buffer(tipHash, 'hex'))
        };
        db.getBlock = sinon.stub().callsArgWith(1, null, tip);
        db.sync = sinon.stub();
        db.loadTip(function() {
          should.exist(db.tip);
          db.tip.hash.should.equal(tipHash);
          db.tip.__height.should.equal(1);
          done();
        });
      });
  
      it('give error if levelup error', function(done) {
        var node = {
          network: Networks.testnet,
          datadir: 'testdir',
          services: {
            bitcoind: {
              genesisBuffer: genesisBuffer,
              on: sinon.stub()
            }
          }
        };
        var db = new DB({node: node});
        db.store = {
          get: sinon.stub().callsArgWith(2, new Error('test'))
        };
        db.loadTip(function(err) {
          should.exist(err);
          err.message.should.equal('test');
          done();
        });
      });
  
      it('should try 3 times before giving error from getBlock', function(done) {
        var node = {
          network: Networks.testnet,
          datadir: 'testdir',
          services: {
            bitcoind: {
              genesisBuffer: genesisBuffer,
              on: sinon.stub(),
              getBlockIndex: sinon.stub().returns({height: 1})
            }
          }
        };
        var db = new DB({node: node});
        db.retryInterval = 10;
        var tipHash = '00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206';
        db.store = {
          get: sinon.stub().callsArgWith(2, null, new Buffer(tipHash, 'hex'))
        };
        db.getBlock = sinon.stub().callsArgWith(1, new Error('test'));
        db.loadTip(function(err) {
          should.exist(err);
          db.getBlock.callCount.should.equal(3);
          err.message.should.equal('test');
          done();
        });
      });
    });
  
    describe('#getBlock', function() {
      var db = new DB(baseConfig);
      var blockBuffer = new Buffer(blockData, 'hex');
      var expectedBlock = Block.fromBuffer(blockBuffer);
      db.node = {};
      db.node.services = {};
      db.node.services.bitcoind = {
        getBlock: sinon.stub().callsArgWith(1, null, blockBuffer)
      };
  
      it('should get the block from bitcoin daemon', function(done) {
        db.getBlock('00000000000000000593b60d8b4f40fd1ec080bdb0817d475dae47b5f5b1f735', function(err, block) {
          should.not.exist(err);
          block.hash.should.equal(expectedBlock.hash);
          done();
        });
      });
      it('should give an error when bitcoind.js gives an error', function(done) {
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {};
        db.node.services.bitcoind.getBlock = sinon.stub().callsArgWith(1, new Error('error'));
        db.getBlock('00000000000000000593b60d8b4f40fd1ec080bdb0817d475dae47b5f5b1f735', function(err, block) {
          should.exist(err);
          err.message.should.equal('error');
          done();
        });
      });
    });
  
    describe('#getBlockHashesByTimestamp', function() {
      it('should get the correct block hashes', function(done) {
        var db = new DB(baseConfig);
        var readStream = new EventEmitter();
        db.store = {
          createReadStream: sinon.stub().returns(readStream)
        };
  
        var block1 = {
          hash: '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b',
          timestamp: 1441911909
        };
  
        var block2 = {
          hash: '000000000383752a55a0b2891ce018fd0fdc0b6352502772b034ec282b4a1bf6',
          timestamp: 1441913112
        };
  
        db.getBlockHashesByTimestamp(1441914000, 1441911000, function(err, hashes) {
          should.not.exist(err);
          hashes.should.deep.equal([block2.hash, block1.hash]);
          done();
        });
  
        readStream.emit('data', {
          key: db._encodeBlockIndexKey(block2.timestamp),
          value: db._encodeBlockIndexValue(block2.hash)
        });
  
        readStream.emit('data', {
          key: db._encodeBlockIndexKey(block1.timestamp),
          value: db._encodeBlockIndexValue(block1.hash)
        });
  
        readStream.emit('close');
      });
  
      it('should give an error if the stream has an error', function(done) {
        var db = new DB(baseConfig);
        var readStream = new EventEmitter();
        db.store = {
          createReadStream: sinon.stub().returns(readStream)
        };
  
        db.getBlockHashesByTimestamp(1441911000, 1441914000, function(err, hashes) {
          should.exist(err);
          err.message.should.equal('error');
          done();
        });
  
        readStream.emit('error', new Error('error'));
  
        readStream.emit('close');
      });
  
      it('should give an error if the timestamp is out of range', function(done) {
        var db = new DB(baseConfig);
        var readStream = new EventEmitter();
        db.store = {
          createReadStream: sinon.stub().returns(readStream)
        };
  
        db.getBlockHashesByTimestamp(-1, -5, function(err, hashes) {
          should.exist(err);
          err.message.should.equal('Invalid Argument: timestamp out of bounds');
          done();
        });
      });
    });
  
    describe('#getPrevHash', function() {
      it('should return prevHash from bitcoind', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          getBlockIndex: sinon.stub().returns({
            prevHash: 'prevhash'
          })
        };
  
        db.getPrevHash('hash', function(err, prevHash) {
          should.not.exist(err);
          prevHash.should.equal('prevhash');
          done();
        });
      });
  
      it('should give an error if bitcoind could not find it', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          getBlockIndex: sinon.stub().returns(null)
        };
  
        db.getPrevHash('hash', function(err, prevHash) {
          should.exist(err);
          done();
        });
      });
    });
  
    describe('#getTransactionWithBlockInfo', function() {
      it('should give a transaction with height and timestamp', function(done) {
        var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex');
        var info = {
          blockHash: '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6',
          height: 530482,
          timestamp: 1439559434000,
          buffer: txBuffer
        };
  
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, info)
        };
  
        db.getTransactionWithBlockInfo('2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f', true, function(err, tx) {
          should.not.exist(err);
          tx.__blockHash.should.equal(info.blockHash);
          tx.__height.should.equal(info.height);
          tx.__timestamp.should.equal(info.timestamp);
          done();
        });
      });
      it('should give an error if one occurred', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, new Error('error'))
        };
  
        db.getTransactionWithBlockInfo('tx', true, function(err, tx) {
          should.exist(err);
          done();
        });
      });
    });
  
    describe('#sendTransaction', function() {
      it('should handle a basic serialized transaction hex string', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          sendTransaction: sinon.stub().returns('txid')
        };
  
        var tx = 'hexstring';
        db.sendTransaction(tx, function(err, txid) {
          should.not.exist(err);
          txid.should.equal('txid');
          done();
        });
      });
      it('should give the txid on success', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          sendTransaction: sinon.stub().returns('txid')
        };
  
        var tx = new Transaction();
        tx.serialize = sinon.stub().returns('txstring');
        db.sendTransaction(tx, function(err, txid) {
          should.not.exist(err);
          tx.serialize.callCount.should.equal(1);
          txid.should.equal('txid');
          done();
        });
      });
      it('should give an error if bitcoind threw an error', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          sendTransaction: sinon.stub().throws(new Error('error'))
        };
  
        var tx = new Transaction();
        tx.serialize = sinon.stub().returns('txstring');
        db.sendTransaction(tx, function(err, txid) {
          tx.serialize.callCount.should.equal(1);
          should.exist(err);
          done();
        });
      });
    });
  
    describe('#estimateFee', function() {
      it('should pass along the fee from bitcoind', function(done) {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        db.node.services.bitcoind = {
          estimateFee: sinon.stub().returns(1000)
        };
  
        db.estimateFee(5, function(err, fee) {
          should.not.exist(err);
          fee.should.equal(1000);
          db.node.services.bitcoind.estimateFee.args[0][0].should.equal(5);
          done();
        });
      });
    });
  
    describe('#connectBlock', function() {
      it('should remove block from mempool and call blockHandler with true', function(done) {
        var db = new DB(baseConfig);
        db.mempool = {
          removeBlock: sinon.stub()
        };
        db.runAllBlockHandlers = sinon.stub().callsArg(2);
        db.connectBlock({hash: 'hash'}, function(err) {
          should.not.exist(err);
          db.runAllBlockHandlers.args[0][1].should.equal(true);
          done();
        });
      });
    });
  
    describe('#disconnectBlock', function() {
      it('should call blockHandler with false', function(done) {
        var db = new DB(baseConfig);
        db.runAllBlockHandlers = sinon.stub().callsArg(2);
        db.disconnectBlock({hash: 'hash'}, function(err) {
          should.not.exist(err);
          db.runAllBlockHandlers.args[0][1].should.equal(false);
          done();
        });
      });
    });
  
    describe('#runAllBlockHandlers', function() {
      var db = new DB(baseConfig);
      var Service1 = function() {};
      Service1.prototype.blockHandler = sinon.stub().callsArgWith(2, null, ['op1', 'op2', 'op3']);
      var Service2 = function() {};
      Service2.prototype.blockHandler = sinon.stub().callsArgWith(2, null, ['op4', 'op5']);
      var Service3 = function() {};
      var Service4 = function() {};
      Service4.prototype.blockHandler = sinon.stub().callsArgWith(2, null, 'bad-value');
      db.node = {};
      db.node.services = {
        service1: new Service1(),
        service2: new Service2()
      };
      db.store = {
        batch: sinon.stub().callsArg(1)
      };
  
      var block = {
        hash: '00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863',
        header: {
          timestamp: 1441906365
        }
      };
  
      it('should call blockHandler in all services and perform operations', function(done) {
        db.runAllBlockHandlers(block, true, function(err) {
          should.not.exist(err);
          var tipOp = {
            type: 'put',
            key: DB.PREFIXES.TIP,
            value: new Buffer('00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863', 'hex')
          }
          var blockOp = {
            type: 'put',
            key: db._encodeBlockIndexKey(1441906365),
            value: db._encodeBlockIndexValue('00000000000000000d0aaf93e464ddeb503655a0750f8b9c6eed0bdf0ccfc863')
          };
          db.store.batch.args[0][0].should.deep.equal([tipOp, blockOp, 'op1', 'op2', 'op3', 'op4', 'op5']);
          done();
        });
      });
  
      it('should give an error if one of the services gives an error', function(done) {
        var Service3 = function() {};
        Service3.prototype.blockHandler = sinon.stub().callsArgWith(2, new Error('error'));
        db.node.services.service3 = new Service3();
  
        db.runAllBlockHandlers(block, true, function(err) {
          should.exist(err);
          done();
        });
      });
  
      it('should not give an error if a service does not have blockHandler', function(done) {
        db.node = {};
        db.node.services = {
          service3: new Service3()
        };
  
        db.runAllBlockHandlers(block, true, function(err) {
          should.not.exist(err);
          done();
        });
      });
  
      it('should throw an error if blockHandler gives unexpected result', function() {
        db.node = {};
        db.node.services = {
          service4: new Service4()
        };
  
        (function() {
          db.runAllBlockHandlers(block, true, function(err) {
            should.not.exist(err);
          });
        }).should.throw('bitcore.ErrorInvalidArgument');
      });
    });
  
    describe('#getAPIMethods', function() {
      it('should return the correct db methods', function() {
        var db = new DB(baseConfig);
        db.node = {};
        db.node.services = {};
        var methods = db.getAPIMethods();
        methods.length.should.equal(6);
      });
    });
  
    describe('#findCommonAncestor', function() {
      it('will find an ancestor 6 deep', function(done) {
        var db = new DB(baseConfig);
        db.tip = {
          hash: chainHashes[chainHashes.length - 1]
        };
  
        var expectedAncestor = chainHashes[chainHashes.length - 6];
  
        var mainBlocks = {};
        for(var i = chainHashes.length - 1; i > chainHashes.length - 10; i--) {
          var hash = chainHashes[i];
          var prevHash = hexlebuf(chainHashes[i - 1]);
          mainBlocks[hash] = {
            header: {
              prevHash: prevHash
            }
          };
        }
  
        var forkedBlocks = {
          'd7fa6f3d5b2fe35d711e6aca5530d311b8c6e45f588a65c642b8baf4b4441d82': {
            header: {
              prevHash: hexlebuf('76d920dbd83beca9fa8b2f346d5c5a81fe4a350f4b355873008229b1e6f8701a')
            },
            hash: 'd7fa6f3d5b2fe35d711e6aca5530d311b8c6e45f588a65c642b8baf4b4441d82'
          },
          '76d920dbd83beca9fa8b2f346d5c5a81fe4a350f4b355873008229b1e6f8701a': {
            header: {
              prevHash: hexlebuf('f0a0d76a628525243c8af7606ee364741ccd5881f0191bbe646c8a4b2853e60c')
            },
            hash: '76d920dbd83beca9fa8b2f346d5c5a81fe4a350f4b355873008229b1e6f8701a'
          },
          'f0a0d76a628525243c8af7606ee364741ccd5881f0191bbe646c8a4b2853e60c': {
            header: {
              prevHash: hexlebuf('2f72b809d5ccb750c501abfdfa8c4c4fad46b0b66c088f0568d4870d6f509c31')
            },
            hash: 'f0a0d76a628525243c8af7606ee364741ccd5881f0191bbe646c8a4b2853e60c'
          },
          '2f72b809d5ccb750c501abfdfa8c4c4fad46b0b66c088f0568d4870d6f509c31': {
            header: {
              prevHash: hexlebuf('adf66e6ae10bc28fc22bc963bf43e6b53ef4429269bdb65038927acfe66c5453')
            },
            hash: '2f72b809d5ccb750c501abfdfa8c4c4fad46b0b66c088f0568d4870d6f509c31'
          },
          'adf66e6ae10bc28fc22bc963bf43e6b53ef4429269bdb65038927acfe66c5453': {
            header: {
              prevHash: hexlebuf('3ea12707e92eed024acf97c6680918acc72560ec7112cf70ac213fb8bb4fa618')
            },
            hash: 'adf66e6ae10bc28fc22bc963bf43e6b53ef4429269bdb65038927acfe66c5453'
          },
          '3ea12707e92eed024acf97c6680918acc72560ec7112cf70ac213fb8bb4fa618': {
            header: {
              prevHash: hexlebuf(expectedAncestor)
            },
            hash: '3ea12707e92eed024acf97c6680918acc72560ec7112cf70ac213fb8bb4fa618'
          }
        };
        db.node.services = {};
        db.node.services.bitcoind = {
          getBlockIndex: function(hash) {
            var forkedBlock = forkedBlocks[hash];
            var mainBlock = mainBlocks[hash];
            var prevHash;
            if (forkedBlock && forkedBlock.header.prevHash) {
              prevHash = BufferUtil.reverse(forkedBlock.header.prevHash).toString('hex');
            } else if (mainBlock && mainBlock.header.prevHash){
              prevHash = BufferUtil.reverse(mainBlock.header.prevHash).toString('hex');
            } else {
              return null;
            }
            return {
              prevHash: prevHash
            };
          }
        };
        var block = forkedBlocks['d7fa6f3d5b2fe35d711e6aca5530d311b8c6e45f588a65c642b8baf4b4441d82'];
        db.findCommonAncestor(block, function(err, ancestorHash) {
          if (err) {
            throw err;
          }
          ancestorHash.should.equal(expectedAncestor);
          done();
        });
      });
    });
  
    describe('#syncRewind', function() {
      it('will undo blocks 6 deep', function() {
        var db = new DB(baseConfig);
        var ancestorHash = chainHashes[chainHashes.length - 6];
        db.tip = {
          __height: 10,
          hash: chainHashes[chainHashes.length],
          header: {
            prevHash: hexlebuf(chainHashes[chainHashes.length - 1])
          }
        };
        db.emit = sinon.stub();
        db.getBlock = function(hash, callback) {
          setImmediate(function() {
            for(var i = chainHashes.length; i > 0; i--) {
              var block = {
                hash: chainHashes[i],
                header: {
                  prevHash: hexlebuf(chainHashes[i - 1])
                }
              };
              if (chainHashes[i] === hash) {
                callback(null, block);
              }
            }
          });
        };
        db.node.services = {};
        db.disconnectBlock = function(block, callback) {
          setImmediate(callback);
        };
        db.findCommonAncestor = function(block, callback) {
          setImmediate(function() {
            callback(null, ancestorHash);
          });
        };
        var forkedBlock = {};
        db.syncRewind(forkedBlock, function(err) {
          if (err) {
            throw err;
          }
          db.tip.__height.should.equal(4);
          db.tip.hash.should.equal(ancestorHash);
        });
      });
    });
  
    describe('#sync', function() {
      var node = new EventEmitter();
      var syncConfig = {
        node: node,
        store: memdown
        };
      syncConfig.node.network = Networks.testnet;
      syncConfig.node.datadir = 'testdir';
      it('will get and add block up to the tip height', function(done) {
        var db = new DB(syncConfig);
        var blockBuffer = new Buffer(blockData, 'hex');
        var block = Block.fromBuffer(blockBuffer);
        db.node.services = {};
        db.runAllMempoolIndexes = sinon.stub().callsArg(0);
        db.node.services.bitcoind = {
          getBlock: sinon.stub().callsArgWith(1, null, blockBuffer),
          isSynced: sinon.stub().returns(true),
          height: 1
        };
        db.tip = {
          __height: 0,
          hash: lebufhex(block.header.prevHash)
        };
        db.emit = sinon.stub();
        db.cache = {
          hashes: {}
        };
        db.connectBlock = function(block, callback) {
          db.tip.__height += 1;
          callback();
        };
        db.node.once('synced', function() {
          db.runAllMempoolIndexes.callCount.should.equal(1);
          done();
        });
        db.sync();
      });
      it('will exit and emit error with error from bitcoind.getBlock', function(done) {
        var db = new DB(syncConfig);
        db.node.services = {};
        db.node.services.bitcoind = {
          getBlock: sinon.stub().callsArgWith(1, new Error('test error')),
          height: 1
        };
        db.tip = {
          __height: 0
        };
        db.node.on('error', function(err) {
          err.message.should.equal('test error');
          done();
        });
        db.sync();
      });
      it('will stop syncing when the node is stopping', function(done) {
        var db = new DB(syncConfig);
        var blockBuffer = new Buffer(blockData, 'hex');
        var block = Block.fromBuffer(blockBuffer);
        db.node.services = {};
        db.node.services.bitcoind = {
          getBlock: sinon.stub().callsArgWith(1, null, blockBuffer),
          isSynced: sinon.stub().returns(true),
          height: 1
        };
        db.tip = {
          __height: 0,
          hash: block.prevHash
        };
        db.emit = sinon.stub();
        db.cache = {
          hashes: {}
        };
        db.connectBlock = function(block, callback) {
          db.tip.__height += 1;
          callback();
        };
        db.node.stopping = true;
        var synced = false;
        db.node.once('synced', function() {
          synced = true;
        });
        db.sync();
        setTimeout(function() {
          synced.should.equal(false);
          done();
        }, 10);
      });
    });
  
  });
  