class BlockchainState(BlockchainStateBase):
    """ Represents a blockchain state, using bitcoin-RPC to
    obtain information of transactions, addresses, and blocks. """
    def __init__(self, bitcoind):
        self.bitcoind = bitcoind

    def publish_tx(self, txdata):
        return self.bitcoind.sendrawtransaction(txdata)

    @classmethod
    def from_url(cls, url, testnet=False):
        if testnet:
            bitcoind = bitcoin.rpc.RawProxy(
                service_url=url, service_port=18332)
        else:
            bitcoind = bitcoin.rpc.RawProxy(  # pragma: no cover
                service_url=url)              # pragma: no cover
        return cls(bitcoind)

    def get_block_height(self, blockhash):
        block = self.bitcoind.getblock(blockhash)
        return block['height']

    def get_block_count(self):
        return self.bitcoind.getblockcount()

    def get_block_hash(self, index):
        return self.bitcoind.getblockhash(index)

    def get_block(self, blockhash):
        return self.bitcoind.getblock(blockhash)

    def get_blockhash_at_height(self, height):
        return self.bitcoind.getblockhash(height)

    def get_previous_blockinfo(self, blockhash):
        block_data = self.bitcoind.getblock(blockhash)
        return block_data['previousblockhash'], block_data['height']

    def get_tx_blockhash(self, txhash):
        try:
            raw = self.bitcoind.getrawtransaction(txhash, 1)
        except Exception, e:
            # print txhash, e
            return None, False
        return raw.get('blockhash', None), True

    def get_raw(self, txhash):
        return self.bitcoind.getrawtransaction(txhash, 0)

    def get_tx(self, txhash):
        txhex = self.bitcoind.getrawtransaction(txhash, 0)
        txbin = bitcoin.core.x(txhex)
        tx = bitcoin.core.CTransaction.deserialize(txbin)
        return CTransaction.from_bitcoincore(txhash, tx, self)

    def get_best_blockhash(self):
        try:
            return self.bitcoin.getbestblockhash()
        except:
            # warning: not atomic!
            # remove once bitcoin 0.9 becomes commonplace
            count = self.bitcoind.getblockcount()
            return self.bitcoind.getblockhash(count)

    def iter_block_txs(self, blockhash):
        block_hex = None
        try:
            block_hex = self.bitcoind.getblock(blockhash, False)
        except bitcoin.rpc.JSONRPCException:
            pass

        if block_hex:
            # block at once
            block = bitcoin.core.CBlock.deserialize(bitcoin.core.x(block_hex))
            block_hex = None
            for tx in block.vtx:
                txhash = bitcoin.core.b2lx(
                    bitcoin.core.serialize.Hash(tx.serialize()))
                yield CTransaction.from_bitcoincore(txhash, tx, self)
        else:
            txhashes = self.bitcoind.getblock(blockhash)['tx']
            for txhash in txhashes:
                yield self.get_tx(txhash)

    def sort_txs(self, tx_list):
        block_txs = {h:self.get_tx(h) for h in tx_list}

        def get_dependent_txs(tx):
            """all transactions from current block this transaction
            directly depends on"""
            dependent_txs = []
            for inp in tx.inputs:
                if inp.prevout.hash in block_txs:
                    dependent_txs.append(block_txs[inp.prevout.hash])
            return dependent_txs

        return toposorted(block_txs.values(), get_dependent_txs)

    def get_mempool_txs(self):
        return self.sort_txs(self.bitcoind.getrawmempool()) class ColorDataBuilderManager(object):
    """Manages multiple color data builders, one per color"""
    def __init__(self, colormap, blockchain_state,
                 cdstore, metastore, builder_class):
        self.colormap = colormap
        self.metastore = metastore
        self.blockchain_state = blockchain_state
        self.cdstore = cdstore
        self.builders = {}
        self.builder_class = builder_class

    def get_color_def_map(self, color_id_set):
        """given a set of color_ids <color_id_set>, return
        a dict of color_id to color_def.
        """
        color_def_map = {}
        for color_id in color_id_set:
            color_def_map[color_id] = self.colormap.get_color_def(color_id)
        return color_def_map

    def get_builder(self, color_id):
        if color_id in self.builders:
            return self.builders[color_id]
        colordef = self.colormap.get_color_def(color_id)
        builder = self.builder_class(
            self.cdstore, self.blockchain_state, colordef, self.metastore)
        self.builders[color_id] = builder
        return builder

    def ensure_scanned_upto(self, color_id_set, blockhash):
        """ Ensure color data is available up to a given block"""
        for color_id in color_id_set:
            if color_id == 0:
                continue
            builder = self.get_builder(color_id)
            builder.ensure_scanned_upto(blockhash)

    def scan_txhash(self, color_id_set, txhash, output_indices=None):
        tx = self.blockchain_state.get_tx(txhash)
        self.scan_tx(color_id_set, tx, output_indices)

    def scan_tx(self, color_id_set, tx, output_indices=None):
        with self.cdstore.transaction():
            for color_id in color_id_set:
                if color_id == 0:
                    continue
                builder = self.get_builder(color_id)
                builder.scan_tx(tx, output_indices) class FullScanColorDataBuilder(BasicColorDataBuilder):
    """Color data builder based on exhaustive blockchain scan,
       for one specific color"""
    def __init__(self, cdstore, blockchain_state, colordef, metastore):
        super(FullScanColorDataBuilder, self).__init__(
            cdstore, blockchain_state, colordef, metastore)
        self.genesis_blockhash = self.blockchain_state.get_blockhash_at_height(
            self.colordef.genesis['height'])

    def scan_block(self, blockhash):
        if self.metastore.did_scan(self.color_id, blockhash):
            return
        for tx in self.blockchain_state.iter_block_txs(blockhash):
            self.scan_tx(tx)
        self.metastore.set_as_scanned(self.color_id, blockhash)

    def scan_blockchain(self, blocklist):
        with self.cdstore.transaction():
            for i, blockhash in enumerate(blocklist):
                self.scan_block(blockhash)
                if i % 25 == 0:  # sync each 25 blocks
                    self.cdstore.sync()

    def ensure_scanned_upto(self, final_blockhash):
        if self.metastore.did_scan(self.color_id, final_blockhash):
            return

        # start from the final_blockhash and go backwards to build up
        #  the list of blocks to scan
        blockhash = final_blockhash
        genesis_height = self.blockchain_state.get_block_height(
            self.genesis_blockhash)
        blocklist = []
        while not self.metastore.did_scan(self.color_id, blockhash):
            blocklist.insert(0, blockhash)
            blockhash, height = self.blockchain_state.get_previous_blockinfo(
                blockhash)
            if blockhash == self.genesis_blockhash:
                break  # pragma: no cover
            # sanity check
            if height < genesis_height:
                break  # pragma: no cover

        self.scan_blockchain(blocklist) class AidedColorDataBuilder(BasicColorDataBuilder):
    """Color data builder based on following output spending transactions
        from the color's genesis transaction output, for one specific color"""

    def __init__(self, cdstore, blockchain_state, colordef, metastore):
        super(AidedColorDataBuilder, self).__init__(
            cdstore, blockchain_state, colordef, metastore)

    def scan_blockchain(self, blocklist):
        txo = self.colordef.genesis.copy()
        txo["blockhash"] = self.genesis_blockhash
        txo_queue = [txo]
        for blockhash in blocklist:
            if self.metastore.did_scan(self.color_id, blockhash):
                continue
            # remove txs from this block from the queue
            block_txo_queue = [txo for txo in txo_queue
                               if txo.get('blockhash') == blockhash]
            txo_queue = [txo for txo in txo_queue
                         if txo.get('blockhash') != blockhash]

            block_txos = {}
            while block_txo_queue:
                txo = block_txo_queue.pop()
                if txo['txhash'] in block_txos:
                    continue
                block_txos[txo['txhash']] = txo
                spends = get_spends(txo['txhash'], self.blockchain_state)
                for stxo in spends:
                    if stxo['blockhash'] == blockhash:
                        block_txo_queue.append(stxo)
                    else:
                        txo_queue.append(stxo)
            block_txs = {}
            for txhash in block_txos.keys():
                block_txs[txhash] = self.blockchain_state.get_tx(txhash)

            def get_prev_txs(tx):
                """all transactions from current block this transaction
                   directly depends on"""
                prev_txs = []
                for inp in tx.inputs:
                    if inp.prevout.hash in block_txs:
                        prev_txs.append(block_txs[inp.prevout.hash])
                return prev_txs

            sorted_block_txs = toposorted(block_txs.values(), get_prev_txs)

            for tx in sorted_block_txs:
                self.scan_tx(tx)
            self.metastore.set_as_scanned(self.color_id, blockhash) class ThickColorData(StoredColorData):
    """ Color data which needs access to the whole blockchain state"""
    def __init__(self, *args, **kwargs):
        super(ThickColorData, self).__init__(*args, **kwargs)
        self.mempool_cache = []

    def get_colorvalues(self, color_id_set, txhash, outindex):
        blockhash, found = self.blockchain_state.get_tx_blockhash(txhash)
        if not found:
            raise UnfoundTransactionError("Transaction %s not found!" % txhash)
        if blockhash:
            self.cdbuilder_manager.ensure_scanned_upto(color_id_set, blockhash)
            return self._fetch_colorvalues(color_id_set, txhash, outindex)
        else:
            # not in the blockchain, but might be in the memory pool
            best_blockhash = None
            while 1:
                best_blockhash_prev = self.blockchain_state.get_best_blockhash()
                mempool = self.blockchain_state.get_mempool_txs()
                best_blockhash = self.blockchain_state.get_best_blockhash()
                if best_blockhash_prev == best_blockhash:
                    break
            if txhash not in [tx.hash for tx in mempool]:
                raise UnfoundTransactionError("Transaction %s not found in mempool!" % txhash)
            # the preceding blockchain
            self.cdbuilder_manager.ensure_scanned_upto(
                color_id_set, best_blockhash)
            # scan everything in the mempool
            for tx in mempool:
                self.cdbuilder_manager.scan_tx(color_id_set, tx)
            return self._fetch_colorvalues(color_id_set, txhash, outindex) class ThinColorData(StoredColorData):
    """ Color data which needs access to the blockchain state up to the genesis of
        color."""
    def get_colorvalues(self, color_id_set, txhash, outindex):
        """
        for a given transaction <txhash> and output <outindex> and color
        <color_id_set>, return a list of dicts that looks like this:
        {
        'color_id': <color id>,
        'value': <colorvalue of this output>,
        'label': <currently unused>,
        }
        These correspond to the colorvalues of particular color ids for this
        output. Currently, each output should have a single element in the list.
        """
        color_def_map = self.cdbuilder_manager.get_color_def_map(color_id_set)

        scanned_outputs = set()

        def process(current_txhash, current_outindex):
            """For any tx out, process the colorvalues of the affecting
            inputs first and then scan that tx.
            """
            if (current_txhash, current_outindex) in scanned_outputs:
                return
            scanned_outputs.add((current_txhash, current_outindex))

            if self._fetch_colorvalues(color_id_set, current_txhash, current_outindex):
                return

            current_tx = self.blockchain_state.get_tx(current_txhash)
            if not current_tx:
                raise UnfoundTransactionError("Transaction %s not found!" % current_txhash)

            # note a genesis tx will simply have 0 affecting inputs
            inputs = set()
            for color_id, color_def in color_def_map.items():
                inputs = inputs.union(
                    color_def.get_affecting_inputs(current_tx,
                                                   [current_outindex]))
            for i in inputs:
                process(i.prevout.hash, i.prevout.n)
            self.cdbuilder_manager.scan_tx(color_id_set, current_tx, [current_outindex])

        process(txhash, outindex)
        return self._fetch_colorvalues(color_id_set, txhash, outindex) class ColorMap(object):
    """ Manages and obtains color definitions """
    def __init__(self, metastore):
        self.metastore = metastore
        self.colordefs = {}

    def find_color_desc(self, color_id):
        if color_id == 0:
            return ""
        else:
            return self.metastore.find_color_desc(color_id)

    def resolve_color_desc(self, color_desc, auto_add=True):
        if color_desc == "":
            return 0
        else:
            return self.metastore.resolve_color_desc(color_desc, auto_add)

    def get_color_def(self, color_id_or_desc):
        """ Finds a color definition given an id or description """
        if color_id_or_desc == 0 or color_id_or_desc == '':
            return UNCOLORED_MARKER
        color_id = color_id_or_desc
        color_desc = None
        if not isinstance(color_id, (int, long)):
            color_desc = color_id_or_desc
            color_id = self.resolve_color_desc(color_id_or_desc)
        if color_id in self.colordefs:
            return self.colordefs[color_id]
        if not color_desc:
            color_desc = self.find_color_desc(color_id)
            if not color_desc:
                raise InvalidColorIdError("Color id '%s' not found!" % color_id)
        cd = ColorDefinition.from_color_desc(
            color_id, color_desc)
        self.colordefs[color_id] = cd
        return cd class DataStoreConnection(object):
    """ A database connection """
    def __init__(self, path, autocommit=False):
        self.path = path
        self.conn = sqlite3.connect(path, check_same_thread=False)
        if autocommit:
            self.conn.isolation_level = None

    def __del__(self):
        if self.conn:
            self.conn.commit()
            self.conn.close() class ColorDataStore(DataStore):
    """ A DataStore for color data storage. """
    def __init__(self, conn, tablename='colordata'):
        super(ColorDataStore, self).__init__(conn)
        self.tablename = tablename
        if not self.table_exists(tablename):
            statement = "CREATE TABLE {0} (color_id INTEGER, txhash TEXT, " \
                "outindex INTEGER, value REAL, label TEXT);".format(tablename)
            self.execute(statement)
            statement = "CREATE UNIQUE INDEX {0}_data_idx on {0}(color_id, " \
                "txhash, outindex)".format(tablename)
            self.execute(statement)
        self.queries = dict()
        self.queries['add'] = "INSERT OR REPLACE INTO {0} VALUES " \
            "(?, ?, ?, ?, ?)".format(self.tablename)
        self.queries['remove'] = "DELETE FROM {0} WHERE color_id = ? " \
            "AND txhash = ? AND outindex = ?".format(self.tablename)
        self.queries['get'] = "SELECT value, label FROM {0} WHERE " \
            "color_id = ? AND txhash = ? AND outindex = ?".format(
                self.tablename)
        self.queries['get_any'] = "SELECT color_id, value, label FROM " \
            "{0} WHERE txhash = ? AND outindex = ?".format(self.tablename)
        self.queries['get_all'] = "SELECT txhash, outindex, value, " \
            "label FROM {0} WHERE color_id = ?".format(self.tablename)

    def add(self, color_id, txhash, outindex, value, label):
        self.execute(
            self.queries['add'], (color_id, txhash, outindex, value, label))

    def remove(self, color_id, txhash, outindex):
        self.execute(self.queries['remove'], (color_id, txhash, outindex))

    def get(self, color_id, txhash, outindex):
        return self.execute(
            self.queries['get'], (color_id, txhash, outindex)).fetchone()

    def get_any(self, txhash, outindex):
        return self.execute(
            self.queries['get_any'], (txhash, outindex)).fetchall()

    def get_all(self, color_id):
        return self.execute(self.queries['get_all'], (color_id,)).fetchall() class ColorMetaStore(DataStore):
    """ A DataStore containing meta-information
    on a coloring scheme, like color ids, how much
    of the blockchain was scanned, etc."""
    def __init__(self, conn):
        super(ColorMetaStore, self).__init__(conn)
        if not self.table_exists('scanned_block'):
            self.execute(
                "CREATE TABLE scanned_block "
                "(color_id INTEGER, blockhash TEXT)")
            self.execute(
                "CREATE UNIQUE INDEX scanned_block_idx "
                "on scanned_block(color_id, blockhash)")
        if not self.table_exists('color_map'):
            self.execute(
                "CREATE TABLE color_map (color_id INTEGER PRIMARY KEY "
                "AUTOINCREMENT, color_desc TEXT)")
            self.execute(
                "CREATE UNIQUE INDEX color_map_idx ON color_map(color_desc)")

    def did_scan(self, color_id, blockhash):
        return unwrap1(
            self.execute(
                "SELECT 1 FROM scanned_block WHERE "
                "color_id = ? AND blockhash = ?",
                (color_id, blockhash)).fetchone())

    def set_as_scanned(self, color_id, blockhash):
        self.execute(
            "INSERT INTO scanned_block (color_id, blockhash) "
            "VALUES (?, ?)",
            (color_id, blockhash))

    def resolve_color_desc(self, color_desc, auto_add):
        q = "SELECT color_id FROM color_map WHERE color_desc = ?"
        res = self.execute(q, (color_desc, )).fetchone()
        if (res is None) and auto_add:
            self.execute(
                "INSERT INTO color_map(color_id, color_desc) VALUES (NULL, ?)",
                (color_desc,))
            self.sync()
            res = self.execute(q, (color_desc, )).fetchone()
        return unwrap1(res)

    def find_color_desc(self, color_id):
        q = "SELECT color_desc FROM color_map WHERE color_id = ?"
        return unwrap1(self.execute(q, (color_id,)).fetchone())
