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:
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())