usawa

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

commit 6d993fcd46109de4caa6d0262fe52007a4ce51de
parent 8305afaa29dedbc12a7f8037816625a602c423f5
Author: lash <dev@holbrook.no>
Date:   Tue, 17 Feb 2026 11:27:28 +0000

Correct lookup and base entry state in ledger XML

Diffstat:
Mdummy/tests/ledger.py | 31+++++++++++++++++++++++++++++++
Mdummy/tests/resolver.py | 22++++++++++++++--------
Mdummy/usawa/entry.py | 20+++++++++++++++-----
Mdummy/usawa/ledger.py | 38+++++++++++++++++++++++++-------------
Mdummy/usawa/resolve/base.py | 13+++++++++++++
Mdummy/usawa/resolve/fs.py | 2+-
Mdummy/usawa/runnable/add.py | 4++--
Mdummy/usawa/runnable/import.py | 2+-
Mdummy/usawa/runnable/view.py | 2+-
9 files changed, 103 insertions(+), 31 deletions(-)

diff --git a/dummy/tests/ledger.py b/dummy/tests/ledger.py @@ -119,5 +119,36 @@ class TestLedger(unittest.TestCase): lxml.etree.fromstring(v, parser) + def test_ledger_truncate(self): + s = 'FOO' + uidx = UnitIndex(s) + uidx.add('USD') + ledger = Ledger(uidx) + store = LedgerStore(self.store, ledger=ledger) + store.start() + + wallet = DemoWallet() + ledger.set_wallet(wallet) + x = EntryPart(s, 'income', 'foo', 1337, debit=True) + y = EntryPart(s, 'asset', 'foo', 1337) + v = Entry(ledger.peek(), datetime.datetime.now(), parent=ledger.current()) + v.add_part(x, debit=True) + v.add_part(y) + v.sign(wallet) + ledger.add_entry(v) + + x = EntryPart(s, 'expense', 'bar̈́', 42, debit=True) + y = EntryPart(s, 'liability', 'bar', 42) + v = Entry(ledger.peek(), datetime.datetime.now(), parent=ledger.current()) + v.add_part(x, debit=True) + v.add_part(y) + v.sign(wallet) + ledger.add_entry(v) + ledger.sign() + + ledger.truncate() + self.assertEqual(ledger.serial, 2) + + if __name__ == '__main__': unittest.main() diff --git a/dummy/tests/resolver.py b/dummy/tests/resolver.py @@ -60,7 +60,7 @@ class TestResolver(unittest.TestCase): def test_resolve_lookup(self): uidx = UnitIndex('FOO') wallet = DemoWallet() - ledger = Ledger(uidx, wallet=wallet, topic=hash_of_foo.encode('utf-8')) + ledger = Ledger(uidx, wallet=wallet, topic=bytes.fromhex(hash_of_foo)) dst = EntryPart('FOO', 'asset', 'foo', 1337) src = EntryPart('FOO', 'income', 'foo', 1337, debit=True) entry = Entry(42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, tx_datereg=self.dtreg) @@ -68,6 +68,7 @@ class TestResolver(unittest.TestCase): entry.add_part(dst) entry.sign(wallet) ledger.add_entry(entry) + first_entry_key = self.backend.put_entry(entry, 'sha512') dst = EntryPart('FOO', 'expense', 'bar̈́', 42, debit=True) src = EntryPart('FOO', 'liability', 'bar', 42) @@ -76,18 +77,23 @@ class TestResolver(unittest.TestCase): entry.add_part(dst) entry.sign(wallet) ledger.add_entry(entry) + ledger.sign() + last_entry_key = self.backend.put_entry(entry, 'sha512') - #s = lxml.etree.tostring(tree) - ledger.truncate(lookup='sha512') tree = ledger.to_tree(lookup='sha512') s = lxml.etree.tostring(tree) - print(s.decode('utf-8')) + ledger = Ledger(uidx, wallet=wallet, topic=bytes.fromhex(hash_of_foo)) - for k in ledger.entries.keys(): - tree = ledger.entries[k].to_tree() - s = lxml.etree.tostring(tree) + k = self.backend.get(first_entry_key) + first_entry = Entry.from_string(k, uidx) + ledger.add_entry(first_entry) - ledger.truncate() + k = self.backend.get(last_entry_key) + last_entry = Entry.from_string(k, uidx) + ledger.add_entry(last_entry) + + tree = ledger.to_tree(lookup='sha512') + s_orig = lxml.etree.tostring(tree) if __name__ == '__main__': diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py @@ -94,7 +94,6 @@ class EntryPart: o = lxml.etree.Element('amount') o.text = str(self.amount) - logg.debug('tree amount {} {}'.format(self.unit, o.text)) tree.append(o) return tree @@ -302,6 +301,11 @@ class Entry: return entry + @staticmethod + def from_string(s, unitindex, min=0): + tree = lxml.etree.fromstring(s) + return Entry.from_tree(tree, unitindex, min=min) + """Generate the simple data structure used for rencode serialization. :returns: data structure @@ -586,14 +590,20 @@ class Entry: tree.append(o) if lookup: - v = self.get_lookup(lookup, tree=tree) + (k, v) = self.get_lookup(lookup, tree=tree) o = lxml.etree.Element('lookup') o.set('algo', lookup) - o.text = v.hex + o.text = v data.append(o) return tree + + def to_string(self, canon=False, lookup=None): + tree = self.to_tree(canon=canon, lookup=lookup) + return lxml.etree.tostring(tree).decode('utf-8') + + def get_lookup(self, lookup, tree=None): if tree == None: tree = self.to_tree(lookup=False) @@ -605,10 +615,10 @@ class Entry: else: raise ValueError('invalid lookup algo') - b = lxml.etree.canonicalize(tree, strip_text="True") + b = lxml.etree.canonicalize(tree, strip_text="True", exclude_tags=['lookup']) h.update(b.encode('utf-8')) - return h.digest().hex() + return (h.digest().hex(), b,) """Generate canonical XML for signature material. diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py @@ -141,6 +141,7 @@ class RunningTotal: + """Generate an XML tree from the current state of the object. The XML generated can be used as a "real" or "virt" sub-element of the ledger/incoming/ element. @@ -328,8 +329,8 @@ class Ledger: :todo: deduplicate signature from wallet identity if already exists """ def to_tree(self, lookup=None): - self.serial = self.base_serial - self.cur = self.base + #self.serial = self.base_serial + #self.cur = self.base tree = lxml.etree.XML('<ledger xmlns="http://usawa.defalsify.org/" version="{}"></ledger>'.format(XML_FORMAT_VERSION)) # generate topic @@ -368,7 +369,7 @@ class Ledger: incoming = lxml.etree.SubElement(tree, 'incoming') # incoming serial - incoming.set('serial', str(self.serial)) + incoming.set('serial', str(self.base_serial)) # incoming base (real) currency balance o = self.running[self.uidx.base].to_tree() @@ -401,6 +402,8 @@ class Ledger: # apply all already existing signatures (e.g. from import) for k in self.sigs.keys(): sig = self.sigs[k] + if len(sig) == 0: + continue o = lxml.etree.SubElement(incoming, 'sig') o.set('keyid', k.hex()) o.set('type', 'ed25519') @@ -550,7 +553,7 @@ class Ledger: public_key = bytes.fromhex(keyid) wallet = DemoWallet(publickey=public_key) ledger.set_wallet(wallet) - logg.warn('currently only support for single identity') + logg.warning('currently only support for single identity') break o = part.find('real', namespaces=nsmap()) @@ -570,10 +573,10 @@ class Ledger: if ledger.running.get(unit) == None: ledger.running[unit] = RunningTotal(unit, unitindex) - o = part.find('lookup') + o = part.find('lookup', namespaces=nsmap()) if o != None: - self.lookup = o.text - self.lookup_algo = o.get('algo') + ledger.lookup = o.text + ledger.lookup_algo = o.get('algo') ledger.apply_entries(tree) logg.debug('loaded ledger tree last serial {}'.format(ledger.serial)) @@ -582,12 +585,11 @@ class Ledger: @staticmethod - def from_file(filepath): + def from_file(filepath, acl=None): f = open(filepath, 'rb') v = f.read() f.close() - tree = lxml.etree.fromstring(v) - return Ledger.from_tree(tree) + return Ledger.from_string(v, acl=acl) @@ -628,12 +630,16 @@ class Ledger: def truncate(self, lookup=None): self.base = self.cur self.base_serial = self.serial + logg.debug('base serial now {}'.format(self.base_serial)) try: entry = self.last_entry() except KeyError: # if no entries + self.entries = {} return - self.lookup = entry.get_lookup(lookup) - self.lookup_algo = lookup + if lookup != None: + (k, v) = entry.get_lookup(lookup) + self.lookup = k + self.lookup_algo = lookup self.entries = {} @@ -652,7 +658,13 @@ class Ledger: """ def to_string(self, lookup=None): tree = self.to_tree(lookup=lookup) - return lxml.etree.tostring(tree) + return lxml.etree.tostring(tree).decode('utf-8') + + + @staticmethod + def from_string(s, acl=None): + tree = lxml.etree.fromstring(s) + return Ledger.from_tree(tree, acl=acl) """Returns the digest of the current state of the ledger. diff --git a/dummy/usawa/resolve/base.py b/dummy/usawa/resolve/base.py @@ -1,9 +1,12 @@ import hashlib +import logging import hexathon from usawa.error import VerifyError +logg = logging.getLogger('usawa.resolve') + """Verifies a key as a sha512 digest, optionally against the given value. @@ -90,3 +93,13 @@ class BaseResolver: """ def state(self, k): raise NotImplementedError() + + + def put_entry(self, entry, lookup=None): + k = None + (k, v) = entry.sum() + self.put(k, v) + if lookup != None: + (k, v) = entry.get_lookup(lookup) + self.put(k, v.encode('utf-8')) + return k diff --git a/dummy/usawa/resolve/fs.py b/dummy/usawa/resolve/fs.py @@ -52,7 +52,7 @@ class FSResolver(BaseResolver): fp = os.path.join(self.path, khx) f = open(fp, 'wb') c = f.write(v) - logg.debug('{} bytes written for key {}'.format(c, k)) + logg.debug('{} bytes written for key {}'.format(c, khx)) f.close() return k diff --git a/dummy/usawa/runnable/add.py b/dummy/usawa/runnable/add.py @@ -40,10 +40,10 @@ class Context: def open(self, output): if output == '<stdout>': - self.f = sys.stdout.buffer + self.f = sys.stdout logg.debug('output is stdout') else: - self.f = open(output, 'wb') + self.f = open(output, 'w') return self diff --git a/dummy/usawa/runnable/import.py b/dummy/usawa/runnable/import.py @@ -70,4 +70,4 @@ store = LedgerStore(storedb, ledger) #acl = ACL.from_wallet(wallet) #store.load(acl=acl) store.put_all(store_assets=True) -sys.stdout.buffer.write(ledger.to_string()) +sys.stdout.write(ledger.to_string()) diff --git a/dummy/usawa/runnable/view.py b/dummy/usawa/runnable/view.py @@ -72,4 +72,4 @@ pk = store.get_key() wallet = DemoWallet(privatekey=pk) acl = ACL.from_wallet(wallet) store.load(acl=acl) -sys.stdout.buffer.write(ledger.to_string()) +sys.stdout.write(ledger.to_string())