import datetime
import unittest
from coloredcoinlib.blockchain import BlockchainState
from coloredcoinlib.builder import (ColorDataBuilderManager,
                                    FullScanColorDataBuilder, AidedColorDataBuilder)
from coloredcoinlib.colordata import ThickColorData, ThinColorData
from coloredcoinlib.colormap import ColorMap
from coloredcoinlib.store import DataStoreConnection, ColorDataStore, ColorMetaStore
        g = self.colordata.get_colorvalues

        cv = g(
            br_set,
            "b1586cd10b32f78795b86e9a3febe58dcb59189175fad884a7f4a6623b77486e",
            0)[0]
        
        self.assertEqual(cv.get_color_id(), self.blue_id)
        self.assertEqual(cv.get_value(), 1000)


        cv = g(
            br_set,
            "8f6c8751f39357cd42af97a67301127d497597ae699ad0670b4f649bd9e39abf",
            0)[0]
        self.assertEqual(cv.get_color_id(), self.red_id)
        self.assertEqual(cv.get_value(), 1000)

        cvs = g(
            br_set,
            "b1586cd10b32f78795b86e9a3febe58dcb59189175fad884a7f4a6623b77486e",
            1)
        self.assertEqual(len(cvs), 0)

        cvs = g(
            br_set,
            "8f6c8751f39357cd42af97a67301127d497597ae699ad0670b4f649bd9e39abf",
            1)
        self.assertEqual(len(cvs), 0)

        cv = g(
            br_set,
            'c1d8d2fb75da30b7b61e109e70599c0187906e7610fe6b12c58eecc3062d1da5',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.red_id)
        self.assertEqual(cv.get_value(), 500)
        
        cv = g(
            br_set,
            '36af9510f65204ec5532ee62d3785584dc42a964013f4d40cfb8b94d27b30aa1',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.red_id)
        self.assertEqual(cv.get_value(), 150)

        cvs = g(
            br_set,
            '3a60b70d425405f3e45f9ed93c30ca62b2a97e692f305836af38a524997dd01d',
            0)
        self.assertEqual(len(cvs), 0)

        cv = g(
            br_set,
            '8f6c8751f39357cd42af97a67301127d497597ae699ad0670b4f649bd9e39abf',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.red_id)
        self.assertEqual(cv.get_value(), 1000)

        cv = g(
            br_set,
            'f50f29906ce306be3fc06df74cc6a4ee151053c2621af8f449b9f62d86cf0647',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.blue_id)
        self.assertEqual(cv.get_value(), 500)


        self.cdbuilder.scan_txhash(blue_set, '7e40d2f414558be60481cbb976e78f2589bc6a9f04f38836c18ed3d10510dce5')

        cv = g(
            br_set,
            '7e40d2f414558be60481cbb976e78f2589bc6a9f04f38836c18ed3d10510dce5',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.blue_id)
        self.assertEqual(cv.get_value(), 100)

        cv = g(
            br_set,
            '4b60bb49734d6e26d798d685f76a409a5360aeddfddcb48102a7c7ec07243498',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.red_id)

        cvs = g(
            br_set,
            '342f119db7f9989f594d0f27e37bb5d652a3093f170de928b9ab7eed410f0bd1',
            0)
        self.assertEqual(len(cvs), 0)

        cv = g(
            br_set,
            'bd34141daf5138f62723009666b013e2682ac75a4264f088e75dbd6083fa2dba',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.blue_id)

        cvs = g(
            br_set,
            'bd34141daf5138f62723009666b013e2682ac75a4264f088e75dbd6083fa2dba',
            1)
        self.assertEqual(len(cvs), 0)

        cv = g(
            br_set,
            '36af9510f65204ec5532ee62d3785584dc42a964013f4d40cfb8b94d27b30aa1',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.red_id)

        cv = g(
            br_set,
            '741a53bf925510b67dc0d69f33eb2ad92e0a284a3172d4e82e2a145707935b3e',
            0)[0]
        self.assertEqual(cv.get_color_id(), self.red_id)

        cv = g(
            br_set,
            '741a53bf925510b67dc0d69f33eb2ad92e0a284a3172d4e82e2a145707935b3e',
            1)[0]
        self.assertEqual(cv.get_color_id(), self.red_id)


class TestFullScan(TestAided):

    def makeBuilder(self):

        self.cdbuilder = ColorDataBuilderManager(self.colormap,