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