class EPOBCColorDefinition(GenesisColorDefinition):
    CLASS_CODE = 'epobc'

    class Tag(object):
        XFER_TAG_BITS = [1, 1, 0, 0, 1, 1]
        GENESIS_TAG_BITS = [1, 0, 1, 0, 0, 1]
        def __init__(self, padding_code, is_genesis):
            self.is_genesis = is_genesis
            self.padding_code = padding_code

        @classmethod
        def closest_padding_code(cls, min_padding):
            if min_padding <= 0:
                return 0
            padding_code = 1
            while 2 ** padding_code < min_padding:
                padding_code += 1
            if padding_code > 63:
                raise Exception('Requires too much padding!')
            return padding_code

        @classmethod
        def from_nSequence(cls, nSequence):
            bits = uint_to_bit_list(nSequence)
            tag_bits = bits[0:6]

            if ((tag_bits != cls.XFER_TAG_BITS) and
                (tag_bits != cls.GENESIS_TAG_BITS)):
                return None

            padding_code = bit_list_to_uint(bits[6:12])
            return cls(padding_code, tag_bits == cls.GENESIS_TAG_BITS)

        def to_nSequence(self):
            if self.is_genesis:
                bits = self.GENESIS_TAG_BITS[:]
            else:
                bits = self.XFER_TAG_BITS[:]
            bits += uint_to_bit_list(self.padding_code,
                                     6)
            bits += [0] * (32 - 12)
            return bit_list_to_uint(bits)

        def get_padding(self):
            if self.padding_code == 0:
                return 0
            else:
                return 2 ** self.padding_code

    @classmethod
    def get_tag(cls, tx):
        if tx.raw.vin[0].prevout.is_null():
            # coinbase tx is neither genesis nor xfer
            return None
        else:
            return cls.Tag.from_nSequence(tx.raw.vin[0].nSequence)

    @classmethod
    def get_xfer_affecting_inputs(cls, tx, padding, out_index):
        """
        Returns a set of indices that correspond to the inputs
        for an output in the transaction tx with output index out_index
        which has a padding of padding (2^n for some n>0 or 0).
        """
        tx.ensure_input_values()
        out_prec_sum = 0
        for oi in range(out_index):
            value_wop = tx.outputs[oi].value - padding
            if value_wop <= 0:
                return set()
            out_prec_sum += value_wop
        out_value_wop = tx.outputs[out_index].value - padding
        if out_value_wop <= 0:
            return set()
        affecting_inputs = set()
        input_running_sum = 0
        for ii, inp in enumerate(tx.inputs):
            prev_tag = cls.get_tag(inp.prevtx)
            if not prev_tag:
                break
            value_wop = tx.inputs[ii].value - prev_tag.get_padding()
            if value_wop <= 0:
                break
            if ((input_running_sum < (out_prec_sum + out_value_wop)) and
                ((input_running_sum + value_wop) > out_prec_sum)):
                affecting_inputs.add(ii)
            input_running_sum += value_wop
        return affecting_inputs


    def run_kernel(self, tx, in_colorvalues):
        """Given a transaction tx and the colorvalues in a list
        in_colorvalues, output the colorvalues of the outputs in a list
        """
        tag = self.get_tag(tx)
        if tag is None:
            return [None] * len(tx.outputs)
        if tag.is_genesis:
            if tx.hash == self.genesis['txhash']:
                value_wop = tx.outputs[0].value - tag.get_padding()
                if value_wop > 0:
                    return ([SimpleColorValue(colordef=self,
                                              value=value_wop)] +
                            [None] * (len(tx.outputs) - 1))
            # we get here if it is a genesis for a different color
            # or if genesis transaction is misconstructed
            return [None] * len(tx.outputs)

        tx.ensure_input_values()
        padding = tag.get_padding()
        out_colorvalues = []
        for out_idx, output in enumerate(tx.outputs):
            out_value_wop = tx.outputs[out_idx].value - padding
            if out_value_wop <= 0:
                out_colorvalues.append(None)
                continue
            affecting_inputs = self.get_xfer_affecting_inputs(tx, tag.get_padding(),
                                                              out_idx)
            ai_colorvalue = SimpleColorValue(colordef=self, value=0)
            all_colored = True
            for ai in affecting_inputs:
                if in_colorvalues[ai] is None:
                    all_colored = False
                    break
                ai_colorvalue += in_colorvalues[ai]
            if (not all_colored) or (ai_colorvalue.get_value() < out_value_wop):
                out_colorvalues.append(None)
                continue
            out_colorvalues.append(SimpleColorValue(colordef=self, value=out_value_wop))
        return out_colorvalues

    def get_affecting_inputs(self, tx, output_set):
        tag = self.get_tag(tx)
        if (tag is None) or tag.is_genesis:
            return set()
        tx.ensure_input_values()
        aii = set()
        for out_idx in output_set:
            aii.update(self.get_xfer_affecting_inputs(
                    tx, tag.get_padding(), out_idx))
        inputs = set([tx.inputs[i] for i in aii])
        return inputs

    def compose_tx_spec(self, op_tx_spec):
        targets_by_color = group_targets_by_color(op_tx_spec.get_targets(), self.__class__)
        uncolored_targets = targets_by_color.pop(UNCOLORED_MARKER.color_id, [])
        composed_tx_spec = op_tx_spec.make_composed_tx_spec()
        if uncolored_targets:
            uncolored_needed = ColorTarget.sum(uncolored_targets)
        else:
            uncolored_needed = SimpleColorValue(colordef=UNCOLORED_MARKER,
                                                value=0)
        dust_threshold = op_tx_spec.get_dust_threshold().get_value()
        inputs_by_color = dict()
        min_padding = 0

        # step 1: get inputs, create change targets, compute min padding
        for color_id, targets in targets_by_color.items():
            color_def = targets[0].get_colordef()
            needed_sum = ColorTarget.sum(targets)
            inputs, total = op_tx_spec.select_coins(needed_sum)
            inputs_by_color[color_id] = inputs
            change = total - needed_sum
            if change > 0:
                targets.append(
                    ColorTarget(op_tx_spec.get_change_addr(color_def), change))
            for target in targets:
                padding_needed = dust_threshold - target.get_value()
                if padding_needed > min_padding:
                    min_padding = padding_needed

        tag = self.Tag(self.Tag.closest_padding_code(min_padding), False)
        padding = tag.get_padding()

        # step 2: create txins & txouts, compute uncolored requirements
        for color_id, targets in targets_by_color.items():
            color_def = targets[0].get_colordef()
            for inp in inputs_by_color[color_id]:
                composed_tx_spec.add_txin(inp)
                uncolored_needed -= SimpleColorValue(colordef=UNCOLORED_MARKER,
                                               value=inp.value)
            for target in targets:
                svalue = target.get_value() + padding
                composed_tx_spec.add_txout(value=svalue,
                                           target=target)
                uncolored_needed += SimpleColorValue(colordef=UNCOLORED_MARKER,
                                               value=svalue)

        composed_tx_spec.add_txouts(uncolored_targets)
        fee = composed_tx_spec.estimate_required_fee()

        uncolored_change = None

        if uncolored_needed + fee > 0:
            uncolored_inputs, uncolored_total = op_tx_spec.select_coins(uncolored_needed, composed_tx_spec)
            composed_tx_spec.add_txins(uncolored_inputs)
            fee = composed_tx_spec.estimate_required_fee()
            uncolored_change = uncolored_total - uncolored_needed - fee
        else:
            uncolored_change =  (- uncolored_needed) - fee

        if uncolored_change > op_tx_spec.get_dust_threshold():
            composed_tx_spec.add_txout(value=uncolored_change,
                                       target_addr=op_tx_spec.get_change_addr(UNCOLORED_MARKER),
                                       is_fee_change=True)

        composed_tx_spec.txins[0].set_nSequence(tag.to_nSequence())

        return composed_tx_spec

    @classmethod
    def compose_genesis_tx_spec(cls, op_tx_spec):
        if len(op_tx_spec.get_targets()) != 1:
            msg = 'Genesis transaction spec needs exactly one target!'
            raise InvalidTargetError(msg)
        g_target = op_tx_spec.get_targets()[0]
        if g_target.get_colordef() != GENESIS_OUTPUT_MARKER:
            msg = 'Genesis transaction target should use -1 color_id!'
            raise InvalidColorError(msg)
        g_value = g_target.get_value()
        padding_needed = op_tx_spec.get_dust_threshold().get_value() - g_value
        tag = cls.Tag(cls.Tag.closest_padding_code(padding_needed), True)
        padding = tag.get_padding()
        composed_tx_spec = op_tx_spec.make_composed_tx_spec()
        composed_tx_spec.add_txout(value=padding + g_value,
                                   target=g_target)
        uncolored_needed = SimpleColorValue(colordef=UNCOLORED_MARKER,
                                           value=padding + g_value)
        uncolored_inputs, uncolored_total = op_tx_spec.select_coins(uncolored_needed,
                                                                    composed_tx_spec)
        composed_tx_spec.add_txins(uncolored_inputs)

        fee = composed_tx_spec.estimate_required_fee()
        change = uncolored_total - uncolored_needed - fee
        if change > op_tx_spec.get_dust_threshold():
            composed_tx_spec.add_txout(value=change,
                                       target_addr=op_tx_spec.get_change_addr(UNCOLORED_MARKER),
                                       is_fee_change=True)
        composed_tx_spec.txins[0].set_nSequence(tag.to_nSequence())
        return composed_tx_spec class OBColorDefinition(GenesisColorDefinition):
    """Implements order-based coloring scheme"""
    CLASS_CODE = 'obc'

    def run_kernel(self, tx, in_colorvalues):
        out_colorvalues = []
        inp_index = 0
        cur_value = 0
        colored = False

        is_genesis = (tx.hash == self.genesis['txhash'])

        tx.ensure_input_values()

        for out_index in xrange(len(tx.outputs)):
            o = tx.outputs[out_index]
            if cur_value == 0:
                colored = True  # reset
            while cur_value < o.value:
                cur_value += tx.inputs[inp_index].value
                if colored:
                    colored = (in_colorvalues[inp_index] is not None)
                inp_index += 1

            is_genesis_output = is_genesis and (
                out_index == self.genesis['outindex'])

            if colored or is_genesis_output:
                cv = SimpleColorValue(colordef=self, value=o.value)
                out_colorvalues.append(cv)
            else:
                out_colorvalues.append(None)
        return out_colorvalues

    def get_affecting_inputs(self, tx, output_set):
        """Returns a set object consisting of inputs that correspond to the
        output indexes of <output_set> from transaction <tx>
        """
        if self.is_special_tx(tx):
            return set()
        tx.ensure_input_values()
        running_sum_inputs = []
        current_sum = 0
        for txin in tx.inputs:
            current_sum += txin.value
            running_sum_inputs.append(current_sum)
        running_sum_outputs = []
        current_sum = 0
        for output in tx.outputs:
            current_sum += output.value
            running_sum_outputs.append(current_sum)

        matching_input_set = set()

        num_inputs = len(running_sum_inputs)
        num_outputs = len(running_sum_outputs)
        for o in output_set:
            if o == 0:
                o_start = 0
            else:
                o_start = running_sum_outputs[o-1]
            o_end = running_sum_outputs[o]
            for i in range(num_inputs):
                if i == 0:
                    i_start = 0
                else:
                    i_start = running_sum_inputs[i - 1]
                i_end = running_sum_inputs[i]
                # if segments overlap
                if (o_start < i_end and i_end <= o_end) \
                        or (i_start < o_end and o_end <= i_end):
                    matching_input_set.add(tx.inputs[i])
        return matching_input_set

    @classmethod
    def compose_genesis_tx_spec(self, op_tx_spec):
        targets = op_tx_spec.get_targets()
        if len(targets) != 1:
            msg = 'Genesis transaction spec needs exactly one target!'
            raise InvalidTargetError(msg)
        target = targets[0]
        if target.get_colordef() != GENESIS_OUTPUT_MARKER:
            msg = 'Genesis transaction target should use -1 color_id!'
            raise InvalidColorError(msg)
        composed_tx_spec = op_tx_spec.make_composed_tx_spec()
        composed_tx_spec.add_txout(value=target.get_value(),
                                   target_addr=target.get_address())
        uncolored_value = SimpleColorValue(colordef=UNCOLORED_MARKER,
                                           value=target.get_value())
        inputs, total = op_tx_spec.select_coins(uncolored_value, composed_tx_spec)
        composed_tx_spec.add_txins(inputs)
        change = total - uncolored_value - composed_tx_spec.estimate_required_fee()
        if change > op_tx_spec.get_dust_threshold():
            composed_tx_spec.add_txout(value=change,
                                       target_addr=op_tx_spec.get_change_addr(UNCOLORED_MARKER),
                                       is_fee_change=True)
        return composed_tx_spec

    def compose_tx_spec(self, op_tx_spec):
        targets_by_color = group_targets_by_color(op_tx_spec.get_targets(), self.__class__)
        uncolored_targets = targets_by_color.pop(UNCOLORED_MARKER.color_id, [])
        composed_tx_spec = op_tx_spec.make_composed_tx_spec()
        # get inputs for each color
        for color_id, targets in targets_by_color.items():
            color_def = targets[0].get_colordef()
            needed_sum = ColorTarget.sum(targets)
            inputs, total = op_tx_spec.select_coins(needed_sum)
            change = total - needed_sum
            if change > 0:
                targets.append(
                    ColorTarget(op_tx_spec.get_change_addr(color_def), change))
            composed_tx_spec.add_txins(inputs)
            for target in targets:
                composed_tx_spec.add_txout(value=target.get_value(),
                                           target=target)
        uncolored_needed = ColorTarget.sum(uncolored_targets)
        uncolored_inputs, uncolored_total = op_tx_spec.select_coins(uncolored_needed, composed_tx_spec)
        composed_tx_spec.add_txins(uncolored_inputs)
        fee = composed_tx_spec.estimate_required_fee()
        uncolored_change = uncolored_total - uncolored_needed - fee
        if uncolored_change > op_tx_spec.get_dust_threshold():
            composed_tx_spec.add_txout(value=uncolored_change,
                                       target_addr=op_tx_spec.get_change_addr(UNCOLORED_MARKER),
                                       is_fee_change=True)
        return composed_tx_spec class ColorDefinition(object):
    """ Represents a color definition scheme.
    This means how color exists and is transferred
    in the blockchain"""
    cd_classes = {}
    CLASS_CODE = None

    def __init__(self, color_id):
        self.color_id = color_id

    def get_color_id(self):
        return self.color_id

    def is_special_tx(self):
        raise Exception("Implement is_special_tx method")  # pragma: no cover

    def run_kernel(self, tx, in_colorvalues):
        raise Exception("Implement run_kernel method")  # pragma: no cover

    @classmethod
    def color_to_satoshi(cls, colorvalue):
        return colorvalue.get_value()

    @classmethod
    def get_class_code(cls):
        return cls.CLASS_CODE

    @staticmethod
    def register_color_def_class(cdclass):
        ColorDefinition.cd_classes[cdclass.get_class_code()] = cdclass

    @classmethod
    def from_color_desc(cls, color_id, color_desc):
        code = get_color_desc_code(color_desc)
        cdclass = cls.cd_classes[code]
        return cdclass.from_color_desc(color_id, color_desc)

    @classmethod
    def get_color_def_cls_for_code(cls, code):
        return cls.cd_classes.get(code, None)

    def __repr__(self):
        if self.color_id == 0:
            return ""
        elif self.color_id == -1:
            return "Genesis"
        return "Color Definition with %s" % self.color_id class GenesisColorDefinition(ColorDefinition):

    def __init__(self, color_id, genesis):
        super(GenesisColorDefinition, self).__init__(color_id)
        self.genesis = genesis

    def __repr__(self):
        return "%s:%s:%s:%s" % (
            self.CLASS_CODE, self.genesis['txhash'], self.genesis['outindex'],
            self.genesis['height'])

    def is_special_tx(self, tx):
        return tx.hash == self.genesis['txhash']

    @classmethod
    def from_color_desc(cls, color_id, color_desc):
        """ Create a color definition given a
        description string and the blockchain state"""
        code, txhash, outindex, height = color_desc.split(':')
        if (code != cls.CLASS_CODE):
            msg = 'Wrong color code "%s" in from_color_desc!'
            raise InvalidColorError(msg % code)
        genesis = {'txhash': txhash,
                   'height': int(height),
                   'outindex': int(outindex)}
        return cls(color_id, genesis) GENESIS_OUTPUT_MARKER = ColorDefinition(-1) UNCOLORED_MARKER = ColorDefinition(0) class InvalidColorError(Exception):
    pass class InvalidTargetError(Exception):
    pass class SimpleColorValue(AdditiveColorValue):
    def __init__(self, **kwargs):
        super(SimpleColorValue, self).__init__(**kwargs)
        if kwargs.get('label'):
            self.label = kwargs.pop('label')
        else:
            self.label = ''

    def get_kwargs(self):
        kwargs = super(SimpleColorValue, self).get_kwargs()
        kwargs['label'] = self.get_label()
        return kwargs

    def get_label(self):
        return self.label

    def __repr__(self):
        return "%s: %s" % (self.get_label() or self.get_colordef(), self.value) class ComposedTxSpec(object):
    """specification of a transaction which is already composed,
       but isn't signed yet"""

    class TxIn(CTxIn):
        pass

    class TxOut(object):
        __slots__ = ['value', 'target_addr']

        def __init__(self, value, target_addr):
            self.value = value
            self.target_addr = target_addr

    class FeeChangeTxOut(TxOut):
        pass

    def __init__(self, operational_tx_spec=None):
        self.txins = []
        self.txouts = []
        self.operational_tx_spec = operational_tx_spec

    def add_txin(self, txin):
        assert isinstance(txin, self.TxIn)
        self.txins.append(txin)

    def add_txout(self, txout=None, value=None, target_addr=None,
                  target=None, is_fee_change=False):
        if not txout:
            if not value:
                if target and target.is_uncolored():
                    value = target.get_value()
                else:
                    raise Exception("Error in ComposedTxSpec.add_txout: no\
value is provided and target is not uncolored!")
            if isinstance(value, ColorValue):
                if value.is_uncolored():
                    value = value.get_value()
                else:
                    raise Exception("Error in ComposedTxSpec.add_txout: no\
value isn't uncolored!")
            if not target_addr:
                target_addr = target.get_address()
            cls = self.FeeChangeTxOut if is_fee_change else self.TxOut
            txout = cls(value, target_addr)
        self.txouts.append(txout)

    def add_txouts(self, txouts):
        for txout in txouts:
            if isinstance(txout, ColorTarget):
                self.add_txout(target=txout)
            elif isinstance(txout, self.TxOut):
                self.add_txout(txout=txout)
            else:
                raise Exception('Wrong txout instance!')

    def add_txins(self, txins):
        for txin in txins:
            self.add_txin(txin)

    def get_txins(self):
        return self.txins

    def get_txouts(self):
        return self.txouts

    def estimate_size(self, extra_txins=0, extra_txouts=0, extra_bytes=0):
        return (181 * (len(self.txins) + extra_txins) +
                34 * (len(self.txouts) + extra_txouts) +
                10 + extra_bytes)

    def estimate_required_fee(self, extra_txins=0, extra_txouts=1, extra_bytes=0):
        return self.operational_tx_spec.get_required_fee(
            self.estimate_size(extra_txins=extra_txins,
                               extra_txouts=extra_txouts,
                               extra_bytes=extra_bytes))

    def get_fee(self):
        sum_txins = sum([inp.value for inp in self.txins])
        sum_txouts = sum([out.value for out in self.txouts])
        return sum_txins - sum_txouts class ColorTarget(object):
    def __init__(self, address, colorvalue):
        self.address = address
        self.colorvalue = colorvalue

    def get_colordef(self):
        return self.colorvalue.get_colordef()

    def get_color_id(self):
        return self.colorvalue.get_color_id()

    def is_uncolored(self):
        return self.colorvalue.is_uncolored()

    def get_address(self):
        return self.address

    def get_value(self):
        return self.colorvalue.get_value()

    def get_satoshi(self):
        return self.colorvalue.get_satoshi()

    def __repr__(self):
        return "%s: %s" % (self.get_address(), self.colorvalue)

    @classmethod
    def sum(cls, targets):
        if len(targets) == 0:
            from colordef import UNCOLORED_MARKER # circular import
            return SimpleColorValue(colordef=UNCOLORED_MARKER, value=0)
        c = targets[0].colorvalue.__class__
        return c.sum([t.colorvalue for t in targets])
