Top-level call to process all blocks in the blockchain.
        Goes and fetches all data-bearing transactions in order,
        and feeds them into the state engine implementation.
        
        Note that this method can take some time (hours, days) to complete 
        when called from the first block.
        
        Return True on success 
        Return False on error
        Raise an exception on recoverable error--the caller should simply try again.
        Exit on irrecoverable error--do not try to make forward progress

def build( cls, blockchain_opts, end_block_id, state_engine, expected_snapshots={}, tx_filter=None ):
        """
        Top-level call to process all blocks in the blockchain.
        Goes and fetches all data-bearing transactions in order,
        and feeds them into the state engine implementation.
        
        Note that this method can take some time (hours, days) to complete 
        when called from the first block.
        
        Return True on success 
        Return False on error
        Raise an exception on recoverable error--the caller should simply try again.
        Exit on irrecoverable error--do not try to make forward progress
        """
       
        first_block_id = state_engine.lastblock + 1
        if first_block_id >= end_block_id:
            # built 
            log.debug("Up-to-date ({} >= {})".format(first_block_id, end_block_id))
            return True 

        rc = True
        batch_size = config.BLOCK_BATCH_SIZE
        log.debug("Sync virtualchain state from {} to {}".format(first_block_id, end_block_id))
        
        for block_id in range( first_block_id, end_block_id+1, batch_size ):
            
            if not rc:
                break 
           
            last_block_id = min(block_id + batch_size, end_block_id)

            # get the blocks and transactions from the underlying blockchain
            block_ids_and_txs = transactions.get_virtual_transactions(state_engine.impl.get_blockchain(), blockchain_opts, block_id, last_block_id, tx_filter=tx_filter, spv_last_block=end_block_id - 1)
            if block_ids_and_txs is None:
                raise Exception("Failed to get virtual transactions {} to {}".format(block_id, last_block_id))

            # process in order by block ID
            block_ids_and_txs.sort()
            for processed_block_id, txs in block_ids_and_txs:

                if state_engine.get_consensus_at(processed_block_id) is not None:
                    raise Exception("Already processed block %s (%s)" % (processed_block_id, state_engine.get_consensus_at( processed_block_id )) )
                
                cls.db_set_indexing(True, state_engine.impl, state_engine.working_dir)

                ops = state_engine.parse_block(processed_block_id, txs)
                consensus_hash = state_engine.process_block(processed_block_id, ops, expected_snapshots=expected_snapshots)

                cls.db_set_indexing(False, state_engine.impl, state_engine.working_dir)
                
                if consensus_hash is None:
                    # request to stop
                    rc = False
                    log.debug("Stopped processing at block %s" % processed_block_id)
                    break

                log.debug("CONSENSUS({}): {}".format(processed_block_id, state_engine.get_consensus_at(processed_block_id)))

                # sanity check, if given 
                expected_consensus_hash = state_engine.get_expected_consensus_at( processed_block_id )
                if expected_consensus_hash is not None:
                    if str(consensus_hash) != str(expected_consensus_hash):
                        rc = False
                        log.error("FATAL: DIVERGENCE DETECTED AT {}: {} != {}".format(processed_block_id, consensus_hash, expected_consensus_hash))
                        traceback.print_stack()
                        os.abort()
       
            if not rc:
                break
        
        log.debug("Last block is %s" % state_engine.lastblock )
        return rc