def compose_tx_spec(self, op_tx_spec): # group targets by color targets_by_color = defaultdict(list) # group targets by color for target in op_tx_spec.get_targets(): color_def = target.get_colordef() if color_def == UNCOLORED_MARKER \ or isinstance(color_def, POBColorDefinition): targets_by_color[color_def.color_id].append(target) else: raise InvalidColorError('incompatible color definition') uncolored_targets = targets_by_color.pop(UNCOLORED_MARKER.color_id, []) # get inputs for each color colored_inputs = [] colored_targets = [] 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)) colored_inputs += inputs colored_targets += targets # we also need some amount of extra "uncolored" coins # for padding purposes, possibly padding_needed = (len(colored_targets) - len(colored_inputs)) \ * self.PADDING uncolored_needed = ColorTarget.sum(uncolored_targets) \ + SimpleColorValue(colordef=UNCOLORED_MARKER, value=padding_needed) fee = op_tx_spec.get_required_fee(250 * (len(colored_inputs) + 1)) amount_needed = uncolored_needed + fee zero = SimpleColorValue(colordef=UNCOLORED_MARKER, value=0) if amount_needed == zero: uncolored_change = zero uncolored_inputs = [] else: uncolored_inputs, uncolored_total = op_tx_spec.select_coins(amount_needed) uncolored_change = uncolored_total - amount_needed if uncolored_change > zero: uncolored_targets.append( ColorTarget(op_tx_spec.get_change_addr(UNCOLORED_MARKER), uncolored_change)) # compose the TxIn and TxOut elements txins = colored_inputs + uncolored_inputs txouts = [ txspec.ComposedTxSpec.TxOut(target.get_satoshi(), target.get_address()) for target in colored_targets] txouts += [txspec.ComposedTxSpec.TxOut(target.get_satoshi(), target.get_address()) for target in uncolored_targets] return txspec.ComposedTxSpec(txins, txouts)
def compose_genesis_tx_spec(cls, op_tx_spec): if len(op_tx_spec.get_targets()) != 1: raise InvalidTargetError( 'genesis transaction spec needs exactly one target') g_target = op_tx_spec.get_targets()[0] if g_target.get_colordef() != GENESIS_OUTPUT_MARKER: raise InvalidColorError( 'genesis transaction target should use -1 color_id') fee = op_tx_spec.get_required_fee(300) 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() uncolored_needed = SimpleColorValue(colordef=UNCOLORED_MARKER, value=padding + g_value) uncolored_inputs, uncolored_total = op_tx_spec.select_coins( uncolored_needed + fee) change = uncolored_total - uncolored_needed - fee txouts = [] txouts.append( txspec.ComposedTxSpec.TxOut(padding + g_value, g_target.get_address())) if change > 0: txouts.append( txspec.ComposedTxSpec.TxOut( change.get_value(), op_tx_spec.get_change_addr(UNCOLORED_MARKER))) uncolored_inputs[0].set_nSequence(tag.to_nSequence()) return txspec.ComposedTxSpec(uncolored_inputs, txouts)
def compose_genesis_tx_spec(self, op_tx_spec): targets = op_tx_spec.get_targets()[:] if len(targets) != 1: raise InvalidTargetError( 'genesis transaction spec needs exactly one target') target = targets[0] if target.get_colordef() != GENESIS_OUTPUT_MARKER: raise InvalidColorError( 'genesis transaction target should use -1 color_id') fee = op_tx_spec.get_required_fee(300) uncolored_value = SimpleColorValue(colordef=UNCOLORED_MARKER, value=target.get_value()) colorvalue = fee + uncolored_value inputs, total = op_tx_spec.select_coins(colorvalue) change = total - fee - uncolored_value if change > 0: targets.append( ColorTarget(op_tx_spec.get_change_addr(UNCOLORED_MARKER), change)) txouts = [ txspec.ComposedTxSpec.TxOut(target.get_satoshi(), target.get_address()) for target in targets ] return txspec.ComposedTxSpec(inputs, txouts)
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 compose_genesis_tx_spec(cls, op_tx_spec): if len(op_tx_spec.get_targets()) != 1: raise InvalidTargetError( 'genesis transaction spec needs exactly one target') g_target = op_tx_spec.get_targets()[0] if g_target.get_colordef() != GENESIS_OUTPUT_MARKER: raise InvalidColorError( 'genesis transaction target should use -1 color_id') 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
def compose_genesis_tx_spec(self, op_tx_spec): targets = op_tx_spec.get_targets() if len(targets) != 1: raise InvalidTargetError( 'genesis transaction spec needs exactly one target') target = targets[0] if target.get_colordef() != GENESIS_OUTPUT_MARKER: raise InvalidColorError( 'genesis transaction target should use -1 color_id') 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 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 """ log("in_colorvalues: %s", in_colorvalues) 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) log("affecting inputs: %s", affecting_inputs) 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] log("all colored: %s, colorvalue:%s", all_colored, ai_colorvalue.get_value()) 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)) log("out_colorvalues: %s", out_colorvalues) return out_colorvalues
def run_kernel(self, tx, in_colorvalues): 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 scan_tx(self, tx): """ Scan transaction to obtain color data for its outputs. """ in_colorvalues = [] empty = True for inp in tx.inputs: val = self.cdstore.get(self.color_id, inp.prevout.hash, inp.prevout.n) cv = None if val: empty = False cv = SimpleColorValue(colordef=self.colordef, value=val[0]) in_colorvalues.append(cv) if empty and not self.colordef.is_special_tx(tx): return out_colorvalues = self.colordef.run_kernel(tx, in_colorvalues) for o_index, val in enumerate(out_colorvalues): if val: self.cdstore.add(self.color_id, tx.hash, o_index, val.get_value(), val.get_label())
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, []) colored_txins = [] colored_txouts = [] 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]: colored_txins.append(inp) uncolored_needed -= SimpleColorValue(colordef=UNCOLORED_MARKER, value=inp.value) print uncolored_needed for target in targets: svalue = target.get_value() + padding colored_txouts.append( txspec.ComposedTxSpec.TxOut(svalue, target.get_address())) uncolored_needed += SimpleColorValue(colordef=UNCOLORED_MARKER, value=svalue) print uncolored_needed fee = op_tx_spec.get_required_fee(250 * (len(colored_txins) + 1)) uncolored_txouts = [] uncolored_inputs = [] uncolored_change = None if uncolored_needed + fee > 0: uncolored_inputs, uncolored_total = op_tx_spec.select_coins( uncolored_needed + fee) uncolored_txouts = [ txspec.ComposedTxSpec.TxOut(target.get_satoshi(), target.get_address()) for target in uncolored_targets ] uncolored_change = uncolored_total - uncolored_needed - fee else: uncolored_change = (-uncolored_needed) - fee if uncolored_change > 0: uncolored_txouts.append( txspec.ComposedTxSpec.TxOut( uncolored_change.get_value(), op_tx_spec.get_change_addr(UNCOLORED_MARKER))) all_inputs = colored_txins + uncolored_inputs all_inputs[0].set_nSequence(tag.to_nSequence()) return txspec.ComposedTxSpec(all_inputs, colored_txouts + uncolored_txouts)
def satoshi_to_color(self, satoshivalue): return SimpleColorValue(colordef=self, value=satoshivalue)
def run_kernel(self, tx, in_colorvalues): """Computes the output colorvalues""" # special case: genesis tx output tx.ensure_input_values() if tx.hash == self.genesis['txhash']: return [SimpleColorValue(colordef=self, value=out.value) if self.genesis['outindex'] == i else None \ for i, out in enumerate(tx.outputs)] # start with all outputs having null colorvalue nones = [None for _ in tx.outputs] out_colorvalues = [None for _ in tx.outputs] output_groups = {} # go through all inputs for inp_index in xrange(len(tx.inputs)): # if input has non-null colorvalue, check its nSequence color_value = in_colorvalues[inp_index] if color_value: nSequence = tx.raw.vin[inp_index].nSequence # nSequence is converted to a set of output indices output_group = list(ones(nSequence)) # exceptions; If exceptional situation is detected, we return a list of null colorvalues. # nSequence is 0 if nSequence == 0: return nones # nSequence has output indices exceeding number of outputs of this transactions for out_idx in output_group: if out_idx >= len(tx.inputs): return nones # there are intersecting 'output groups' (i.e output belongs to more than one group) if not nSequence in output_groups: for og in output_groups: if len(set(ones(og)).intersection(output_group)) != 0: return nones output_groups[nSequence] = SimpleColorValue(colordef=self, value=0) # add colorvalue of this input to colorvalue of output group output_groups[nSequence] += color_value # At this step we have total colorvalue for each output group. # For each output group: for nSequence in output_groups: output_group = list(ones(nSequence)) in_colorvalue = output_groups[nSequence] # sum satoshi-values of outputs in it (let's call it ssvalue) ssvalue = sum(tx.outputs[out_idx].value for out_idx in output_group) #find n such that 2^n*ssvalue = total colorvalue (loop over all |n|<32, positive and negative) for n in xrange(-31,32): if ssvalue*2**n == in_colorvalue.get_value(): # if n exists, each output of this group is assigned colorvalue svalue*2^n, where svalue is its satoshi-value for out_idx in output_group: svalue = tx.outputs[out_idx].value out_colorvalues[out_idx] = SimpleColorValue(colordef=self, value=svalue*2**n, label=in_colorvalue.get_label()) break else: # if n doesn't exist, we treat is as an exceptional sitation and return a list of None values. return nones # pragma: no cover return out_colorvalues
def run_kernel(self, tx, in_colorvalues): """Computes the output colorvalues""" is_genesis = (tx.hash == self.genesis['txhash']) # it turns out having a running sum in an array is easier # than constructing segments input_running_sums = [] ZERO_COLORVALUE = SimpleColorValue(colordef=self, value=0) running_sum = ZERO_COLORVALUE.clone() tx.ensure_input_values() for element in tx.inputs: colorvalue = self.satoshi_to_color(element.value) if colorvalue <= ZERO_COLORVALUE: break running_sum += colorvalue input_running_sums.append(running_sum.clone()) output_running_sums = [] running_sum = ZERO_COLORVALUE.clone() for element in tx.outputs: colorvalue = self.satoshi_to_color(element.value) if colorvalue <= ZERO_COLORVALUE: break running_sum += colorvalue output_running_sums.append(running_sum.clone()) # default is that every output has a null colorvalue out_colorvalues = [None for i in output_running_sums] # see if this is a genesis transaction if is_genesis: # adjust the single genesis index to have the right value # and return it i = self.genesis['outindex'] out_colorvalues[i] = self.satoshi_to_color(tx.outputs[i].value) return out_colorvalues # determine if the in_colorvalues are well-formed: # after that, there should be some number of non-null values # if another null is hit, there should be no more non-null values nonnull_sequences = 0 if in_colorvalues[0] is None: current_sequence_is_null = True input_color_start = None else: current_sequence_is_null = False input_color_start = 0 input_color_end = None for i, colorvalue in enumerate(in_colorvalues): if colorvalue is not None and current_sequence_is_null: current_sequence_is_null = False input_color_start = i elif colorvalue is None and not current_sequence_is_null: current_sequence_is_null = True input_color_end = i - 1 nonnull_sequences += 1 if not current_sequence_is_null: nonnull_sequences += 1 input_color_end = len(in_colorvalues) - 1 if nonnull_sequences > 1: return out_colorvalues # now figure out which segments correspond in the output if input_color_start == 0: sum_before_sequence = ZERO_COLORVALUE.clone() else: sum_before_sequence = input_running_sums[input_color_start - 1] sum_after_sequence = input_running_sums[input_color_end] # the sum of the segment before the sequence must be equal # as the sum of the sequence itself must also be equal if (sum_before_sequence != ZERO_COLORVALUE and sum_before_sequence not in output_running_sums) or \ sum_after_sequence not in output_running_sums: # this means we don't have matching places to start and/or end return out_colorvalues # now we know exactly where the output color sequence should start # and end if sum_before_sequence == ZERO_COLORVALUE: output_color_start = 0 else: output_color_start = output_running_sums.index( sum_before_sequence) + 1 output_color_end = output_running_sums.index(sum_after_sequence) # calculate what the color value at that point is for i in range(output_color_start, output_color_end + 1): previous_sum = ZERO_COLORVALUE if i == 0 \ else output_running_sums[i - 1] out_colorvalues[i] = output_running_sums[i] - previous_sum return out_colorvalues
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])
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) print uncolored_needed 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