import random
import unittest
from coloredcoinlib.colordef import (
    EPOBCColorDefinition, OBColorDefinition,
    ColorDefinition, GenesisColorDefinition, GENESIS_OUTPUT_MARKER,
    UNCOLORED_MARKER, InvalidColorError, InvalidTargetError)
from coloredcoinlib.colorvalue import SimpleColorValue
from coloredcoinlib.txspec import ComposedTxSpec, ColorTarget
from test_txspec import MockTX, MockOpTxSpec, i2seq
        self.epobc = EPOBCColorDefinition(1, {'txhash': 'genesis',
                                              'outindex': 0, 'height': 0})
        self.tester = ColorDefinitionTester(self.epobc)
        self.tag_class = EPOBCColorDefinition.Tag

    def test_tag_closest_padding_code(self):
        self.assertEqual(self.tag_class.closest_padding_code(0), 0)
        for i in range(15):
            if i > 0:
                self.assertEqual(self.tag_class.closest_padding_code(2**i), i)
            self.assertEqual(self.tag_class.closest_padding_code(2**i+1), i+1)
        self.assertRaises(Exception,
                          self.tag_class.closest_padding_code, 2**63+1)

    def test_tag_from_nsequence(self):
        self.assertEqual(self.tag_class.from_nSequence(1), None)
        # xfer is 110011 in binary = 51
        xfer_tag = self.tag_class.from_nSequence(512 + 51)
        self.assertEqual(xfer_tag.is_genesis, False)
        self.assertEqual(xfer_tag.padding_code, 8)
        # genesis is 100101 in binary = 37
        genesis_tag = self.tag_class.from_nSequence(2048 + 37)
        self.assertEqual(genesis_tag.is_genesis, True)
        self.assertEqual(genesis_tag.padding_code, 32)

    def test_tag_to_nsequence(self):
        n = random.randint(0,63) * 64 + 51
        xfer_tag = self.tag_class.from_nSequence(n)
        self.assertEqual(xfer_tag.to_nSequence(), n)

    def test_get_tag(self):
        tx = MockTX("random", [1,1,1], [2,1], [0,1,4,5,9])
        self.assertEqual(EPOBCColorDefinition.get_tag(tx).padding_code, 8)
        def tmp():
            return True
        tx.raw.vin[0].prevout.is_null = tmp
        self.assertEqual(EPOBCColorDefinition.get_tag(tx), None)

    def test_run_kernel(self):
        # test the EPOBC color kernel
        test = self.tester.test

        # genesis
        # pad 8, direct
        self.assertEqual(test([9], [9], [1], "genesis", [0,2,5,6,7]), [1])
        # pad 32, split
        self.assertEqual(test([59], [33, 26], [1], "genesis", [0,2,5,6,8]), [1, None])
        # pad 16, join
        self.assertEqual(test([12, 5], [17], [1], "genesis", [0,2,5,8]), [1])

        # different outindex
