def tx_sources(self, amount, community): """ Get inputs to generate a transaction with a given amount of money :param int amount: The amount target value :param community: The community target of the transaction :return: The list of inputs to use in the transaction document """ amount, amount_base = reduce_base(amount, 0) cache = self.caches[community.currency] current_base = amount_base while current_base >= 0: value = 0 sources = [] buf_sources = list(cache.available_sources) for s in [src for src in cache.available_sources if src['base'] == current_base]: value += s['amount'] * pow(10, s['base']) sources.append(s) buf_sources.remove(s) if value >= amount * pow(10, amount_base): overhead = value - int(amount) * pow(10, amount_base) overhead, overhead_max_base = reduce_base(overhead, 0) if overhead_max_base >= current_base: return (sources, buf_sources) current_base -= 1 raise NotEnoughMoneyError(value, community.currency, len(sources), amount * pow(10, amount_base))
def tx_sources(self, amount, community): """ Get inputs to generate a transaction with a given amount of money :param int amount: The amount target value :param community: The community target of the transaction :return: The list of inputs to use in the transaction document """ # such a dirty algorithmm # everything should be done again from scratch # in future versions def current_value(inputs, overhs): i = 0 for s in inputs: i += s['amount'] * (10**s['base']) for o in overhs: i -= o[0] * (10**o[1]) return i amount, amount_base = reduce_base(amount, 0) cache = self.caches[community.currency] if cache.available_sources: current_base = max([src['base'] for src in cache.available_sources]) value = 0 sources = [] outputs = [] overheads = [] buf_sources = list(cache.available_sources) while current_base >= 0: for s in [src for src in cache.available_sources if src['base'] == current_base]: test_sources = sources + [s] val = current_value(test_sources, overheads) # if we have to compute an overhead if current_value(test_sources, overheads) > amount * (10**amount_base): overhead = current_value(test_sources, overheads) - int(amount) * (10**amount_base) # we round the overhead in the current base # exemple : 12 in base 1 -> 1*10^1 overhead = int(round(float(overhead) / (10**current_base))) source_value = s['amount'] * (10**s['base']) out = int((source_value - (overhead * (10**current_base)))/(10**current_base)) if out * (10**current_base) <= amount * (10**amount_base): sources.append(s) buf_sources.remove(s) overheads.append((overhead, current_base)) outputs.append((out, current_base)) # else just add the output else: sources.append(s) buf_sources.remove(s) outputs.append((s['amount'] , s['base'])) if current_value(sources, overheads) == amount * (10 ** amount_base): return sources, outputs, overheads, buf_sources current_base -= 1 raise NotEnoughMoneyError(value, community.currency, len(sources), amount * pow(10, amount_base))
def prepare_tx(self, key, receiver, blockstamp, amount, amount_base, message, currency): """ Prepare a simple Transaction document :param SigningKey key: the issuer of the transaction :param str receiver: the target of the transaction :param duniterpy.documents.BlockUID blockstamp: the blockstamp :param int amount: the amount sent to the receiver :param int amount_base: the amount base of the currency :param str message: the comment of the tx :param str currency: the target community :return: the transaction document :rtype: List[sakia.data.entities.Transaction] """ forged_tx = [] sources = [None]*41 while len(sources) > 40: result = self.tx_sources(int(amount), amount_base, currency, key.pubkey) sources = result[0] computed_outputs = result[1] overheads = result[2] # Fix issue #594 if len(sources) > 40: sources_value = 0 for s in sources[:39]: sources_value += s.amount * (10**s.base) sources_value, sources_base = reduce_base(sources_value, 0) chained_tx = self.prepare_tx(key, key.pubkey, blockstamp, sources_value, sources_base, "[CHAINED]", currency) forged_tx += chained_tx self._sources_processor.consume(sources) logging.debug("Inputs : {0}".format(sources)) inputs = self.tx_inputs(sources) unlocks = self.tx_unlocks(sources) outputs = self.tx_outputs(key.pubkey, receiver, computed_outputs, overheads) logging.debug("Outputs : {0}".format(outputs)) txdoc = TransactionDoc(10, currency, blockstamp, 0, [key.pubkey], inputs, unlocks, outputs, message, None) txdoc.sign([key]) self.commit_outputs_to_self(currency, key.pubkey, txdoc) time = self._blockchain_processor.time(currency) tx = Transaction(currency=currency, pubkey=key.pubkey, sha_hash=txdoc.sha_hash, written_block=0, blockstamp=blockstamp, timestamp=time, signatures=txdoc.signatures, issuers=[key.pubkey], receivers=[receiver], amount=amount, amount_base=amount_base, comment=txdoc.comment, txid=0, state=Transaction.TO_SEND, local=True, raw=txdoc.signed_raw()) forged_tx.append(tx) return forged_tx
def tx_sources(self, amount, amount_base, currency, pubkey): """ Get inputs to generate a transaction with a given amount of money :param int amount: The amount target value :param int amount_base: The amount base target value :param str currency: The community target of the transaction :param str pubkey: The pubkey owning the sources :return: The list of inputs to use in the transaction document """ # such a dirty algorithmm # everything should be done again from scratch # in future versions def current_value(inputs, overhs): i = 0 for s in inputs: i += s.amount * (10**s.base) for o in overhs: i -= o[0] * (10**o[1]) return i amount, amount_base = reduce_base(amount, amount_base) available_sources = self._sources_processor.available(currency, pubkey) if available_sources: current_base = max([src.base for src in available_sources]) value = 0 sources = [] outputs = [] overheads = [] buf_sources = list(available_sources) while current_base >= 0: for s in [src for src in available_sources if src.base == current_base]: test_sources = sources + [s] val = current_value(test_sources, overheads) # if we have to compute an overhead if current_value(test_sources, overheads) > amount * (10**amount_base): overhead = current_value(test_sources, overheads) - int(amount) * (10**amount_base) # we round the overhead in the current base # exemple : 12 in base 1 -> 1*10^1 overhead = int(round(float(overhead) / (10**current_base))) source_value = s.amount * (10**s.base) out = int((source_value - (overhead * (10**current_base)))/(10**current_base)) if out * (10**current_base) <= amount * (10**amount_base): sources.append(s) buf_sources.remove(s) overheads.append((overhead, current_base)) outputs.append((out, current_base)) # else just add the output else: sources.append(s) buf_sources.remove(s) outputs.append((s.amount, s.base)) if current_value(sources, overheads) == amount * (10 ** amount_base): return sources, outputs, overheads current_base -= 1 raise NotEnoughChangeError(value, currency, len(sources), amount * pow(10, amount_base))
def outputs_from_sources(self, amount, sources): # such a dirty algorithmm # shamelessly copy pasted from sakia def current_value(inputs, overhs): i = 0 for s in inputs: i += s.amount * (10**s.base) for o in overhs: i -= o[0] * (10**o[1]) return i amount, amount_base = reduce_base(amount, 0) current_base = max([src.base for src in sources]) result_sources = [] outputs = [] overheads = [] buf_sources = list(sources) while current_base >= 0: for s in [src for src in buf_sources if src.base == current_base]: test_sources = result_sources + [s] val = current_value(test_sources, overheads) # if we have to compute an overhead if current_value(test_sources, overheads) > amount * (10**amount_base): overhead = current_value( test_sources, overheads) - int(amount) * (10**amount_base) # we round the overhead in the current base # exemple : 12 in base 1 -> 1*10^1 overhead = int(round(float(overhead) / (10**current_base))) source_value = s.amount * (10**s.base) out = int( (source_value - (overhead * (10**current_base))) / (10**current_base)) if out * (10**current_base) <= amount * (10**amount_base): result_sources.append(s) buf_sources.remove(s) overheads.append((overhead, current_base)) outputs.append((out, current_base)) # else just add the output else: result_sources.append(s) buf_sources.remove(s) outputs.append((s.amount, s.base)) if current_value(result_sources, overheads) == amount * (10**amount_base): return result_sources, outputs, overheads current_base -= 1 raise ValueError("Not enough sources")
def parse_transaction_doc(tx_doc, pubkey, block_number, mediantime, txid): """ Parse a transaction :param duniterpy.documents.Transaction tx_doc: The tx json data :param str pubkey: The pubkey of the transaction to parse, to know if its a receiver or issuer :param int block_number: The block number where we found the tx :param int mediantime: Median time on the network :param int txid: The latest txid :return: the found transaction """ receivers = [ o.conditions.left.pubkey for o in tx_doc.outputs if o.conditions.left.pubkey != tx_doc.issuers[0] ] in_issuers = len([i for i in tx_doc.issuers if i == pubkey]) > 0 in_outputs = len( [o for o in tx_doc.outputs if o.conditions.left.pubkey == pubkey]) > 0 if len(receivers) == 0: receivers = [tx_doc.issuers[0]] # Transaction to self outputs = [o for o in tx_doc.outputs] amount = 0 for o in outputs: amount += o.amount * math.pow(10, o.base) amount, amount_base = reduce_base(amount, 0) elif in_issuers or in_outputs: # If the wallet pubkey is in the issuers we sent this transaction if in_issuers: outputs = [ o for o in tx_doc.outputs if o.conditions.left.pubkey != pubkey ] amount = 0 for o in outputs: amount += o.amount * math.pow(10, o.base) # If we are not in the issuers, # maybe we are in the recipients of this transaction else: outputs = [ o for o in tx_doc.outputs if o.conditions.left.pubkey == pubkey ] amount = 0 for o in outputs: amount += o.amount * math.pow(10, o.base) amount, amount_base = reduce_base(amount, 0) else: return None transaction = Transaction(currency=tx_doc.currency, pubkey=pubkey, sha_hash=tx_doc.sha_hash, written_block=block_number, blockstamp=tx_doc.blockstamp, timestamp=mediantime, signatures=tx_doc.signatures, issuers=tx_doc.issuers, receivers=receivers, amount=amount, amount_base=amount_base, comment=tx_doc.comment, txid=txid, state=Transaction.VALIDATED, raw=tx_doc.signed_raw()) return transaction
def test_reduce_base_2(self): amount = 120 base = 4 computed = reduce_base(amount, base) self.assertEqual(computed[0], 12) self.assertEqual(computed[1], 5)
def parse_transaction_doc(tx_doc, pubkey, block_number, mediantime, txid): """ Parse a transaction :param duniterpy.documents.Transaction tx_doc: The tx json data :param str pubkey: The pubkey of the transaction to parse, to know if its a receiver or issuer :param int block_number: The block number where we found the tx :param int mediantime: Median time on the network :param int txid: The latest txid :return: the found transaction """ receivers = [o.conditions.left.pubkey for o in tx_doc.outputs if o.conditions.left.pubkey != tx_doc.issuers[0]] in_issuers = len([i for i in tx_doc.issuers if i == pubkey]) > 0 in_outputs = len([o for o in tx_doc.outputs if o.conditions.left.pubkey == pubkey]) > 0 if len(receivers) == 0 and in_issuers: receivers = [tx_doc.issuers[0]] # Transaction to self outputs = [o for o in tx_doc.outputs] amount = 0 for o in outputs: amount += o.amount * math.pow(10, o.base) amount, amount_base = reduce_base(amount, 0) elif in_issuers or in_outputs: # If the wallet pubkey is in the issuers we sent this transaction if in_issuers: outputs = [o for o in tx_doc.outputs if o.conditions.left.pubkey != pubkey] amount = 0 for o in outputs: amount += o.amount * math.pow(10, o.base) # If we are not in the issuers, # maybe we are in the recipients of this transaction else: outputs = [o for o in tx_doc.outputs if o.conditions.left.pubkey == pubkey] amount = 0 for o in outputs: amount += o.amount * math.pow(10, o.base) amount, amount_base = reduce_base(amount, 0) else: return None transaction = Transaction(currency=tx_doc.currency, pubkey=pubkey, sha_hash=tx_doc.sha_hash, written_block=block_number, blockstamp=tx_doc.blockstamp, timestamp=mediantime, signatures=tx_doc.signatures, issuers=tx_doc.issuers, receivers=receivers, amount=amount, amount_base=amount_base, comment=tx_doc.comment, txid=txid, state=Transaction.VALIDATED, raw=tx_doc.signed_raw()) return transaction