usawa

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | Submodules | LICENSE

commit d228482a4dc102002d7df8fbf2d65da4e467feae
parent 4a04d9a8bcbbbf56f984eb785247a08ff2dc39af
Author: lash <dev@holbrook.no>
Date:   Mon,  5 Jan 2026 18:56:42 +0100

Add tool stub for adding entry to ledger

Diffstat:
Mdummy/usawa/crypto.py | 15+++++++++++++++
Adummy/usawa/runnable/add.py | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdummy/usawa/runnable/create.py | 38++++++++++++++++++++++++++++++++++----
Mdummy/usawa/store.py | 54+++++++++++++++++++++++++++++++++++++++++++++++-------
4 files changed, 171 insertions(+), 11 deletions(-)

diff --git a/dummy/usawa/crypto.py b/dummy/usawa/crypto.py @@ -32,6 +32,16 @@ class Wallet: raise NotImplementedError + def privkey(self): + """Return the private key data in the wallet. + + :returns: Private key data. + :rtype: bytes + :todo: Raise local error if sign fail + """ + raise NotImplementedError + + def verify(self, v, sig): """Verify signature data against the given message. @@ -80,6 +90,11 @@ class DemoWallet(Wallet): return self.pubk.encode() + def privkey(self, passphrase=None): + """Implements usawa.Wallet.privkey + """ + return self.pk.encode() + def verify(self, v, sig): """Implements usawa.Wallet.verify """ diff --git a/dummy/usawa/runnable/add.py b/dummy/usawa/runnable/add.py @@ -0,0 +1,75 @@ +import logging +import urllib.parse +import argparse +import uuid +import datetime + +from usawa import Ledger, Entry, EntryPart, DemoWallet, UnitIndex, load +from usawa.store import LedgerStore +from whee.valkey import ValkeyStore + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + + +def parse_type(v): + if v not in ['expense', 'income', 'liability', 'asset']: + raise ValueError('invalid type') + return v + + +def parse_account(v): + logg.warning('account parsing is noop') + return v + + +def parse_amount(uidx, sym, v): + return uidx.from_floatstring(sym, v) + + +argp = argparse.ArgumentParser() +argp.add_argument('-f', type=str, help='load ledger metadata from XML file') +argp.add_argument('--topic', type=str, help='hexadecimal topic to load') +arg = argp.parse_args() + +ledger = None +unit = 'BTC' +uidx = UnitIndex(unit) +logg.warning('hardcoding unit index default sym, need unitindex xml parser') +logg.warning('using default sym for all entries for now') +if arg.f: + ledger_tree = load(arg.f) + ledger = Ledger.from_tree(ledger_tree, uidx) + +db = ValkeyStore('') +store = LedgerStore(db, ledger) +pk = store.get_key() +wallet = DemoWallet(privatekey=pk) +dt = datetime.datetime.now() + +v = input('Entry description: ') +dsc = v + +pair = [] +amount = None +for k in ['src', 'dst']: + v = input('Entry {} type: '.format(k)) + typ = parse_type(v) + + v = input('Entry {} account: '.format(k)) + account = parse_account(v) + + if amount != None: + amount *= -1.0 + else: + v = input('Entry {} amount: '.format(k)) + amount = parse_amount(uidx, unit, v) + + pair.append(EntryPart(typ, account, amount)) + +ref = uuid.uuid4() +logg.debug('generated ref {}'.format(ref)) + +entry = Entry(pair[0], pair[1], unit, ledger.serial, dt, parent=ledger.current(), description=dsc, ref=str(ref)) +entry.sign(wallet) +ledger.add_entry(entry) diff --git a/dummy/usawa/runnable/create.py b/dummy/usawa/runnable/create.py @@ -1,7 +1,10 @@ +import os import logging import urllib.parse from usawa import Ledger, DemoWallet, UnitIndex +from usawa.store import LedgerStore +from whee.valkey import ValkeyStore logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() @@ -28,6 +31,7 @@ def parse_unit(v): raise ValueError('invalid unit string') return v.upper() + print("Creating new ledger") v = input("Topic: ") @@ -37,23 +41,49 @@ if len(v) > 0: topic = bytes.fromhex(r) logg.debug('topic {} -> {}'.format(v, topic)) -v = input("Default unit: (default: BTC)") +v = input("Default unit: (default: BTC): ") if len(v) == 0: v = 'BTC' unit = parse_unit(v) -v = input("Unit decimals (default: 2):") +v = input("Unit decimals (default: 2): ") if len(v) == 0: v = 2 dec = int(v) uidx = UnitIndex(unit, precision=dec) -v = input("Source URI:") +v = input("Source URI: ") src = None if len(v) > 0: o = urllib.parse.urlparse(v) src = urllib.parse.urlunparse(o) +v = input("XML output filename (default: start.xml):") +fp = None +if len(v) == 0: + fp = os.path.join('.', 'start.xml') +fp = os.path.realpath(fp) + + ledger = Ledger(uidx, topic=topic, src=src) -print(ledger.to_string()) + + +db = ValkeyStore('') +store = LedgerStore(db, ledger) +pk = None +wallet = None +try: + pk = store.get_key() +except FileNotFoundError: + logg.info('no default key found') + wallet = DemoWallet() + store.add_key(wallet) +if wallet == None: + wallet = DemoWallet(privatekey=pk) + logg.info('loaded existing key. {}'.format(wallet.pubkey().hex())) + +logg.info('writing XML to file: {}'.format(fp)) +f = open(fp, 'wb') +f.write(ledger.to_string()) +f.close() diff --git a/dummy/usawa/store.py b/dummy/usawa/store.py @@ -16,8 +16,11 @@ PFX_ENTRY = b'\x04' logg = logging.getLogger('usawa.store') -def pfx_key(): - return PFX_KEY +def pfx_key(pubkey=None): + v = PFX_KEY + if pubkey == None: + return v + return v + pubkey def pfx_ledger_topic(topic): @@ -88,7 +91,6 @@ class LedgerStore(Interface): self.__o = implementation - # WIP implementation of couchdb? def start(self): serial = 0 k = pfx_ledger_topic(self.ledger.topic) @@ -101,7 +103,6 @@ class LedgerStore(Interface): self.ledger.serial = serial - # WIP implementation of couchdb? def lock(self): k = pfx_ledger_lock(self.ledger.topic) v = None @@ -114,7 +115,6 @@ class LedgerStore(Interface): # atomic until here - # WIP implementation of couchdb? def unlock(self): k = pfx_ledger_lock(self.ledger.topic) v = self.__o.delete(k) @@ -150,14 +150,54 @@ class LedgerStore(Interface): return Entry.unwrap(v, acl=acl) + """Flush ledger and load all entries from store. + + The existing state will always be lost. + + If the load fails, the ledger will be reset before returning. + + :raises FileNotFoundError: If an entry cannot be found. + """ def load(self): - logg.debug('load ledger {}'.format(self.ledger.acl)) + self.ledger.reset() + logg.debug('load ledger from store {}'.format(self.ledger)) while True: o = None try: o = self.get_entry(self.ledger.serial) except FileNotFoundError: + self.ledger.reset() break - logg.debug('entry {}'.format(o)) self.ledger.add_entry(o, modify_tree=True) self.ledger.next_serial() + + + """Add signing key to the store. + + If this is the first key in the store, it will be set as default. + + :param wallet: The wallet object to store keys for. + :type wallet: usawa.Wallet implementation + :param default: If True, this key will be set as default key. + :type default: bool + :todo: Currently the signing key is stored literally. It needs encryption! + """ + def add_key(self, wallet, default=False): + k = pfx_key() + try: + self.__o.get(k) + except FileNotFoundError: + default = True + pubkey = wallet.pubkey() + if default: + self.__o.put(k, pubkey, exist_ok=True) + k = pfx_key(pubkey=pubkey) + self.__o.put(k, wallet.privkey()) + + + def get_key(self, pubkey=None): + if pubkey == None: + k = pfx_key() + pubkey = self.__o.get(k) + k = pfx_key(pubkey=pubkey) + return self.__o.get(k)