#        self.epobc.genesis['outindex'] = 1
#        self.assertEqual(test([30], [27, 3], [1], "genesis", [0,2,5,6]), [None, 1])
#        self.epobc.genesis['outindex'] = 0

        # transfer
        xfer_8 = [0, 1, 4, 5, 6, 7]
        # pad 8, direct
        self.assertEqual(test([9], [9], [1], "xfer", xfer_8), [1])
        # pad 8, join
        self.assertEqual(test([10, 11], [13], [2, 3], "xfer", xfer_8), [5])
        # pad 8, split
        self.assertEqual(test([13], [10, 11], [5], "xfer", xfer_8), [2, 3])

        # 0's all around
        self.assertEqual(test([8, 8, 8], [8, 8, 8], [None, None, None], "xfer", xfer_8), [None, None, None])

        # null values before and after
        self.assertEqual(test([9, 10, 11, 20], [9, 13, 20], [None, 2, 3, None], "xfer", xfer_8), [None, 5, None])

        # ignore below-padding values -- DOES NOT PASS
#        self.assertEqual(test([5, 10, 11, 5], [5, 13, 5], [2, 3, None], "xfer", xfer_8), [None, 5, None])
        # color values don't add up the same
        self.assertEqual(test([9, 10, 11], [13, 15, 0], [1, 2, 3], "xfer", xfer_8), [5, None, None])
        self.assertEqual(test([9, 10, 11], [9, 15, 0], [1, 2, 3], "xfer", xfer_8), [1, None, None])
        self.assertEqual(test([9, 10, 11], [19, 9, 0], [1, 2, 3], "xfer", xfer_8), [None, None, None])

        # sum before color values is not the same
        self.assertEqual(test([5, 10, 11], [6, 13], [None, 2, 3], "xfer", xfer_8), [None, None])
        # nonnull color values are not adjacent
        self.assertEqual(test([10, 10, 10, 12, 10], [10, 32, 10], [None, 2, None, 4, None], "xfer", xfer_8), [None, None, None])
        # sequence before don't add up the same
        self.assertEqual(test([10, 10, 10, 11, 10], [15, 13, 10], [None, None, 2, 3, None], "xfer", xfer_8), [None, None, None])
        # sequence before does add up the same
        self.assertEqual(test([10, 10, 10, 11, 10], [9, 11, 13, 10], [None, None, 2, 3, None], "xfer", xfer_8), [None, None, 5, None])
        # split to many
        self.assertEqual(test([10, 10, 13, 40], [10, 10, 9, 9, 9, 9, 9, 5], [None, None, 5, None], "xfer", xfer_8), [None, None, 1, 1, 1, 1, 1, None])
        # combine many
        self.assertEqual(test([10, 9, 9, 9, 9, 9, 10], [10, 13, 32], [None, 1, 1, 1, 1, 1, None], "xfer", xfer_8), [None, 5, None])
        # split and combine
        self.assertEqual(test([10, 10, 11, 12, 13, 14, 10], [10, 13, 17, 14, 30], [None, 2, 3, 4, 5, 6, None], "xfer", xfer_8), [None, 5, 9, 6, None])
        # combine and split
        self.assertEqual(test([13, 17, 14, 30], [10, 11, 12, 13, 14, 10], [5, 9, 6, None], "xfer", xfer_8), [2, 3, 4, 5, 6, None])

    def test_compose_genesis_tx_spec(self):
        cv = SimpleColorValue(colordef=self.epobc, value=5)
        txspec = MockOpTxSpec([])
        self.assertRaises(InvalidTargetError,
                          EPOBCColorDefinition.compose_genesis_tx_spec, txspec)
        target = ColorTarget('addr', cv)
        txspec = MockOpTxSpec([target])
        self.assertRaises(InvalidColorError,
                          EPOBCColorDefinition.compose_genesis_tx_spec, txspec)

        cv = SimpleColorValue(colordef=GENESIS_OUTPUT_MARKER, value=5)
        target = ColorTarget('addr', cv)
        txspec = MockOpTxSpec([target])
        self.assertTrue(isinstance(
                EPOBCColorDefinition.compose_genesis_tx_spec(txspec),
                ComposedTxSpec))

    def test_compose_tx_spec(self):
        cv1 = SimpleColorValue(colordef=UNCOLORED_MARKER, value=10000)
        cv2 = SimpleColorValue(colordef=self.epobc, value=10000)
        targets = [ColorTarget('addr1', cv1), ColorTarget('addr1', cv2)]
        txspec = MockOpTxSpec(targets)
        self.assertTrue(isinstance(self.epobc.compose_tx_spec(txspec),
                                   ComposedTxSpec))
        cv1 = SimpleColorValue(colordef=UNCOLORED_MARKER, value=20000)
        cv2 = SimpleColorValue(colordef=self.epobc, value=20000)

        targets = [ColorTarget('addr1', cv1), ColorTarget('addr1', cv2)]