usawa

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

commit 03ef1626d39f8bced8eb79ee82a2b9041b0d8c52
parent 4fd0bf5db3a41814259960ba41af22bdae0e09f4
Author: lash <dev@holbrook.no>
Date:   Sat, 17 Jan 2026 12:10:37 +0000

Correct serial and state for ledger for viewing entries

Diffstat:
Mdummy/tests/store.py | 16+++++++++-------
Mdummy/usawa/data/schema.xsd | 2+-
Mdummy/usawa/entry.py | 26+++++++++++++++++++-------
Mdummy/usawa/error.py | 4++++
Mdummy/usawa/ledger.py | 10+++++++---
Mdummy/usawa/runnable/add.py | 14++++++++------
Mdummy/usawa/store.py | 5++---
7 files changed, 50 insertions(+), 27 deletions(-)

diff --git a/dummy/tests/store.py b/dummy/tests/store.py @@ -48,14 +48,15 @@ class TestStore(unittest.TestCase): def test_store_ledger(self): uidx = UnitIndex('FOO') wallet = DemoWallet() - acl = ACL() - acl.add(wallet.pubkey()) - ledger = Ledger(uidx, serial=42, base=self.parent) + #acl = ACL() + #acl.add(wallet.pubkey()) + acl = ACL.from_wallet(wallet) + ledger = Ledger(uidx, serial=41, base=self.parent, acl=acl, wallet=wallet) store = LedgerStore(self.store, ledger) dst = EntryPart('FOO', 'asset', 'foo', 1337) src = EntryPart('FOO', 'income', 'foo', 1337, debit=True) - o = Entry(42, datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg, unitindex=uidx) + o = Entry(ledger.next_serial(), datetime.datetime.strptime('2025-11-11', '%Y-%m-%d'), parent=self.parent, ref=self.ref, description=self.description, tx_datereg=self.dtreg, unitindex=uidx) o.add_part(src, debit=True) o.add_part(dst) o.sign(wallet) @@ -67,13 +68,14 @@ class TestStore(unittest.TestCase): dtreg = datetime.datetime.now() dst = EntryPart('FOO', 'expense', 'bar', 4200) src = EntryPart('FOO', 'liability', 'bar', 4200, debit=True) - o = Entry(43, datetime.datetime.strptime('2025-11-12', '%Y-%m-%d'), parent=parent, ref=ref, description=description, tx_datereg=dtreg, unitindex=uidx) + o = Entry(ledger.next_serial(), datetime.datetime.strptime('2025-11-12', '%Y-%m-%d'), parent=parent, ref=ref, description=description, tx_datereg=dtreg, unitindex=uidx) o.add_part(src, debit=True) o.add_part(dst) o.sign(wallet) store.add_entry(o) - - ledger = Ledger(uidx, serial=42, base=self.parent, acl=acl, topic=ledger.topic) + + ledger.sign() + #ledger.reset() store.load() diff --git a/dummy/usawa/data/schema.xsd b/dummy/usawa/data/schema.xsd @@ -27,7 +27,7 @@ <xs:element name="digest" type="Digest" minOccurs="1" maxOccurs="1"/> <xs:element name="sig" type="Signature" minOccurs="1" maxOccurs="unbounded" /> </xs:sequence> - <xs:attribute name="serial" type="xs:positiveInteger" /> + <xs:attribute name="serial" type="xs:nonNegativeInteger" /> </xs:complexType> <xs:complexType name="Balance"> diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py @@ -107,12 +107,8 @@ class Entry: If parent is not specified, the entry will be expected to be the first entry in the ledger, and the serial number must be zero. - :param src: The account debited, as a path-like string. - :type src: str - :param dst: The account credited, as a path-like string. - :type dst: str - :param unit: The symbol of the unit of account for the transation. - :type dst: str + If unitindex is not specified, unit symbols in entry parts will not be validated as they are added. + :param serial: The entry's serial number in the ledger. :type serial: int :param tx_date: The date of the transaction. @@ -125,6 +121,8 @@ class Entry: :type parent: str :param tx_datereg: Date and time entry was added to the ledger. If not set, the current date and time will be used. :type tx_datereg: datetime.datetime + :param unitindex: Unix index top validate unit symbols and exchange rates against. + :type unit: UnitIndex :todo: Add an optional time part to the entry, e.g. for POS items. :todo: Implement throw error if non-zero serial has zero-value digest. :todo: Check hashlen of parent against actual digest length defined in digest_algo. @@ -154,6 +152,16 @@ class Entry: self.credit = [] + """Add an entry part to the entry. + + At least one debit and one credit item must be added to be valid. + + :param part: Entry part to add + :type part: EntryPart + :param debit: If true, entry part will be added to debits. If false, credits. + :type debit: boolean + :raises KeyError: Symbol does not exist in unit index. + """ def add_part(self, part, debit=False): if self.uidx != None: self.uidx.sym(part.unit) @@ -183,11 +191,13 @@ class Entry: """Add a signature over the ledger state of the entry. :param keyid: Key identifier, e.g. as used in a DID. - :type keyid: str + :type keyid: str or bytes :param sigdata: Key identifier, e.g. as used in a DID. :type keyid: bytes """ def add_signature(self, keyid, sigdata): + if isinstance(keyid, bytes): + keyid = keyid.hex() self.sigs[keyid] = sigdata @@ -355,6 +365,7 @@ class Entry: sigs, data, ] + return rencode.dumps(d) @@ -387,6 +398,7 @@ class Entry: wallet = DemoWallet(publickey=pubkey_bytes) # TODO: demo only takes into account single signature sig = v[1][0] + logg.debug('our sig {} {}'.format(pubkey_bytes.hex(), sig.hex())) entry = Entry.deserialize(v[2]) (z, b) = entry.sum() if not wallet.verify(z, sig): diff --git a/dummy/usawa/error.py b/dummy/usawa/error.py @@ -4,3 +4,7 @@ class ACLError(Exception): class VerifyError(Exception): pass + + +class ValidateError(Exception): + pass diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py @@ -166,7 +166,7 @@ class Ledger: :todo: Add warnings for ignored parameters """ - def __init__(self, unitindex, tree=None, acl=None, serial=1, base=DEFAULTPARENT, topic=None, src=None, wallet=None): + def __init__(self, unitindex, tree=None, acl=None, serial=0, base=DEFAULTPARENT, topic=None, src=None, wallet=None): self.uidx = unitindex self.sigs = {} self.entries = {} @@ -618,12 +618,16 @@ class Ledger: """ def truncate(self, modify_tree=True): self.base = self.cur - self.serial_base = self.serial + self.base_serial = self.serial if not modify_tree: return inc_tree = self.tree.find('incoming', namespaces=nsmap()) + inc_tree.set('serial', str(self.base_serial)) + o = inc_tree.find('digest', namespaces=nsmap()) + o.text = self.base.hex() + # xpath does not support empty namespace names ns = {'ns': nsmap()[None]} for k in self.running: @@ -727,4 +731,4 @@ class Ledger: def __str__(self): - return "state: " + self.base.hex() + return "state: " + self.base.hex() + " serial " + str(self.serial) diff --git a/dummy/usawa/runnable/add.py b/dummy/usawa/runnable/add.py @@ -155,13 +155,12 @@ def do_interactive(ctx): v = input_or_default('Entry {} account'.format(k), o[k][1]) o[k][1] = parse_account(v) - if amount != None: - amount *= -1 - else: + if amount == None: v = input_or_default('Entry {} amount'.format(k), ctx.amount) amount = parse_amount(uidx, ctx.unit, v) + amount *= -1 - ctx.part.append(EntryPart(o[k][0], o[k][1], amount, src=k=='src')) + ctx.part.append(EntryPart(ctx.unit, o[k][0], o[k][1], amount, debit=k=='src')) ctx.ref = input_or_default('External ref', ctx.ref) @@ -174,11 +173,14 @@ if arg.i: ctx = do_interactive(ctx) ctx.validate() -entry = Entry(ctx.part[0], ctx.part[1], ctx.unit, ledger.next_serial(), dt, parent=ledger.current(), description=ctx.description, ref=ctx.ref) +entry = Entry(ledger.next_serial(), dt, parent=ledger.current(), description=ctx.description, ref=ctx.ref, unitindex=ctx.uidx) +entry.add_part(ctx.part[0], debit=True) +entry.add_part(ctx.part[1]) entry.sign(wallet) +logg.debug('storing entry {}'.format(entry)) store.add_entry(entry) ledger.add_entry(entry, modify_tree=True) -ledger.truncate() +ledger.truncate(modify_tree=True) ledger.sign() ctx.f.write(ledger.to_string()) ctx.close() diff --git a/dummy/usawa/store.py b/dummy/usawa/store.py @@ -62,7 +62,7 @@ def pfx_entry(ledger, entry): :returns: Prefix. :rtype: bytes """ - serial = -1 + serial = 0 if isinstance(entry, Entry): serial = entry.serial elif isinstance(entry, int): @@ -164,11 +164,10 @@ class LedgerStore(Interface): while True: o = None try: - o = self.get_entry(self.ledger.serial) + o = self.get_entry(self.ledger.next_serial()) except FileNotFoundError: break self.ledger.add_entry(o, modify_tree=True) - self.ledger.next_serial() """Add signing key to the store.