diff --git a/__init__.py b/__init__.py index 62ea64ab7eb9621e6623d25cbfb44325ae526778..38a920998b23a4645a39eff0789e6bb6b8e88108 100644 --- a/__init__.py +++ b/__init__.py @@ -177,9 +177,9 @@ class API: return response - def merkle_easy_parser(self, path): + def merkle_easy_parser(self, path, begin=None, end=None): root = self.requests_get(path, leaves='true').json() - for leaf in root['leaves']: + for leaf in root['leaves'][begin:end]: yield self.requests_get(path, leaf=leaf).json()['leaf'] from . import pks, ucg, hdc, wrappers diff --git a/hdc/transactions/__init__.py b/hdc/transactions/__init__.py index cc230c0ae2e9b60515223d9b468c9cd0b19d7abb..61b6bb61ce0d79816aecd0645d20e14802aded8e 100644 --- a/hdc/transactions/__init__.py +++ b/hdc/transactions/__init__.py @@ -71,34 +71,42 @@ class Last(Base): class Sender(Base): """GET all the transactions sent by this sender and stored by this node (should contain all transactions of the sender).""" - def __init__(self, pgp_fingerprint): + def __init__(self, pgp_fingerprint, begin=None, end=None): """ Arguments: - `pgp_fingerprint`: PGP fingerprint of the key we want to see sent transactions. + - `begin`: integer value used by the merkle parser to know when to begin requesting. + - `end`: integer value used by the merkle parser to know when to end requesting. """ super().__init__() self.pgp_fingerprint = pgp_fingerprint + self.begin = begin + self.end = end def __get__(self, **kwargs): - return self.merkle_easy_parser('/sender/%s' % self.pgp_fingerprint) + return self.merkle_easy_parser('/sender/%s' % self.pgp_fingerprint, begin=self.begin, end=self.end) class Recipient(Base): """GET all the transactions received for this recipient stored by this node.""" - def __init__(self, pgp_fingerprint): + def __init__(self, pgp_fingerprint, begin=None, end=None): """ Arguments: - `pgp_fingerprint`: PGP fingerprint of the key we want to see sent transactions. + - `begin`: integer value used by the merkle parser to know when to begin requesting. + - `end`: integer value used by the merkle parser to know when to end requesting. """ super().__init__() self.pgp_fingerprint = pgp_fingerprint + self.begin = begin + self.end = end def __get__(self, **kwargs): - return self.merkle_easy_parser('/recipient/%s' % self.pgp_fingerprint) + return self.merkle_easy_parser('/recipient/%s' % self.pgp_fingerprint, begin=self.begin, end=self.end) class View(Base): """GET the transaction of given TRANSACTION_ID.""" diff --git a/wrappers/__init__.py b/wrappers/__init__.py index d4a272760f74cc98cc7ea12e80c85028b5738dd7..2e7f868a60bc02826c92644038f2d16810c14448 100644 --- a/wrappers/__init__.py +++ b/wrappers/__init__.py @@ -17,7 +17,7 @@ # Caner Candan <caner@candan.fr>, http://caner.candan.fr # -import hashlib, logging +import logging from .. import pks, ucg, hdc, settings logger = logging.getLogger("wrappers") @@ -26,170 +26,4 @@ class Wrapper: def __call__(self): pass -class Transaction(Wrapper): - def __init__(self, type, pgp_fingerprint, message=''): - self.pgp_fingerprint = pgp_fingerprint - self.message = message - self.type = type - - def __call__(self): - try: - last_tx = hdc.transactions.sender.Last(self.pgp_fingerprint).get() - except ValueError: - last_tx = None - - context_data = {} - context_data.update(settings) - context_data['version'] = 1 - context_data['number'] = 0 if not last_tx else last_tx['transaction']['number']+1 - context_data['previousHash'] = hashlib.sha1(("%(raw)s%(signature)s" % last_tx).encode('ascii')).hexdigest().upper() if last_tx else None - context_data['message'] = self.message - context_data['type'] = self.type - context_data.update(self.get_context_data()) - - tx = """\ -Version: %(version)d -Currency: %(currency)s -Sender: %(fingerprint)s -Number: %(number)d -""" % context_data - - if last_tx: tx += "PreviousHash: %(previousHash)s\n" % context_data - - tx += self.get_message(context_data) - - tx += """\ -Comment: -%(message)s -""" % context_data - - tx = tx.replace("\n", "\r\n") - txs = settings['gpg'].sign(tx, detach=True) - - return self.process(tx, txs) - - def get_context_data(self): - return {} - - def get_message(self, context_data, tx=''): - return tx - - def process(self, tx, txs): - try: - hdc.transactions.Process().post(transaction=tx, signature=txs) - except ValueError as e: - print(e) - else: - return True - - return False - -class Transfer(Transaction): - def __init__(self, pgp_fingerprint, recipient, coins, message=''): - super().__init__('TRANSFER', pgp_fingerprint, message) - self.recipient = recipient - self.coins = coins - - def get_message(self, context_data, tx=''): - context_data['recipient'] = self.recipient - - tx += """\ -Recipient: %(recipient)s -Type: %(type)s -Coins: -""" % context_data - - for coin in self.coins.split(','): - data = coin.split(':') - issuer = data[0] - for number in data[1:]: - context_data.update(hdc.coins.View(issuer, int(number)).get()) - tx += '%(id)s, %(transaction)s\n' % context_data - - return tx - -class Issue(Transaction): - def __init__(self, pgp_fingerprint, amendment, coins, message=''): - super().__init__('ISSUANCE', pgp_fingerprint, message) - self.amendment = amendment - self.coins = coins - - def get_next_coin_number(self, coins): - number = 0 - for c in coins: - candidate = int(c['id'].split('-')[1]) - if candidate > number: number = candidate - return number+1 - - def get_message(self, context_data, tx=''): - context_data['amendment'] = self.amendment - - tx += """\ -Recipient: %(fingerprint)s -Type: %(type)s -Coins: -""" % context_data - - try: - last_issuance = hdc.transactions.sender.issuance.Last(self.pgp_fingerprint).get() - except ValueError: - last_issuance = None - - previous_idx = 0 if not last_issuance else self.get_next_coin_number(last_issuance['transaction']['coins']) - - for idx, coin in enumerate(self.coins): - context_data['idx'] = idx+previous_idx - context_data['base'], context_data['power'] = [int(x) for x in coin.split(',')] - tx += '%(fingerprint)s-%(idx)d-%(base)d-%(power)d-A-%(amendment)d\n' % context_data - - return tx - -class CoinsWrapper(Wrapper): - def __init__(self, pgp_fingerprint): - self.pgp_fingerprint = pgp_fingerprint - -class CoinsGet(CoinsWrapper): - def __init__(self, pgp_fingerprint, values): - super().__init__(pgp_fingerprint) - self.values = values - - def __call__(self): - __list = hdc.coins.List(self.pgp_fingerprint).get() - coins = {} - for c in __list['coins']: - for id in c['ids']: - n,b,p,t,i = id.split('-') - amount = int(b) * 10**int(p) - coins[amount] = {'issuer': c['issuer'], 'number': int(n), 'base': int(b), 'power': int(p), 'type': t, 'type_number': int(i), 'amount': amount} - - issuers = {} - for v in self.values: - if v in coins: - c = coins[v] - issuers[c['issuer']] = issuers.get(c['issuer']) or [] - issuers[c['issuer']].append(c) - else: - raise ValueError('You do not have enough coins of value (%d)' % v) - - res = '' - for i, issuer in enumerate(issuers): - if i > 0: res += ',' - res += issuer - for c in issuers[issuer]: - res += ':%(number)d' % c - - return res - -class CoinsList(CoinsWrapper): - def __call__(self): - __list = hdc.coins.List(self.pgp_fingerprint).get() - coins = [] - __sum = 0 - for c in __list['coins']: - for id in c['ids']: - n,b,p,t,i = id.split('-') - amount = int(b) * 10**int(p) - __dict = {'issuer': c['issuer'], 'number': int(n), 'base': int(b), 'power': int(p), 'type': t, 'type_number': int(i), 'amount': amount} - coins.append(__dict) - __sum += amount - return __sum, coins +from . import transactions, coins diff --git a/wrappers/coins.py b/wrappers/coins.py new file mode 100644 index 0000000000000000000000000000000000000000..106898563ce98ed6360d72796cfeb2197e12f224 --- /dev/null +++ b/wrappers/coins.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +import logging +from . import Wrapper, pks, ucg, hdc, settings + +logger = logging.getLogger("coins") + +class Coins(Wrapper): + def __init__(self, pgp_fingerprint): + self.pgp_fingerprint = pgp_fingerprint + +class Get(Coins): + def __init__(self, pgp_fingerprint, values): + super().__init__(pgp_fingerprint) + self.values = values + + def __call__(self): + __list = hdc.coins.List(self.pgp_fingerprint).get() + coins = {} + for c in __list['coins']: + for id in c['ids']: + n,b,p,t,i = id.split('-') + amount = int(b) * 10**int(p) + if amount not in coins: coins[amount] = [] + coins[amount].append({'issuer': c['issuer'], 'number': int(n), 'base': int(b), 'power': int(p), 'type': t, 'type_number': int(i), 'amount': amount}) + + issuers = {} + for v in self.values: + if v in coins and coins[v]: + c = coins[v].pop() + issuers[c['issuer']] = issuers.get(c['issuer']) or [] + issuers[c['issuer']].append(c) + else: + raise ValueError('You do not have enough coins of value (%d)' % v) + + res = '' + for i, issuer in enumerate(issuers): + if i > 0: res += ',' + res += issuer + for c in issuers[issuer]: + res += ':%(number)d' % c + + return res + +class List(Coins): + def __init__(self, pgp_fingerprint, limit=None): + super().__init__(pgp_fingerprint) + self.limit = limit + + def __call__(self): + __list = hdc.coins.List(self.pgp_fingerprint).get() + coins = [] + __sum = 0 + + for c in __list['coins']: + for id in c['ids']: + n,b,p,t,i = id.split('-') + amount = int(b) * 10**int(p) + __dict = {'issuer': c['issuer'], 'number': int(n), 'base': int(b), 'power': int(p), 'type': t, 'type_number': int(i), 'amount': amount} + + if not self.limit or self.limit >= amount: + coins.append(__dict) + __sum += amount + + return __sum, coins diff --git a/wrappers/transactions.py b/wrappers/transactions.py new file mode 100644 index 0000000000000000000000000000000000000000..670dbffaab7902c6c9db435ee2d9fa69a845c0e8 --- /dev/null +++ b/wrappers/transactions.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +import hashlib, logging +from . import Wrapper, pks, ucg, hdc, settings + +logger = logging.getLogger("transactions") + +class Transaction(Wrapper): + def __init__(self, type, pgp_fingerprint, message='', peering=None): + self.pgp_fingerprint = pgp_fingerprint + self.message = message + self.type = type + self.error = None + self.peering = peering + + def __call__(self): + try: + last_tx = hdc.transactions.sender.Last(self.pgp_fingerprint).get() + except ValueError: + last_tx = None + + context_data = {} + context_data.update(settings) + context_data.update(self.peering if self.peering else ucg.Peering().get()) + context_data['version'] = 1 + context_data['number'] = 0 if not last_tx else last_tx['transaction']['number']+1 + context_data['previousHash'] = hashlib.sha1(("%(raw)s%(signature)s" % last_tx).encode('ascii')).hexdigest().upper() if last_tx else None + context_data['message'] = self.message + context_data['type'] = self.type + context_data['fingerprint'] = self.pgp_fingerprint + context_data.update(self.get_context_data()) + + tx = """\ +Version: %(version)d +Currency: %(currency)s +Sender: %(fingerprint)s +Number: %(number)d +""" % context_data + + if last_tx: tx += "PreviousHash: %(previousHash)s\n" % context_data + + try: + tx += self.get_message(context_data) + except ValueError as e: + self.error = e + return False + + tx += """\ +Comment: +%(message)s +""" % context_data + + tx = tx.replace("\n", "\r\n") + txs = settings['gpg'].sign(tx, detach=True) + + return self.process(tx, txs) + + def get_context_data(self): + return {} + + def get_message(self, context_data, tx=''): + return tx + + def get_error(self): + return self.error + + def process(self, tx, txs): + try: + hdc.transactions.Process().post(transaction=tx, signature=txs) + except ValueError as e: + self.error = str(e) + else: + return True + + return False + + def parse_coins(self, coins_message): + coins = [] + for coin in coins_message.split(','): + data = coin.split(':') + issuer, numbers = data[0], data[1:] + for number in numbers: + view = hdc.coins.View(issuer, int(number)).get() + if view['owner'] != self.pgp_fingerprint: + raise ValueError('Trying to divide a coin you do not own (%s)' % view['id']) + coins.append(view) + return coins + + def get_coins_sum(self, coins): + __sum = 0 + for coin in coins: + base, power = coin['id'].split('-')[2:4] + __sum += int(base) * 10**int(power) + return __sum + + def check_coins_sum(self, __sum): + m = re.match(r'^(\d)(0*)$', str(__sum)) + if not m: raise ValueError('bad sum value %d' % __sum) + +class Transfer(Transaction): + def __init__(self, pgp_fingerprint, recipient, coins, message=''): + super().__init__('TRANSFER', pgp_fingerprint, message) + self.recipient = recipient + self.coins = coins + + def get_message(self, context_data, tx=''): + context_data['recipient'] = self.recipient + + tx += """\ +Recipient: %(recipient)s +Type: %(type)s +Coins: +""" % context_data + + for coin in self.coins.split(','): + data = coin.split(':') + issuer = data[0] + for number in data[1:]: + context_data.update(hdc.coins.View(issuer, int(number)).get()) + tx += '%(id)s, %(transaction)s\n' % context_data + + return tx + +class MonoTransaction(Transaction): + def get_next_coin_number(self, coins): + number = 0 + for c in coins: + candidate = int(c['id'].split('-')[1]) + if candidate > number: number = candidate + return number+1 + + def get_message(self, context_data, tx=''): + tx += """\ +Recipient: %(fingerprint)s +Type: %(type)s +Coins: +""" % context_data + + try: + last_issuance = hdc.transactions.sender.issuance.Last(self.pgp_fingerprint).get() + except ValueError: + last_issuance = None + + context_data['previous_idx'] = 0 if not last_issuance else self.get_next_coin_number(last_issuance['transaction']['coins']) + + tx += self.get_mono_message(context_data) + + return tx + + def get_mono_message(self, context_data, tx=''): + return tx + +class Issue(MonoTransaction): + def __init__(self, pgp_fingerprint, amendment, coins, message=''): + super().__init__('ISSUANCE', pgp_fingerprint, message) + self.amendment = amendment + self.coins = coins + + def get_mono_message(self, context_data, tx=''): + context_data['amendment'] = self.amendment + + for idx, coin in enumerate(self.coins): + context_data['idx'] = idx + context_data['previous_idx'] + context_data['base'], context_data['power'] = [int(x) for x in coin.split(',')] + tx += '%(fingerprint)s-%(idx)d-%(base)d-%(power)d-A-%(amendment)d\n' % context_data + + return tx + +class Fusion(MonoTransaction): + def __init__(self, pgp_fingerprint, coins, message=''): + super().__init__('FUSION', pgp_fingerprint, message) + self.coins = coins + + def get_mono_message(self, context_data, tx=''): + coins = self.parse_coins(self.coins) + self.check_coins_sum(self.get_coins_sum(coins)) + + context_data['base'], context_data['power'] = int(m.groups()[0]), len(m.groups()[1]) + tx += '%(fingerprint)s-%(previous_idx)d-%(base)d-%(power)d-F-%(number)d\n' % context_data + + for coin in coins: tx += '%(id)s, %(transaction)s\n' % coin + + return tx + +class Divide(MonoTransaction): + def __init__(self, pgp_fingerprint, old_coins, new_coins, message=''): + super().__init__('DIVISION', pgp_fingerprint, message) + self.old_coins = old_coins + self.new_coins = new_coins + + def get_mono_message(self, context_data, tx=''): + old_coins = self.parse_coins(self.old_coins) + old_coins_sum = self.get_coins_sum(old_coins) + + new_coins_sum = 0 + for idx, coin in enumerate(self.new_coins): + context_data['idx'] = idx + context_data['previous_idx'] + context_data['base'], context_data['power'] = [int(x) for x in coin.split(',')] + new_coins_sum += context_data['base'] * 10**context_data['power'] + tx += '%(fingerprint)s-%(idx)d-%(base)d-%(power)d-D-%(number)d\n' % context_data + + if old_coins_sum != new_coins_sum: + raise ValueError('Amount of old coins (%d) is not equal to new coins (%d)' % (old_coins_sum, new_coins_sum)) + + for coin in old_coins: tx += '%(id)s, %(transaction)s\n' % coin + + return tx