def select_coins(self, colorvalue, use_fee_estimator=None): #FIXME if type(colorvalue) is int: colorvalue = SimpleColorValue( colordef=UNCOLORED_MARKER, value=colorvalue ) self._validate_select_coins_parameters(colorvalue, use_fee_estimator) colordef = colorvalue.get_colordef() if colordef == UNCOLORED_MARKER: return self.select_uncolored_coins(colorvalue, use_fee_estimator) color_id = colordef.get_color_id() if color_id in self.inputs: # use inputs provided in proposal total = SimpleColorValue.sum([cv_u[0] for cv_u in self.inputs[color_id]]) if total < colorvalue: raise InsufficientFundsError('not enough coins: %s requested, %s found' % (colorvalue, total)) return [cv_u[1] for cv_u in self.inputs[color_id]], total if colorvalue != self.our_value_limit: raise InsufficientFundsError("%s requested, %s found" % (colorvalue, our_value_limit)) return super(OperationalETxSpec, self).select_coins(colorvalue)
def setUp(self): self.colordef0 = POBColorDefinition(1, { 'txhash': 'genesis', 'outindex': 0 }) self.colordef1 = OBColorDefinition(2, { 'txhash': 'genesis', 'outindex': 0 }) self.colorvalue0 = SimpleColorValue(colordef=self.colordef0, value=1, label='test') self.colorvalue1 = SimpleColorValue(colordef=self.colordef0, value=2, label='test2') self.colorvalue2 = SimpleColorValue(colordef=self.colordef1, value=1) self.e0 = MyEOffer.from_data({ 'oid': 1, 'A': self.colorvalue0, 'B': self.colorvalue1 }) self.e1 = MyEOffer.from_data({ 'oid': 2, 'A': self.colorvalue0, 'B': self.colorvalue1 })
def select_uncolored_coins(self, colorvalue, use_fee_estimator): selected_inputs = [] selected_value = SimpleColorValue(colordef=UNCOLORED_MARKER, value=0) needed = colorvalue + use_fee_estimator.estimate_required_fee() color_id = 0 if color_id in self.inputs: total = SimpleColorValue.sum([cv_u[0] for cv_u in self.inputs[color_id]]) needed -= total selected_inputs += [cv_u[1] for cv_u in self.inputs[color_id]] selected_value += total if needed > 0: value_limit = SimpleColorValue(colordef=UNCOLORED_MARKER, value=10000+8192) if self.our_value_limit.is_uncolored(): value_limit += self.our_value_limit if needed > value_limit: raise InsufficientFundsError("exceeded limits: %s requested, %s found" % (needed, value_limit)) our_inputs, our_value = super(OperationalETxSpec, self).\ select_coins(colorvalue - selected_value, use_fee_estimator) selected_inputs += our_inputs selected_value += our_value return selected_inputs, selected_value
def test_tx_spec(self): self.add_colors() our = SimpleColorValue(colordef=UNCOLORED_MARKER, value=500) colormap = self.model.get_color_map() colordef = colormap.get_color_def(self.cspec) their = SimpleColorValue(colordef=colordef, value=10) etx = self.ewc.make_etx_spec(our, their) self.assertTrue(isinstance(etx, ETxSpec)) for target in etx.targets: self.assertTrue(isinstance(target, ColorTarget)) signed = self.ewc.make_reply_tx(etx, our, their) self.assertTrue(isinstance(signed, RawTxSpec)) self.ewc.publish_tx(signed) etx = self.ewc.make_etx_spec(their, our) self.assertTrue(isinstance(etx, ETxSpec)) for target in etx.targets: self.assertTrue(isinstance(target, ColorTarget)) signed = self.ewc.make_reply_tx(etx, their, our) self.assertTrue(isinstance(signed, RawTxSpec)) oets = OperationalETxSpec(self.model, self.ewc) zero = SimpleColorValue(colordef=UNCOLORED_MARKER, value=0) self.assertRaises(ZeroSelectError, oets.select_coins, zero) toomuch = SimpleColorValue(colordef=UNCOLORED_MARKER, value=10000000000000) self.assertRaises(InsufficientFundsError, oets.select_coins, toomuch)
def test_tx_spec(self): alice_cv = {'color_spec': self.color_spec, 'value': 10} bob_cv = {'color_spec': "", 'value': 500} alice_offer = MyEOffer(None, alice_cv, bob_cv) bob_offer = MyEOffer(None, bob_cv, alice_cv) bob_etx = self.ewc.make_etx_spec(bob_cv, alice_cv) self.assertTrue(isinstance(bob_etx, ETxSpec)) for target in bob_etx.targets: # check address address = target[0] self.assertTrue(isinstance(address, type(u"unicode"))) # TODO check address is correct format # check color_spec color_spec = target[1] self.assertTrue(isinstance(color_spec, type("str"))) color_spec_parts = len(color_spec.split(":")) self.assertTrue(color_spec_parts == 4 or color_spec_parts == 1) # check value value = target[2] self.assertTrue(isinstance(value, type(10))) signed = self.ewc.make_reply_tx(bob_etx, alice_cv, bob_cv) self.assertTrue(isinstance(signed, RawTxSpec)) self.ewc.publish_tx(signed, alice_offer) alice_etx = self.ewc.make_etx_spec(alice_cv, bob_cv) self.assertTrue(isinstance(alice_etx, ETxSpec)) for target in alice_etx.targets: # check address address = target[0] self.assertTrue(isinstance(address, type(u"unicode"))) # TODO check address is correct format # check color_spec color_spec = target[1] self.assertTrue(isinstance(color_spec, type("str"))) color_spec_parts = len(color_spec.split(":")) self.assertTrue(color_spec_parts == 4 or color_spec_parts == 1) # check value value = target[2] self.assertTrue(isinstance(value, type(10))) signed = self.ewc.make_reply_tx(alice_etx, bob_cv, alice_cv) self.assertTrue(isinstance(signed, RawTxSpec)) oets = OperationalETxSpec(self.model, self.ewc) oets.set_our_value_limit(bob_cv) oets.prepare_inputs(alice_etx) zero = SimpleColorValue(colordef=UNCOLORED_MARKER, value=0) self.assertRaises(ZeroSelectError, oets.select_coins, zero) toomuch = SimpleColorValue(colordef=UNCOLORED_MARKER, value=10000000000000) self.assertRaises(InsufficientFundsError, oets.select_coins, toomuch)
def _select_enough_coins(self, colordef, utxo_list, required_sum_fn): ssum = SimpleColorValue(colordef=colordef, value=0) selection = [] required_sum = None for utxo in utxo_list: ssum += SimpleColorValue.sum(utxo.colorvalues) selection.append(utxo) required_sum = required_sum_fn(utxo_list) if ssum >= required_sum: return selection, ssum raise InsufficientFundsError( 'Not enough coins: %s requested, %s found!' % (required_sum, ssum))
def select_coins(self, colorvalue): colordef = colorvalue.get_colordef() color_id = colordef.get_color_id() if color_id in self.inputs: total = SimpleColorValue.sum([cv_u[0] for cv_u in self.inputs[color_id]]) if total < colorvalue: raise InsufficientFundsError('not enough coins: %s requested, %s found' % (colorvalue, total)) return [cv_u[1] for cv_u in self.inputs[color_id]], total cq = self.model.make_coin_query({"color_id_set": set([color_id])}) utxo_list = cq.get_result() zero = ssum = SimpleColorValue(colordef=colordef, value=0) selection = [] if colorvalue == zero: raise ZeroSelectError('cannot select 0 coins') for utxo in utxo_list: if utxo.colorvalues: ssum += utxo.colorvalues[0] selection.append(utxo) if ssum >= colorvalue: return selection, ssum raise InsufficientFundsError('not enough coins: %s requested, %s found' % (colorvalue, ssum))
def prepare_inputs(self, etx_spec): self.inputs = defaultdict(list) colordata = self.model.ccc.colordata for color_spec, inps in etx_spec.inputs.items(): colordef = self.ewctrl.resolve_color_spec(color_spec) color_id_set = set([colordef.get_color_id()]) for inp in inps: txhash, outindex = inp tx = self.model.ccc.blockchain_state.get_tx(txhash) prevout = tx.outputs[outindex] utxo = UTXO({"txhash": txhash, "outindex": outindex, "value": prevout.value, "script": prevout.script}) colorvalue = None if colordef == UNCOLORED_MARKER: colorvalue = SimpleColorValue(colordef=UNCOLORED_MARKER, value=prevout.value) else: css = colordata.get_colorvalues(color_id_set, txhash, outindex) if (css and len(css) == 1): colorvalue = css[0] if colorvalue: self.inputs[colordef.get_color_id()].append( (colorvalue, utxo))
def prepare_targets(self, etx_spec, their): self.targets = [] for address, color_spec, value in etx_spec.targets: colordef = self.ewctrl.resolve_color_spec(color_spec) self.targets.append(ColorTarget(address, SimpleColorValue(colordef=colordef, value=value))) wam = self.model.get_address_manager() colormap = self.model.get_color_map() their_colordef = self.ewctrl.resolve_color_spec(their['color_spec']) their_color_set = ColorSet.from_color_ids(self.model.get_color_map(), [their_colordef.get_color_id()]) ct = ColorTarget(wam.get_change_address(their_color_set).get_address(), SimpleColorValue(colordef=their_colordef, value=their['value'])) self.targets.append(ct)
def make_etx_spec(self, our, their): our_color_def = self.resolve_color_spec(our['color_spec']) our_color_set = ColorSet.from_color_ids(self.model.get_color_map(), [our_color_def.get_color_id()]) their_color_def = self.resolve_color_spec(their['color_spec']) their_color_set = ColorSet.from_color_ids(self.model.get_color_map(), [their_color_def.get_color_id()]) extra_value = 0 if our_color_def == UNCOLORED_MARKER: # pay fee + padding for one colored outputs extra_value = 10000 + 8192 * 1 c_utxos, c_change = self.select_inputs( SimpleColorValue(colordef=our_color_def, value=our['value'] + extra_value)) inputs = {our['color_spec']: [utxo.get_outpoint() for utxo in c_utxos]} wam = self.model.get_address_manager() our_address = wam.get_change_address(their_color_set) targets = [(our_address.get_address(), their['color_spec'], their['value'])] if c_change > 0: our_change_address = wam.get_change_address(our_color_set) targets.append((our_change_address.get_address(), our['color_spec'], c_change.get_value())) return ETxSpec(inputs, targets, c_utxos)
def get_required_fee(self, tx_size): """Given a transaction that is of size <tx_size>, return the transaction fee in Satoshi that needs to be paid out to miners. """ # TODO: this should change to something dependent on tx_size return SimpleColorValue(colordef=UNCOLORED_MARKER, value=10000)
def issue_coins(self, moniker, pck, units, atoms_in_unit): """Issues a new color of name <moniker> using coloring scheme <pck> with <units> per share and <atoms_in_unit> total. """ color_definition_cls = ColorDefinition.get_color_def_cls_for_code(pck) if not color_definition_cls: raise InvalidColorDefinitionError( 'color scheme %s not recognized' % pck) total = units * atoms_in_unit op_tx_spec = SimpleOperationalTxSpec(self.model, None) wam = self.model.get_address_manager() address = wam.get_new_genesis_address() colorvalue = SimpleColorValue(colordef=GENESIS_OUTPUT_MARKER, value=total) color_target = ColorTarget(address.get_address(), colorvalue) op_tx_spec.add_target(color_target) genesis_ctxs = color_definition_cls.compose_genesis_tx_spec(op_tx_spec) genesis_tx = self.model.transform_tx_spec(genesis_ctxs, 'signed') height = self.model.ccc.blockchain_state.bitcoind.getblockcount() \ - 1 genesis_tx_hash = self.publish_tx(genesis_tx) color_desc = ':'.join([pck, genesis_tx_hash, '0', str(height)]) adm = self.model.get_asset_definition_manager() asset = adm.add_asset_definition({ "monikers": [moniker], "color_set": [color_desc], "unit": atoms_in_unit }) wam.update_genesis_address(address, asset.get_color_set()) # scan the tx so that the rest of the system knows self.model.ccc.colordata.cdbuilder_manager.scan_txhash( asset.color_set.color_id_set, genesis_tx_hash)
def select_inputs(self, colorvalue): cs = ColorSet.from_color_ids(self.model.get_color_map(), [colorvalue.get_color_id()]) cq = self.model.make_coin_query({"color_set": cs}) utxo_list = cq.get_result() selection = [] csum = SimpleColorValue(colordef=colorvalue.get_colordef(), value=0) for utxo in utxo_list: csum += SimpleColorValue.sum(utxo.colorvalues) selection.append(utxo) if csum >= colorvalue: break if csum < colorvalue: raise InsufficientFundsError('not enough money') return selection, (csum - colorvalue)
def select_coins(self, colorvalue): colordef = colorvalue.get_colordef() color_id = colordef.get_color_id() cq = self.model.make_coin_query({"color_id_set": set([color_id])}) utxo_list = cq.get_result() zero = ssum = SimpleColorValue(colordef=colordef, value=0) selection = [] if colorvalue == zero: raise ZeroSelectError('cannot select 0 coins') for utxo in utxo_list: ssum += SimpleColorValue.sum(utxo.colorvalues) selection.append(utxo) if ssum >= colorvalue: return selection, ssum raise InsufficientFundsError( 'not enough coins: %s requested, %s found' % (colorvalue, ssum))
def get_required_fee(self, tx_size): """Given a transaction that is of size <tx_size>, return the transaction fee in Satoshi that needs to be paid out to miners. """ base_fee = 11000.0 fee_value = math.ceil((tx_size * base_fee) / 1000) return SimpleColorValue(colordef=UNCOLORED_MARKER, value=fee_value)
def get_balance(self, asset): """Returns an integer value corresponding to the total number of Satoshis owned of asset/color <asset>. """ cq = self.model.make_coin_query({"asset": asset}) utxo_list = cq.get_result() value_list = [asset.get_colorvalue(utxo) for utxo in utxo_list] if len(value_list) == 0: return 0 else: return SimpleColorValue.sum(value_list).get_value()
def test_get_colorvalue(self): g = {'txhash': 'blah', 'height': 1, 'outindex': 0} cid0 = list(self.colorset0.color_id_set)[0] cdef0 = OBColorDefinition(cid0, g) cid1 = list(self.colorset1.color_id_set)[0] cdef1 = OBColorDefinition(cid1, g) cid2 = list(self.colorset2.color_id_set)[0] cdef2 = OBColorDefinition(cid2, g) cv0 = SimpleColorValue(colordef=cdef0, value=1) cv1 = SimpleColorValue(colordef=cdef1, value=2) cv2 = SimpleColorValue(colordef=cdef2, value=3) utxo = MockUTXO([cv0, cv1, cv2]) self.assertEquals(self.asset0.get_colorvalue(utxo), cv0) self.assertEquals(self.asset1.get_colorvalue(utxo), cv1) self.assertEquals(self.asset2.get_colorvalue(utxo), cv2) utxo = MockUTXO([cv0, cv2]) self.assertRaises(Exception, self.asset1.get_colorvalue, utxo)
def test_operational(self): self.basic.add_target(self.assettarget0) self.basic.add_target(self.assettarget1) self.basic.add_target(self.assettarget2) op = self.transformer.transform_basic(self.basic, 'operational') self.assertTrue(self.transformer.classify_tx_spec(op), 'operational') self.assertRaises(InvalidTargetError, op.add_target, 1) self.assertEqual(ColorTarget.sum(op.get_targets()), ColorTarget.sum(self.targets)) self.assertEqual(op.get_change_addr(self.colordef0), self.addr0) self.assertEqual(op.get_change_addr(UNCOLORED_MARKER), self.baddr) self.assertEqual(op.get_required_fee(1).get_value(), 10000) self.assertRaises(InvalidColorIdError, op.get_change_addr, self.colordef1) cv = SimpleColorValue(colordef=self.colordef0, value=0) self.assertRaises(ZeroSelectError, op.select_coins, cv) cv = SimpleColorValue(colordef=self.colordef0, value=5) self.assertRaises(InsufficientFundsError, op.select_coins, cv) self.add_coins() self.assertEqual(op.select_coins(cv)[1].get_value(), 100)
def select_coins(self, colorvalue): """Return a list of utxos and sum that corresponds to the colored coins identified by <color_def> of amount <colorvalue> that we'll be spending from our wallet. """ colordef = colorvalue.get_colordef() color_id = colordef.get_color_id() cq = self.model.make_coin_query({"color_id_set": set([color_id])}) utxo_list = cq.get_result() zero = ssum = SimpleColorValue(colordef=colordef, value=0) selection = [] if colorvalue == zero: raise ZeroSelectError('cannot select 0 coins') for utxo in utxo_list: ssum += SimpleColorValue.sum(utxo.colorvalues) selection.append(utxo) if ssum >= colorvalue: return selection, ssum raise InsufficientFundsError( 'not enough coins: %s requested, %s found' % (colorvalue, ssum))
def _select_enough_coins(self, colordef, utxo_list, required_sum_fn): ssum = SimpleColorValue(colordef=colordef, value=0) selection = [] required_sum = None for utxo in utxo_list: ssum += SimpleColorValue.sum(utxo.colorvalues) selection.append(utxo) required_sum = required_sum_fn(utxo_list) if ssum >= required_sum: return selection, ssum raise InsufficientFundsError('Not enough coins: %s requested, %s found!' % (required_sum, ssum))
def sendmany_coins(self, entries): """Sendmany coins given in entries [(asset, address, value), ...] """ self.validate_sendmany_entries(entries) tx_spec = SimpleOperationalTxSpec(self.model, None) for asset, address, value in entries: color_id = asset.get_color_id() colordef = self.model.get_color_def(color_id) colorvalue = SimpleColorValue(colordef=colordef, value=value) tx_spec.add_target(ColorTarget(address, colorvalue)) signed_tx_spec = self.model.transform_tx_spec(tx_spec, 'signed') txhash = self.publish_tx(signed_tx_spec) # TODO add to history return txhash
def test_basic(self): model = MockModel() ewctrl = EWalletController(model, None) config = {"offer_expiry_interval": 30, "ep_expiry_interval": 30} comm = MockComm() agent = EAgent(ewctrl, config, comm) # At this point the agent should not have an active proposal self.assertFalse(agent.has_active_ep()) # no messages should have been sent to the network self.assertEqual(len(comm.get_messages()), 0) self.cd = OBColorDefinition(1, { 'txhash': 'xxx', 'outindex': 0, 'height': 0 }) cv0 = SimpleColorValue(colordef=UNCOLORED_MARKER, value=100) cv1 = SimpleColorValue(colordef=self.cd, value=200) my_offer = MyEOffer(None, cv0, cv1) their_offer = EOffer('abcdef', cv1, cv0) agent.register_my_offer(my_offer) agent.register_their_offer(their_offer) agent.update() # Agent should have an active exchange proposal self.assertTrue(agent.has_active_ep()) # Exchange proposal should have been sent over comm # it should be the only message, as we should not resend our offer # if their is an active proposal to match it self.assertTrue(len(comm.get_messages()), 1) [proposal] = comm.get_messages() # The offer data should be in the proposal their_offer_data = their_offer.get_data() self.assertEquals(their_offer_data, proposal["offer"])
def compute_colorvalues(self, coin): wam = self.model.get_address_manager() address_rec = wam.find_address_record(coin.address) if not address_rec: raise Exception('address record not found') color_set = address_rec.get_color_set() if color_set.uncolored_only(): return [ SimpleColorValue(colordef=UNCOLORED_MARKER, value=coin.value) ] else: cdata = self.model.ccc.colordata return cdata.get_colorvalues(color_set.color_id_set, coin.txhash, coin.outindex)
def select_inputs(self, colorvalue): op_tx_spec = SimpleOperationalTxSpec(self.model, None) if colorvalue.is_uncolored(): composed_tx_spec = op_tx_spec.make_composed_tx_spec() selection, total = op_tx_spec.select_coins(colorvalue, composed_tx_spec) change = total - colorvalue - \ composed_tx_spec.estimate_required_fee(extra_txins=len(selection)) if change < op_tx_spec.get_dust_threshold(): change = SimpleColorValue(colordef=UNCOLORED_MARKER, value=0) return selection, change else: selection, total = op_tx_spec.select_coins(colorvalue) change = total - colorvalue return selection, change
def make_operational_tx_spec(self, asset): """Given a <tx_spec> of type BasicTxSpec, return a SimpleOperationalTxSpec. """ if not self.is_monocolor(): raise InvalidTransformationError('tx spec type not supported') op_tx_spec = SimpleOperationalTxSpec(self.model, asset) color_id = list(asset.get_color_set().color_id_set)[0] color_def = self.model.get_color_def(color_id) for target in self.targets: colorvalue = SimpleColorValue(colordef=color_def, value=target.get_value()) colortarget = ColorTarget(target.get_address(), colorvalue) op_tx_spec.add_target(colortarget) return op_tx_spec
def get_coins_for_address(self, address_rec): """Given an address <address_rec>, return the list of coin's straight from the DB. Note this specifically does NOT return COIN objects. """ color_set = self.color_set addr_color_set = address_rec.get_color_set() all_coins = filter( self.coin_matches_filter, self.coin_manager.get_coins_for_address(address_rec.get_address())) cdata = self.model.ccc.colordata address_is_uncolored = addr_color_set.color_id_set == set([0]) if address_is_uncolored: for coin in all_coins: coin.address_rec = address_rec coin.colorvalues = [ SimpleColorValue(colordef=UNCOLORED_MARKER, value=coin.value) ] return all_coins for coin in all_coins: coin.address_rec = address_rec coin.colorvalues = None try: coin.colorvalues = cdata.get_colorvalues( addr_color_set.color_id_set, coin.txhash, coin.outindex) except Exception as e: print e raise def relevant(coin): cvl = coin.colorvalues if coin.colorvalues is None: return False # None indicates failure if cvl == []: return color_set.has_color_id(0) for cv in cvl: if color_set.has_color_id(cv.get_color_id()): return True return False return filter(relevant, all_coins)
def select_coins(self, colorvalue, use_fee_estimator=None): self._validate_select_coins_parameters(colorvalue, use_fee_estimator) colordef = colorvalue.get_colordef() if colordef == UNCOLORED_MARKER: return self.select_uncolored_coins(colorvalue, use_fee_estimator) color_id = colordef.get_color_id() if color_id in self.inputs: # use inputs provided in proposal total = SimpleColorValue.sum([cv_u[0] for cv_u in self.inputs[color_id]]) if total < colorvalue: raise InsufficientFundsError('not enough coins: %s requested, %s found' % (colorvalue, total)) return [cv_u[1] for cv_u in self.inputs[color_id]], total if colorvalue != self.our_value_limit: raise InsufficientFundsError("%s requested, %s found" % (colorvalue, our_value_limit)) return super(OperationalETxSpec, self).select_coins(colorvalue)
def select_coins(self, colorvalue): """Return a list of utxos and sum that corresponds to the colored coins identified by <color_def> of amount <colorvalue> that we'll be spending from our wallet. """ colordef = colorvalue.get_colordef() color_id = colordef.get_color_id() cq = self.model.make_coin_query({"color_id_set": set([color_id])}) utxo_list = cq.get_result() zero = ssum = SimpleColorValue(colordef=colordef, value=0) selection = [] if colorvalue == zero: raise ZeroSelectError('cannot select 0 coins') for utxo in utxo_list: ssum += SimpleColorValue.sum(utxo.colorvalues) selection.append(utxo) if ssum >= colorvalue: return selection, ssum raise InsufficientFundsError('not enough coins: %s requested, %s found' % (colorvalue, ssum))
def get_utxos_for_address(self, address_rec): """Given an address <address_rec>, return the list of utxo's straight from the DB. Note this specifically does NOT return UTXO objects. """ color_set = self.color_set addr_color_set = address_rec.get_color_set() all_utxos = self.utxo_manager.get_utxos_for_address( address_rec.get_address()) cdata = self.model.ccc.colordata address_is_uncolored = addr_color_set.color_id_set == set([0]) if address_is_uncolored: for utxo in all_utxos: utxo.address_rec = address_rec utxo.colorvalues = [ SimpleColorValue(colordef=UNCOLORED_MARKER, value=utxo.value) ] return all_utxos for utxo in all_utxos: utxo.address_rec = address_rec utxo.colorvalues = None try: utxo.colorvalues = cdata.get_colorvalues( addr_color_set.color_id_set, utxo.txhash, utxo.outindex) except Exception as e: print e def relevant(utxo): cvl = utxo.colorvalues if utxo.colorvalues is None: return False # None indicates failure if cvl == []: return color_set.has_color_id(0) for cv in cvl: if color_set.has_color_id(cv.get_color_id()): return True return False return filter(relevant, all_utxos)
def set_our_value_limit(self, our): our_colordef = self.ewctrl.resolve_color_spec(our['color_spec']) self.our_value_limit = SimpleColorValue(colordef=our_colordef, value=our['value'])
def offer_side_to_colorvalue(self, side): colordef = self.resolve_color_spec(side['color_spec']) return SimpleColorValue(colordef=colordef, value=side['value'])
class OperationalETxSpec(SimpleOperationalTxSpec): def __init__(self, model, ewctrl): self.model = model self.ewctrl = ewctrl self.our_value_limit = None def get_targets(self): return self.targets def get_change_addr(self, color_def): color_id = color_def.color_id cs = ColorSet.from_color_ids(self.model.get_color_map(), [color_id]) wam = self.model.get_address_manager() return wam.get_change_address(cs).get_address() def set_our_value_limit(self, our): our_colordef = self.ewctrl.resolve_color_spec(our['color_spec']) self.our_value_limit = SimpleColorValue(colordef=our_colordef, value=our['value']) def prepare_inputs(self, etx_spec): self.inputs = defaultdict(list) colordata = self.model.ccc.colordata for color_spec, inps in etx_spec.inputs.items(): colordef = self.ewctrl.resolve_color_spec(color_spec) color_id_set = set([colordef.get_color_id()]) for inp in inps: txhash, outindex = inp tx = self.model.ccc.blockchain_state.get_tx(txhash) prevout = tx.outputs[outindex] utxo = UTXO({"txhash": txhash, "outindex": outindex, "value": prevout.value, "script": prevout.script}) colorvalue = None if colordef == UNCOLORED_MARKER: colorvalue = SimpleColorValue(colordef=UNCOLORED_MARKER, value=prevout.value) else: css = colordata.get_colorvalues(color_id_set, txhash, outindex) if (css and len(css) == 1): colorvalue = css[0] if colorvalue: self.inputs[colordef.get_color_id()].append( (colorvalue, utxo)) def prepare_targets(self, etx_spec, their): self.targets = [] for address, color_spec, value in etx_spec.targets: colordef = self.ewctrl.resolve_color_spec(color_spec) self.targets.append(ColorTarget(address, SimpleColorValue(colordef=colordef, value=value))) wam = self.model.get_address_manager() colormap = self.model.get_color_map() their_colordef = self.ewctrl.resolve_color_spec(their['color_spec']) their_color_set = ColorSet.from_color_ids(self.model.get_color_map(), [their_colordef.get_color_id()]) ct = ColorTarget(wam.get_change_address(their_color_set).get_address(), SimpleColorValue(colordef=their_colordef, value=their['value'])) self.targets.append(ct) def select_uncolored_coins(self, colorvalue, use_fee_estimator): selected_inputs = [] selected_value = SimpleColorValue(colordef=UNCOLORED_MARKER, value=0) needed = colorvalue + use_fee_estimator.estimate_required_fee() color_id = 0 if color_id in self.inputs: total = SimpleColorValue.sum([cv_u[0] for cv_u in self.inputs[color_id]]) needed -= total selected_inputs += [cv_u[1] for cv_u in self.inputs[color_id]] selected_value += total if needed > 0: value_limit = SimpleColorValue(colordef=UNCOLORED_MARKER, value=10000+8192) if self.our_value_limit.is_uncolored(): value_limit += self.our_value_limit if needed > value_limit: raise InsufficientFundsError("exceeded limits: %s requested, %s found" % (needed, value_limit)) our_inputs, our_value = super(OperationalETxSpec, self).\ select_coins(colorvalue - selected_value, use_fee_estimator) selected_inputs += our_inputs selected_value += our_value return selected_inputs, selected_value def select_coins(self, colorvalue, use_fee_estimator=None): self._validate_select_coins_parameters(colorvalue, use_fee_estimator) colordef = colorvalue.get_colordef() if colordef == UNCOLORED_MARKER: return self.select_uncolored_coins(colorvalue, use_fee_estimator) color_id = colordef.get_color_id() if color_id in self.inputs: # use inputs provided in proposal total = SimpleColorValue.sum([cv_u[0] for cv_u in self.inputs[color_id]]) if total < colorvalue: raise InsufficientFundsError('not enough coins: %s requested, %s found' % (colorvalue, total)) return [cv_u[1] for cv_u in self.inputs[color_id]], total if colorvalue != self.our_value_limit: raise InsufficientFundsError("%s requested, %s found" % (colorvalue, our_value_limit)) return super(OperationalETxSpec, self).select_coins(colorvalue)
def setUp(self): self.path = ":memory:" self.pwallet = PersistentWallet(self.path) self.config = { 'dw_master_key': 'test', 'testnet': True, 'ccc': { 'colordb_path': self.path }, 'bip0032': False } self.pwallet.wallet_config = self.config self.pwallet.init_model() self.model = self.pwallet.get_model() self.colormap = self.model.get_color_map() self.colordesc0 = "obc:color0:0:0" self.colordesc1 = "obc:color1:0:0" self.colordesc2 = "obc:color2:0:0" # add some colordescs self.colorid0 = self.colormap.resolve_color_desc(self.colordesc0) self.colorid1 = self.colormap.resolve_color_desc(self.colordesc1) self.colorid2 = self.colormap.resolve_color_desc(self.colordesc2) self.colordef0 = OBColorDefinition(self.colorid0, { 'txhash': 'color0', 'outindex': 0 }) self.colordef1 = OBColorDefinition(self.colorid1, { 'txhash': 'color1', 'outindex': 0 }) self.colordef2 = OBColorDefinition(self.colorid2, { 'txhash': 'color2', 'outindex': 0 }) self.asset_config = { 'monikers': ['blue'], 'color_set': [self.colordesc0], } self.basset_config = { 'monikers': ['bitcoin'], 'color_set': [''], } self.asset = AssetDefinition(self.colormap, self.asset_config) self.basset = AssetDefinition(self.colormap, self.basset_config) self.basic = BasicTxSpec(self.model) self.bbasic = BasicTxSpec(self.model) wam = self.model.get_address_manager() self.address0 = wam.get_new_address(self.asset.get_color_set()) self.addr0 = self.address0.get_address() self.bcolorset = ColorSet(self.colormap, ['']) self.baddress = wam.get_new_address(self.bcolorset) self.baddr = self.baddress.get_address() self.assetvalue0 = AdditiveAssetValue(asset=self.asset, value=5) self.assetvalue1 = AdditiveAssetValue(asset=self.asset, value=6) self.assetvalue2 = AdditiveAssetValue(asset=self.asset, value=7) self.bassetvalue = AdditiveAssetValue(asset=self.basset, value=8) self.assettarget0 = AssetTarget(self.addr0, self.assetvalue0) self.assettarget1 = AssetTarget(self.addr0, self.assetvalue1) self.assettarget2 = AssetTarget(self.addr0, self.assetvalue2) self.bassettarget = AssetTarget(self.baddr, self.bassetvalue) self.atargets = [ self.assettarget0, self.assettarget1, self.assettarget2 ] # add some targets self.colorvalue0 = SimpleColorValue(colordef=self.colordef0, value=5) self.colortarget0 = ColorTarget(self.addr0, self.colorvalue0) self.colorvalue1 = SimpleColorValue(colordef=self.colordef0, value=6) self.colortarget1 = ColorTarget(self.addr0, self.colorvalue1) self.colorvalue2 = SimpleColorValue(colordef=self.colordef0, value=7) self.colortarget2 = ColorTarget(self.addr0, self.colorvalue2) self.bcolorvalue = SimpleColorValue(colordef=UNCOLORED_MARKER, value=8) self.bcolortarget = ColorTarget(self.baddr, self.bcolorvalue) self.targets = [ self.colortarget0, self.colortarget1, self.colortarget2 ] self.transformer = TransactionSpecTransformer(self.model, self.config) self.blockhash = '00000000c927c5d0ee1ca362f912f83c462f644e695337ce3731b9f7c5d1ca8c' self.txhash = '4fe45a5ba31bab1e244114c4555d9070044c73c98636231c77657022d76b87f7'
def get_dust_threshold(self): return SimpleColorValue(colordef=UNCOLORED_MARKER, value=10000)