diff --git a/src/sakia/core/wallet.py b/src/sakia/core/wallet.py index b18700f3e4644d83008918b7132c7194c0b0c456..7cd58741298fda0f7c007d3f33b192e0ad5e0b3f 100644 --- a/src/sakia/core/wallet.py +++ b/src/sakia/core/wallet.py @@ -175,22 +175,52 @@ class Wallet(QObject): :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] - current_base = amount_base + 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: - 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) + 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, @@ -222,31 +252,35 @@ class Wallet(QObject): unlocks.append(Unlock(i, [SIGParameter(0)])) return unlocks - def tx_outputs(self, pubkey, amount, inputs): + def tx_outputs(self, pubkey, outputs, overheads): """ Get outputs to generate a transaction with a given amount of money :param str pubkey: The target pubkey of the transaction - :param int amount: The amount to send + :param list outputs: The amount to send :param list inputs: The inputs used to send the given amount of money + :param list overheads: The overheads used to send the given amount of money :return: The list of outputs to use in the transaction document """ - outputs = [] - inputs_value = 0 - for i in inputs: - logging.debug(i) - inputs_value += i['amount'] * pow(10, i['base']) - inputs_max_base = max([i['base'] for i in inputs]) - overhead = inputs_value - int(amount) - - amount, amount_base = int(amount / pow(10, inputs_max_base)), inputs_max_base - overhead, overhead_base = int(overhead / pow(10, inputs_max_base)), inputs_max_base - - outputs.append(OutputSource(amount, amount_base, output.Condition.token(output.SIG.token(pubkey)))) - if overhead != 0: - outputs.append(OutputSource(overhead, overhead_base, output.Condition.token(output.SIG.token(self.pubkey)))) - return outputs + total = [] + outputs_bases = set(o[1] for o in outputs) + for base in outputs_bases: + output_sum = 0 + for o in outputs: + if o[1] == base: + output_sum += o[0] + total.append(OutputSource(output_sum, base, output.Condition.token(output.SIG.token(pubkey)))) + + overheads_bases = set(o[1] for o in overheads) + for base in overheads_bases: + overheads_sum = 0 + for o in overheads: + if o[1] == base: + overheads_sum += o[0] + total.append(OutputSource(overheads_sum, base, output.Condition.token(output.SIG.token(self.pubkey)))) + + return total def prepare_tx(self, pubkey, blockstamp, amount, message, community): """ @@ -260,12 +294,14 @@ class Wallet(QObject): """ result = self.tx_sources(int(amount), community) sources = result[0] - self.caches[community.currency].available_sources = result[1][1:] + computed_outputs = result[1] + overheads = result[2] + self.caches[community.currency].available_sources = result[3][1:] logging.debug("Inputs : {0}".format(sources)) inputs = self.tx_inputs(sources) unlocks = self.tx_unlocks(sources) - outputs = self.tx_outputs(pubkey, amount, sources) + outputs = self.tx_outputs(pubkey, computed_outputs, overheads) logging.debug("Outputs : {0}".format(outputs)) tx = Transaction(PROTOCOL_VERSION, community.currency, blockstamp, 0, [self.pubkey], inputs, unlocks, diff --git a/src/sakia/tests/unit/core/test_wallet.py b/src/sakia/tests/unit/core/test_wallet.py index 79035777697730b9368b25fb4ad0816c3aa2f1ee..eee64a6e121b83bbc353c1a6d9de35dfacb27016 100644 --- a/src/sakia/tests/unit/core/test_wallet.py +++ b/src/sakia/tests/unit/core/test_wallet.py @@ -173,4 +173,90 @@ Unlocks: Outputs: 10:1:SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn) 1:1:SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ) +Comment:""" + " \n") + + def test_prepare_tx_base_1_overheads(self): + community = MagicMock("sakia.core.Community") + community.currency = "test_currency" + cache = MagicMock("sakia.core.txhistory.TxHistory") + cache.available_sources = [{ + "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + "type": "D", + "noffset": 2, + "identifier": "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365", + "amount": 15, + "base": 0 + }, + { + "pubkey": "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + "type": "D", + "noffset": 4, + "identifier": "A0AC57E2E4B24D66F2D25E66D8501D8E881D9E6453D1789ED753D7D426537ED5", + "amount": 85, + "base": 0 + }, + { + "pubkey": "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + "type": "T", + "noffset": 4, + "identifier": "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67", + "amount": 11, + "base": 1 + }] + wallet = Wallet(0, "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ", + "Wallet 1", self.identities_registry) + wallet.caches["test_currency"] = cache + tx = wallet.prepare_tx("FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn", + BlockUID(32, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8"), + 101, "", community) + self.assertEqual(tx.blockstamp.number, 32) + self.assertEqual(tx.blockstamp.sha_hash, "000005E0F228038E4DDD4F6CA4ACB01EC88FBAF8") + self.assertEqual(len(tx.issuers), 1) + self.assertEqual(tx.issuers[0], "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ") + self.assertEqual(len(tx.inputs), 2) + self.assertEqual(tx.inputs[0].origin_id, "7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67") + self.assertEqual(tx.inputs[0].source, "T") + self.assertEqual(tx.inputs[0].index, 4) + self.assertEqual(tx.inputs[1].origin_id, "FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365") + self.assertEqual(tx.inputs[1].source, "D") + self.assertEqual(tx.inputs[1].index, 2) + self.assertEqual(len(tx.outputs), 4) + self.assertEqual(tx.outputs[0].amount, 1) + self.assertEqual(tx.outputs[0].base, 0) + self.assertEqual(pypeg2.compose(tx.outputs[0].conditions, output.Condition), + "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)") + self.assertEqual(tx.outputs[1].amount, 10) + self.assertEqual(tx.outputs[1].base, 1) + self.assertEqual(pypeg2.compose(tx.outputs[1].conditions, output.Condition), + "SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn)") + self.assertEqual(tx.outputs[2].amount, 14) + self.assertEqual(tx.outputs[2].base, 0) + self.assertEqual(pypeg2.compose(tx.outputs[2].conditions, output.Condition), + "SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)") + self.assertEqual(tx.outputs[3].amount, 1) + self.assertEqual(tx.outputs[3].base, 1) + self.assertEqual(pypeg2.compose(tx.outputs[3].conditions, output.Condition), + "SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ)") + self.assertEqual(len(tx.unlocks), 2) + self.assertEqual(tx.unlocks[0].index, 0) + self.assertEqual(tx.unlocks[0].parameters[0].index, 0) + self.assertEqual(tx.unlocks[1].index, 1) + self.assertEqual(tx.unlocks[1].parameters[0].index, 0) + self.assertEqual(tx.raw(), """Version: 2 +Type: Transaction +Currency: test_currency +Locktime: 0 +Issuers: +7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ +Inputs: +T:7518C700E78B56CC21FB1DDC6CBAB24E0FACC9A798F5ED8736EA007F38617D67:4 +D:FCAD5A388AC8A811B45A9334A375585E77071AA9F6E5B6896582961A6C66F365:2 +Unlocks: +0:SIG(0) +1:SIG(0) +Outputs: +1:0:SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn) +10:1:SIG(FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn) +14:0:SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ) +1:1:SIG(7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ) Comment:""" + " \n")