usawa

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

commit 90c34c3c29df9f76ef5adee0411c0baac56f41f1
parent 1bb1b0af3f103de0604f893aa1e501f806f5af86
Author: lash <dev@holbrook.no>
Date:   Thu, 15 Jan 2026 14:48:35 +0000

Add schema verification test for ledger and entries

Diffstat:
Mdummy/requirements.txt | 2+-
Ddummy/schema.xsd | 149-------------------------------------------------------------------------------
Mdummy/tests/ledger.py | 38+++++++++++++++++++++++++++++++++++++-
Mdummy/usawa/__init__.py | 5+++++
Adummy/usawa/data/schema.xsd | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdummy/usawa/entry.py | 4++--
Mdummy/usawa/ledger.py | 17++++++++++++++---
7 files changed, 208 insertions(+), 156 deletions(-)

diff --git a/dummy/requirements.txt b/dummy/requirements.txt @@ -5,4 +5,4 @@ pycparser==2.23 PyNaCl==1.6.0 python-gnupg==0.4.9 rencode==1.0.8 -wheepy~=0.0.3 +wheepy[valkey]~=0.0.3 diff --git a/dummy/schema.xsd b/dummy/schema.xsd @@ -1,149 +0,0 @@ -<?xml version="1.0" ?> - -<xs:schema xmlns:xs = "http://www.w3.org/2001/XMLSchema" - targetNamespace = "http://usawa.defalsify.org/" - xmlns = "http://usawa.defalsify.org/" - elementFormDefault = "qualified"> - - <xs:element name="ledger"> - <xs:complexType> - <xs:sequence> - <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> - <xs:element name="topic" type="xs:string" /> <xs:element name="retrieved" type="xs:dateTime" /> <xs:element name="src" type="xs:string" minOccurs="1" maxOccurs="1" /> - <xs:element name="units" type="Units" minOccurs="1" maxOccurs="1" /> - <xs:element name="resolver" type="Resolver" minOccurs="0" maxOccurs="unbounded" /> - <xs:element name="identity" type="Identity" minOccurs="1" maxOccurs="unbounded" /> - <xs:element name="incoming" type="Incoming" minOccurs="0" maxOccurs="1" /> - <xs:element name="entry" type="Entry" minOccurs="0" maxOccurs="unbounded" /> - </xs:sequence> - <xs:attribute name="version" type="xs:integer" /> - </xs:complexType> - </xs:element> - - <xs:complexType name="Incoming"> - <xs:sequence> - <xs:element name="real" type="Balance" minOccurs="1" maxOccurs="unbounded" /> - <xs:element name="virt" type="Balance" minOccurs="0" maxOccurs="unbounded" /> - <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:complexType> - - <xs:complexType name="Balance"> - <xs:sequence> - <xs:element name="income" type="xs:integer" minOccurs="0"/> - <xs:element name="expense" type="xs:integer" minOccurs="0" /> - <xs:element name="asset" type="xs:integer" /> - <xs:element name="liability" type="xs:integer" /> - </xs:sequence> - <xs:attribute name="unit" type="xs:string" /> - </xs:complexType> - - <xs:complexType name="Units"> - <xs:sequence> - <xs:element name="unit" type="Unit" minOccurs="1" maxOccurs="unbounded" /> - </xs:sequence> - <xs:attribute name="base" type="xs:string" /> - </xs:complexType> - - <xs:complexType name="Digest"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute name="algo" type="xs:string" /> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - - <xs:complexType name="Signature"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute name="keyid" type="xs:string" /> - <xs:attribute name="type" type="xs:string" /> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - - <xs:complexType name="Unit"> - <xs:sequence> - <xs:element name="precision" type="xs:positiveInteger" maxOccurs="1" minOccurs="1" /> - <xs:choice> - <xs:element name="exchange" type="xs:positiveInteger" maxOccurs="1" minOccurs="1" /> - <xs:sequence> - <xs:element name="rate" type="Rate" minOccurs="1" maxOccurs="unbounded" /> - </xs:sequence> - </xs:choice> - </xs:sequence> - <xs:attribute name="sym" type="xs:string" /> - </xs:complexType> - - <xs:complexType name="Rate"> - <xs:sequence> - <xs:choice> - <xs:element name="at" type="xs:date" /> - <xs:element name="at" type="xs:dateTime" /> - </xs:choice> - <xs:element name="vs" type="xs:string" /> - <xs:element name="value" type="xs:integer" /> - </xs:sequence> - </xs:complexType> - - <xs:complexType name="Identity"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute name="keyid" type="xs:string" /> - <xs:attribute name="didtype" type="xs:string" /> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - - <xs:complexType name="Resolver"> - <xs:simpleContent> - <xs:extension base="xs:string"> - <xs:attribute name="algo" type="xs:string" /> - <xs:attribute name="proto" type="xs:string" /> - </xs:extension> - </xs:simpleContent> - </xs:complexType> - - <xs:complexType name="Entry"> - <xs:sequence> - <xs:element name="data" type="EntryData" /> - <xs:element name="sig" type="Signature" minOccurs="1" maxOccurs="unbounded" /> - </xs:sequence> - <!-- TODO: attribute enum --> - </xs:complexType> - - <xs:complexType name="EntryData"> - <xs:sequence> - <xs:element name="parent" type="xs:string" /> - <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> - <xs:element name="ref" type="xs:string" /> - <xs:element name="serial" type="xs:positiveInteger" /> - <xs:element name="unit" type="xs:string" /> - <xs:element name="date" type="xs:date" /> - <xs:element name="dateTimeRegistered" type="xs:dateTime" /> - <xs:element name="src" type="EntryPart"/> - <xs:element name="dst" type="EntryPart"/> - <xs:element name="attachment" type="Attachment" minOccurs="1" maxOccurs="unbounded" /> - </xs:sequence> - </xs:complexType> - - <xs:complexType name="EntryPart"> - <xs:sequence> - <xs:element name="account" type="xs:string" /> - <xs:element name="amount" type="xs:nonNegativeInteger" /> - </xs:sequence> - <xs:attribute name="type" type="xs:string" /> - </xs:complexType> - - <xs:complexType name="Attachment"> - <xs:sequence> - <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> - <xs:element name="slug" type="xs:string" /> - <xs:element name="description" type="xs:string" /> - <xs:element name="digest" type="Digest" /> - </xs:sequence> - <xs:attribute name="mime" type="xs:string" /> - </xs:complexType> -</xs:schema> diff --git a/dummy/tests/ledger.py b/dummy/tests/ledger.py @@ -7,7 +7,7 @@ import copy import lxml.etree from whee.mem import MemStore -from usawa import Ledger, UnitIndex, EntryPart, Entry, DemoWallet, ACL +from usawa import Ledger, UnitIndex, EntryPart, Entry, DemoWallet, ACL, schema_path from usawa.store import LedgerStore logging.basicConfig(level=logging.DEBUG) @@ -72,5 +72,41 @@ class TestLedger(unittest.TestCase): r = o.sign() + def test_validate(self): + s = 'FOO' + uidx = UnitIndex(s) + uidx.add('USD') + o = Ledger(uidx) + store = LedgerStore(self.store, ledger=o) + store.start() + + wallet = DemoWallet() + o.set_wallet(wallet) + x = EntryPart('income', 'foo', 1337, src=True) + y = EntryPart('asset', 'foo', 1337) + v = Entry(x, y, s, o.peek(), datetime.datetime.now(), parent=o.current()) + v.sign(wallet) + o.add_entry(v) + + x = EntryPart('expense', 'bar̈́', 42, src=True) + y = EntryPart('liability', 'bar', 42) + v = Entry(x, y, s, o.peek(), datetime.datetime.now(), parent=o.current()) + v.sign(wallet) + o.add_entry(v) + + o.sign() + v = o.to_string() + + logg.debug('schema checking xml string {}'.format(v)) + + f = open(schema_path, 'r') + b = f.read() + f.close() + o = lxml.etree.XML(b) + schema = lxml.etree.XMLSchema(o) + parser = lxml.etree.XMLParser(schema=schema) + lxml.etree.fromstring(v, parser) + + if __name__ == '__main__': unittest.main() diff --git a/dummy/usawa/__init__.py b/dummy/usawa/__init__.py @@ -1,3 +1,5 @@ +import os + from lxml import etree from .ledger import Ledger @@ -7,6 +9,9 @@ from .xml import nsmap from .unit import UnitIndex +data_dir = os.path.join(os.path.dirname(__file__), 'data') +schema_path = os.path.join(data_dir, 'schema.xsd') + def init_ledger(tree, units, acl=None): return Ledger.from_tree(tree, units, acl=acl) diff --git a/dummy/usawa/data/schema.xsd b/dummy/usawa/data/schema.xsd @@ -0,0 +1,149 @@ +<?xml version="1.0" ?> + +<xs:schema xmlns:xs = "http://www.w3.org/2001/XMLSchema" + targetNamespace = "http://usawa.defalsify.org/" + xmlns = "http://usawa.defalsify.org/" + elementFormDefault = "qualified"> + + <xs:element name="ledger"> + <xs:complexType> + <xs:sequence> + <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> + <xs:element name="topic" type="xs:string" /> <xs:element name="retrieved" type="xs:dateTime" /> <xs:element name="src" type="xs:string" minOccurs="1" maxOccurs="1" /> + <xs:element name="units" type="Units" minOccurs="1" maxOccurs="1" /> + <xs:element name="resolver" type="Resolver" minOccurs="0" maxOccurs="unbounded" /> + <xs:element name="identity" type="Identity" minOccurs="1" maxOccurs="unbounded" /> + <xs:element name="incoming" type="Incoming" minOccurs="0" maxOccurs="1" /> + <xs:element name="entry" type="Entry" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + <xs:attribute name="version" type="xs:integer" /> + </xs:complexType> + </xs:element> + + <xs:complexType name="Incoming"> + <xs:sequence> + <xs:element name="real" type="Balance" minOccurs="1" maxOccurs="unbounded" /> + <xs:element name="virt" type="Balance" minOccurs="0" maxOccurs="unbounded" /> + <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:complexType> + + <xs:complexType name="Balance"> + <xs:sequence> + <xs:element name="income" type="xs:integer" minOccurs="0"/> + <xs:element name="expense" type="xs:integer" minOccurs="0" /> + <xs:element name="asset" type="xs:integer" /> + <xs:element name="liability" type="xs:integer" /> + </xs:sequence> + <xs:attribute name="unit" type="xs:string" /> + </xs:complexType> + + <xs:complexType name="Units"> + <xs:sequence> + <xs:element name="unit" type="Unit" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + <xs:attribute name="base" type="xs:string" /> + </xs:complexType> + + <xs:complexType name="Digest"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="algo" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="Signature"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="keyid" type="xs:string" /> + <xs:attribute name="type" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="Unit"> + <xs:sequence> + <xs:element name="precision" type="xs:positiveInteger" maxOccurs="1" minOccurs="1" /> + <xs:choice> + <xs:element name="exchange" type="xs:positiveInteger" maxOccurs="1" minOccurs="1" /> + <xs:sequence> + <xs:element name="rate" type="Rate" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + </xs:choice> + </xs:sequence> + <xs:attribute name="sym" type="xs:string" /> + </xs:complexType> + + <xs:complexType name="Rate"> + <xs:sequence> + <xs:choice> + <xs:element name="at" type="xs:date" /> + <xs:element name="at" type="xs:dateTime" /> + </xs:choice> + <xs:element name="vs" type="xs:string" /> + <xs:element name="value" type="xs:integer" /> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="Identity"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="keyid" type="xs:string" /> + <xs:attribute name="didtype" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="Resolver"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="algo" type="xs:string" /> + <xs:attribute name="proto" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + + <xs:complexType name="Entry"> + <xs:sequence> + <xs:element name="data" type="EntryData" /> + <xs:element name="sig" type="Signature" minOccurs="1" maxOccurs="unbounded" /> + </xs:sequence> + <!-- TODO: attribute enum --> + </xs:complexType> + + <xs:complexType name="EntryData"> + <xs:sequence> + <xs:element name="parent" type="xs:string" /> + <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> + <xs:element name="ref" type="xs:string" /> + <xs:element name="serial" type="xs:positiveInteger" /> + <xs:element name="unit" type="xs:string" /> + <xs:element name="date" type="xs:date" /> + <xs:element name="dateTimeRegistered" type="xs:dateTime" /> + <xs:element name="src" type="EntryPart"/> + <xs:element name="dst" type="EntryPart"/> + <xs:element name="attachment" type="Attachment" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="EntryPart"> + <xs:sequence> + <xs:element name="account" type="xs:string" /> + <xs:element name="amount" type="xs:nonNegativeInteger" /> + </xs:sequence> + <xs:attribute name="type" type="xs:string" /> + </xs:complexType> + + <xs:complexType name="Attachment"> + <xs:sequence> + <xs:element name="uuid" type="xs:string" minOccurs="0" maxOccurs="1" /> + <xs:element name="slug" type="xs:string" /> + <xs:element name="description" type="xs:string" /> + <xs:element name="digest" type="Digest" /> + </xs:sequence> + <xs:attribute name="mime" type="xs:string" /> + </xs:complexType> +</xs:schema> diff --git a/dummy/usawa/entry.py b/dummy/usawa/entry.py @@ -398,8 +398,8 @@ class Entry: o.text = self.description data.append(o) - self.src.apply_tree(tree) - self.dst.apply_tree(tree) + self.src.apply_tree(data) + self.dst.apply_tree(data) tree.append(data) diff --git a/dummy/usawa/ledger.py b/dummy/usawa/ledger.py @@ -495,13 +495,24 @@ class Ledger: logg.debug('entryunit {} {}'.format(entry.unit, self.running[entry.unit])) + def apply_signature(self, identity): + sig = self.sigs[identity] + tree = self.tree.find('incoming', namespaces=nsmap()) + o = lxml.etree.SubElement(tree, NSPREFIX + 'sig', nsmap=nsmap()) + o.set('keyid', identity.hex()) + o.set('type', 'ed25519') + o.text = sig.hex() + + """Add a signature on the ledger. :todo: not an appropriate API function? :todo: implement validity checks for signature. """ - def add_signature(self, sigdata, identity): - self.sigs[identity] = sigdata + def add_signature(self, sigdata, identity, modify_tree=True): + self.sigs[identity] = sigdata + if modify_tree: + self.apply_signature(identity) logg.debug('add sig from key {}: {}'.format(identity.hex(), sigdata.hex())) @@ -532,7 +543,7 @@ class Ledger: for sig in part.iter(NSPREFIX + 'sig'): keyid = sig.get('keyid') digest = sig.text - r.add_signature(bytes.fromhex(digest), bytes.fromhex(keyid)) + r.add_signature(bytes.fromhex(digest), bytes.fromhex(keyid), modify_tree=False) o = part.find('real', namespaces=nsmap()) asset = int(o.find('asset', namespaces=nsmap()).text)