From d72c714f81013aee2ad3b9472ee4fa3dc96a5571 Mon Sep 17 00:00:00 2001 From: timotheyca Date: Tue, 12 Jul 2022 14:28:56 +0300 Subject: [PATCH] flow trace --- plot.py | 6 +- .../trees/binary/actions/__init__.py | 4 +- .../trees/binary/actions/stdactions.py | 67 ++++- .../trees/binary/activebinarytree.py | 11 + rainbowadn/core/__init__.py | 4 +- rainbowadn/core/gather.py | 47 ++- rainbowadn/core/hashpointformat.py | 12 +- rainbowadn/flow/primitive/__init__.py | 2 + rainbowadn/flow13/__init__.py | 12 +- rainbowadn/flow13/_bankblock.py | 63 ++++ ...{_flowbankverification.py => _bankflow.py} | 67 +++-- rainbowadn/flow13/_flowbank.py | 49 ++- rainbowadn/flow13/_flowblock.py | 182 +++++++++++ rainbowadn/flow13/_flowcheque.py | 229 +++++++++----- rainbowadn/flow13/_flowiterate.py | 29 ++ rainbowadn/flow13/_flowstandard.py | 89 +++++- rainbowadn/flow13/_flowtransaction.py | 282 ++++++++++-------- rainbowadn/flow13/_flowunion.py | 11 + rainbowadn/testing/test_bridge.py | 54 +++- rainbowadn/testing/test_trees.py | 25 ++ rainbowadn/v13/signature.py | 4 + trace.py => trace_chain.py | 78 +---- trace_common.py | 76 +++++ trace_flow.py | 107 +++++++ 24 files changed, 1208 insertions(+), 302 deletions(-) create mode 100644 rainbowadn/flow13/_bankblock.py rename rainbowadn/flow13/{_flowbankverification.py => _bankflow.py} (61%) create mode 100644 rainbowadn/flow13/_flowblock.py create mode 100644 rainbowadn/flow13/_flowiterate.py create mode 100644 rainbowadn/flow13/_flowunion.py rename trace.py => trace_chain.py (66%) create mode 100644 trace_common.py create mode 100644 trace_flow.py diff --git a/plot.py b/plot.py index 6efc191..62d9378 100644 --- a/plot.py +++ b/plot.py @@ -23,7 +23,7 @@ def plot(fn: str): plt.ylabel('concurrency (1)') with open(fn) as file: - jsonified: dict = json.load(file) + jsonified: dict[str] = json.load(file) def logplot(plot_function, metric: str, **kwargs): if (log := jsonified.pop(metric, None)) is not None: @@ -35,12 +35,14 @@ def plot(fn: str): logplot(plt.plot, 'Stack:list:concurrency') logplot(plt.scatter, 'ActiveBinaryTree:add:entry', c='tomato', zorder=100, s=.5) logplot(plt.scatter, 'ActiveBinaryTree:add:exit', c='gold', zorder=99, s=.5) + plt.legend() plt.show() if __name__ == '__main__': - plot('trace/latest.json') + if Path('trace/latest.json').exists(): + plot('trace/latest.json') for fp in list(Path('trace').glob('*.json')): if fp != Path('trace/latest.json'): plot(str(fp)) diff --git a/rainbowadn/collection/trees/binary/actions/__init__.py b/rainbowadn/collection/trees/binary/actions/__init__.py index 39563ff..0fabb60 100644 --- a/rainbowadn/collection/trees/binary/actions/__init__.py +++ b/rainbowadn/collection/trees/binary/actions/__init__.py @@ -1,11 +1,11 @@ __all__ = ( 'BinaryAction', 'CompareAction', - 'AddAction', 'RemoveAction', 'ContainsAction', + 'AddAction', 'RemoveAction', 'ContainsAction', 'SplitAction', 'UnionAction', 'Symmetric', 'InnerOuter', 'OuterInner', ) from .binaryaction import BinaryAction from .compareaction import CompareAction -from .stdactions import AddAction, ContainsAction, RemoveAction +from .stdactions import AddAction, ContainsAction, RemoveAction, SplitAction, UnionAction from .symmetric import InnerOuter, OuterInner, Symmetric diff --git a/rainbowadn/collection/trees/binary/actions/stdactions.py b/rainbowadn/collection/trees/binary/actions/stdactions.py index a7619d2..bf62f42 100644 --- a/rainbowadn/collection/trees/binary/actions/stdactions.py +++ b/rainbowadn/collection/trees/binary/actions/stdactions.py @@ -2,10 +2,11 @@ from typing import Generic, TypeVar from rainbowadn.collection.comparison import * from rainbowadn.collection.trees.binary.core import * +from rainbowadn.core import * from .binaryaction import * from .compareaction import * -__all__ = ('AddAction', 'RemoveAction', 'ContainsAction',) +__all__ = ('AddAction', 'RemoveAction', 'ContainsAction', 'SplitAction', 'UnionAction',) TreeType = TypeVar('TreeType') ActiveKeyType = TypeVar('ActiveKeyType') @@ -180,3 +181,67 @@ class ContainsAction( ) -> bool: assert isinstance(protocolized, BinaryProtocolized) return False + + +class SplitAction( + CompareAction[ + ActiveKeyType, + MetaDataType, + TreeType, + tuple[TreeType, TreeType] + ], + Generic[ActiveKeyType, MetaDataType, TreeType] +): + async def on_equal( + self, case: ProtocolizedBinarySplit[ActiveKeyType, MetaDataType, TreeType], equal: Equal + ) -> tuple[TreeType, TreeType]: + if isinstance(equal, Replace): + return case.split.treel, case.split.treer + else: + raise TypeError + + async def on_left( + self, case: ProtocolizedBinarySplit[ActiveKeyType, MetaDataType, TreeType] + ) -> tuple[TreeType, TreeType]: + ll, lr = await self.on(case.protocolizedl()) + return ll, await case.protocol.tree(lr, case.split.treer, case.split.key) + + async def on_right( + self, case: ProtocolizedBinarySplit[ActiveKeyType, MetaDataType, TreeType] + ) -> tuple[TreeType, TreeType]: + rl, rr = await self.on(case.protocolizedr()) + return await case.protocol.tree(case.split.treel, rl, case.split.key), rr + + async def on_null( + self, protocolized: BinaryProtocolized[ActiveKeyType, MetaDataType, TreeType] + ) -> tuple[TreeType, TreeType]: + return protocolized.tree, protocolized.tree + + +class UnionAction( + BinaryAction[ + ActiveKeyType, + MetaDataType, + TreeType, + TreeType + ], + Generic[ActiveKeyType, MetaDataType, TreeType] +): + def __init__(self, protocolized: BinaryProtocolized[ActiveKeyType, MetaDataType, TreeType]): + assert isinstance(protocolized, BinaryProtocolized) + self.protocolized = protocolized + + async def on_null(self, protocolized: BinaryProtocolized[ActiveKeyType, MetaDataType, TreeType]) -> TreeType: + return self.protocolized.tree + + async def on_split(self, case: ProtocolizedBinarySplit[ActiveKeyType, MetaDataType, TreeType]) -> TreeType: + treel: TreeType + treer: TreeType + treel, treer = await SplitAction(case.split.key).on(self.protocolized) + unionl: TreeType + unionr: TreeType + unionl, unionr = await gather( + UnionAction(case.protocolizedl()).on(BinaryProtocolized(case.protocol, treel)), + UnionAction(case.protocolizedr()).on(BinaryProtocolized(case.protocol, treer)), + ) + return await case.protocol.tree(unionl, unionr, case.split.key) diff --git a/rainbowadn/collection/trees/binary/activebinarytree.py b/rainbowadn/collection/trees/binary/activebinarytree.py index 5209fbe..9c1bffc 100644 --- a/rainbowadn/collection/trees/binary/activebinarytree.py +++ b/rainbowadn/collection/trees/binary/activebinarytree.py @@ -82,6 +82,17 @@ class ActiveBinaryTree( assert isinstance(key, HashPoint) return await ContainsAction(key).on(self.protocolized()) + async def split(self, key: HashPoint[ActiveKeyType]) -> tuple[ + 'ActiveBinaryTree[ActiveKeyType, MetaDataType]', + 'ActiveBinaryTree[ActiveKeyType, MetaDataType]', + ]: + return await SplitAction(key).on(self.protocolized()) + + async def union( + self, other: 'ActiveBinaryTree[ActiveKeyType, MetaDataType]' + ) -> 'ActiveBinaryTree[ActiveKeyType, MetaDataType]': + return await UnionAction(other.protocolized()).on(self.protocolized()) + def loose(self) -> CollectionInterface[ BinaryTree[KeyMetadata[ActiveKeyType, MetaDataType]] ]: diff --git a/rainbowadn/core/__init__.py b/rainbowadn/core/__init__.py index 14e0f88..885a760 100644 --- a/rainbowadn/core/__init__.py +++ b/rainbowadn/core/__init__.py @@ -2,7 +2,7 @@ __all__ = ( 'assert_true', 'assert_trues', 'assert_false', 'assert_none', 'assert_none_strict', 'assert_eq', 'ExtendableResolver', 'gather', 'asum', 'alist', 'set_gather_asyncio', 'set_gather_linear', - 'hash_point_format', 'tabulate', + 'hash_point_format', 'tabulate', 'enable_newline', 'disable_newline', 'HashPoint', 'HashResolver', 'LocalMetaOrigin', @@ -21,7 +21,7 @@ from .asserts import assert_eq, assert_false, assert_none, assert_none_strict, a from .extendableresolver import ExtendableResolver from .gather import alist, asum, gather, set_gather_asyncio, set_gather_linear from .hashpoint import HashPoint -from .hashpointformat import hash_point_format, tabulate +from .hashpointformat import disable_newline, enable_newline, hash_point_format, tabulate from .hashresolver import HashResolver from .localmetaorigin import LocalMetaOrigin from .localorigin import LocalOrigin diff --git a/rainbowadn/core/gather.py b/rainbowadn/core/gather.py index 373a10f..6bfd3ad 100644 --- a/rainbowadn/core/gather.py +++ b/rainbowadn/core/gather.py @@ -1,5 +1,5 @@ import asyncio -from typing import AsyncIterable, TypeVar +from typing import Any, AsyncIterable, Awaitable, Coroutine, TypeVar, overload __all__ = ('gather', 'asum', 'alist', 'set_gather_asyncio', 'set_gather_linear',) @@ -20,6 +20,51 @@ def set_gather_linear(): _gather = _local_gather +T0 = TypeVar('T0') +T1 = TypeVar('T1') +T2 = TypeVar('T2') +T3 = TypeVar('T3') +T4 = TypeVar('T4') + + +@overload +def gather( + a0: Coroutine[Any, Any, T0], + a1: Coroutine[Any, Any, T1], +) -> Awaitable[tuple[T0, T1]]: ... + + +@overload +def gather( + a0: Coroutine[Any, Any, T0], + a1: Coroutine[Any, Any, T1], + a2: Coroutine[Any, Any, T2], +) -> Awaitable[tuple[T0, T1, T2]]: ... + + +@overload +def gather( + a0: Coroutine[Any, Any, T0], + a1: Coroutine[Any, Any, T1], + a2: Coroutine[Any, Any, T2], + a3: Coroutine[Any, Any, T3], +) -> Awaitable[tuple[T0, T1, T2, T3]]: ... + + +@overload +def gather( + a0: Coroutine[Any, Any, T0], + a1: Coroutine[Any, Any, T1], + a2: Coroutine[Any, Any, T2], + a3: Coroutine[Any, Any, T3], + a4: Coroutine[Any, Any, T4], +) -> Awaitable[tuple[T0, T1, T2, T3, T4]]: ... + + +@overload +def gather(*args): ... + + def gather(*args): return _gather(*args) diff --git a/rainbowadn/core/hashpointformat.py b/rainbowadn/core/hashpointformat.py index 4e3e98b..3cdcc7d 100644 --- a/rainbowadn/core/hashpointformat.py +++ b/rainbowadn/core/hashpointformat.py @@ -2,7 +2,7 @@ from .hashpoint import * from .mentionable import * from .recursivementionable import * -__all__ = ('hash_point_format', 'tabulate',) +__all__ = ('hash_point_format', 'tabulate', 'enable_newline', 'disable_newline',) async def hash_point_format(hash_point: HashPoint, tab: int) -> str: @@ -19,6 +19,16 @@ async def hash_point_format(hash_point: HashPoint, tab: int) -> str: newline = False +def enable_newline(): + global newline + newline = True + + +def disable_newline(): + global newline + newline = False + + def tabulate(tab: int) -> str: assert isinstance(tab, int) if newline: diff --git a/rainbowadn/flow/primitive/__init__.py b/rainbowadn/flow/primitive/__init__.py index 4e3c7f7..47fb1ba 100644 --- a/rainbowadn/flow/primitive/__init__.py +++ b/rainbowadn/flow/primitive/__init__.py @@ -1,5 +1,7 @@ __all__ = ( + 'ConstMapper', 'UnitReducer', ) +from ._constmapper import ConstMapper from ._unitreducer import UnitReducer diff --git a/rainbowadn/flow13/__init__.py b/rainbowadn/flow13/__init__.py index 37f3956..df5575f 100644 --- a/rainbowadn/flow13/__init__.py +++ b/rainbowadn/flow13/__init__.py @@ -1,3 +1,13 @@ -__all__ = ('FlowStandard',) +__all__ = ( + 'BankBlock', + 'FlowCheque', + 'FlowIterate', + 'FlowStandard', + 'FlowCoinData', 'FlowCoin', 'FlowTransactionData', 'FlowTransaction', +) +from ._bankblock import BankBlock +from ._flowcheque import FlowCheque +from ._flowiterate import FlowIterate from ._flowstandard import FlowStandard +from ._flowtransaction import FlowCoin, FlowCoinData, FlowTransaction, FlowTransactionData diff --git a/rainbowadn/flow13/_bankblock.py b/rainbowadn/flow13/_bankblock.py new file mode 100644 index 0000000..e1014ca --- /dev/null +++ b/rainbowadn/flow13/_bankblock.py @@ -0,0 +1,63 @@ +from typing import TypeAlias + +from rainbowadn.collection.pair import * +from rainbowadn.core import * +from rainbowadn.flow.verification.core import * +from rainbowadn.nullability import * +from ._bankflow import * +from ._flowbank import * +from ._flowblock import * +from ._flowcheque import * +from ._flowstandard import * + +__all__ = ('BankBlock',) + +Index: TypeAlias = FlowStandard[FlowBlock[Pair[FlowCheque, FlowBank]]] + + +class BankBlock: + def __init__(self, reference: NullableReference[FlowBlock[Pair[FlowCheque, FlowBank]]]): + assert isinstance(reference, NullableReference) + self.reference = reference + + @classmethod + def flow(cls) -> 'BankFlow': + return BankFlow(FlowBank.empty()) + + @classmethod + def link_factory(cls) -> RainbowFactory[Pair[FlowCheque, FlowBank]]: + return PairFactory(FlowCheque.factory(), FlowBank.factory()).loose() + + @classmethod + def empty(cls) -> 'BankBlock': + return cls( + NullableReference( + Null(), + FlowBlockFactory(cls.link_factory()).loose() + ) + ) + + @classmethod + def verification(cls) -> Verification[Index]: + return FlowBlockVerification(cls.flow().link_verification()).loose() + + async def verify(self) -> bool: + assert_true(await self.verification().verify(await FlowBlock.outer_of(self.link_factory(), self.reference))) + return True + + async def add(self, cheque: FlowCheque) -> 'BankBlock': + assert isinstance(cheque, FlowCheque) + previous_link: Nullable[Pair[FlowCheque, FlowBank]] = await FlowBlock.link_of(self.reference) + assert isinstance(previous_link, Nullable) + previous_bank: Nullable[FlowBank] + if previous_link.null(): + previous_bank = Null() + else: + previous_bank = NotNull(await (previous_link.resolve()).element1.resolve()) + assert isinstance(previous_link, Nullable) + bank: FlowBank = await self.flow().add(previous_bank, cheque) + assert isinstance(bank, FlowBank) + link: Pair[FlowCheque, FlowBank] = Pair(HashPoint.of(cheque), HashPoint.of(bank)) + block: FlowBlock[Pair[FlowCheque, FlowBank]] = await FlowBlock.add_to(self.reference, HashPoint.of(link)) + assert isinstance(block, FlowBlock) + return BankBlock(NullableReference.off(block)) diff --git a/rainbowadn/flow13/_flowbankverification.py b/rainbowadn/flow13/_bankflow.py similarity index 61% rename from rainbowadn/flow13/_flowbankverification.py rename to rainbowadn/flow13/_bankflow.py index 032e8aa..7f846d0 100644 --- a/rainbowadn/flow13/_flowbankverification.py +++ b/rainbowadn/flow13/_bankflow.py @@ -1,17 +1,21 @@ +from rainbowadn.collection.pair import * from rainbowadn.core import * from rainbowadn.flow.bridge import * +from rainbowadn.flow.core import * from rainbowadn.flow.primitive import * from rainbowadn.flow.verification.core import * +from rainbowadn.flow.verification.stateverification import * from rainbowadn.nullability import * from ._flowbank import * from ._flowcheque import * from ._flowstandard import * from ._flowtransaction import * +from ._flowunion import * -__all__ = ('FlowBankVerification',) +__all__ = ('BankFlow',) -class FlowBankVerification( +class BankFlow( Verification[tuple[Nullable[FlowBank], FlowCheque, FlowBank]], ): def __init__(self, initial: FlowBank): @@ -56,36 +60,28 @@ class FlowBankVerification( assert isinstance(bank, FlowBank) async def verify_unique_minted(): - previous_minted: FlowStandard[FlowCoin] = await previous.minted() - cheque_minted: FlowStandard[FlowCoin] = await cheque.minted() - bank_minted: FlowStandard[FlowCoin] = await bank.minted() assert_true( await cls._verify_disjoint_union( - previous_minted, - cheque_minted, - bank_minted, + previous.minted, + cheque.minted, + bank.minted, ) ) return True async def verify_unique_used(): - previous_used: FlowStandard[FlowCoin] = await previous.used() - cheque_used: FlowStandard[FlowCoin] = await cheque.used() - bank_used: FlowStandard[FlowCoin] = await bank.used() assert_true( await cls._verify_disjoint_union( - previous_used, - cheque_used, - bank_used, + previous.used, + cheque.used, + bank.used, ) ) return True async def verify_used_were_minted(): - bank_minted: FlowStandard[FlowCoin] = await bank.minted() - cheque_used: FlowStandard[FlowCoin] = await cheque.used() assert_true( - await cheque_used.verify_subset(UnitReducer(bank_minted)) + await cheque.used.verify_subset(UnitReducer(bank.minted)) ) return True @@ -119,3 +115,40 @@ class FlowBankVerification( assert isinstance(previous_bank, FlowBank) assert_true(await self._verify(previous_bank, cheque, bank)) return True + + async def add(self, previous: Nullable[FlowBank], cheque: FlowCheque) -> FlowBank: + assert isinstance(previous, Nullable) + assert isinstance(cheque, FlowCheque) + previous_bank: FlowBank + if previous.null(): + previous_bank = self.initial + else: + previous_bank = previous.resolve() + assert isinstance(previous_bank, FlowBank) + minted: FlowStandard[FlowCoin] + used: FlowStandard[FlowCoin] + minted, used = await gather( + flow_union(previous_bank.minted, cheque.minted), + flow_union(previous_bank.used, cheque.used), + ) + return FlowBank(minted, used) + + def link_verification(self) -> Verification[ + tuple[Nullable[Pair[FlowCheque, FlowBank]], Pair[FlowCheque, FlowBank]] + ]: + class Decomposition(Mapper[Pair[FlowCheque, FlowBank], tuple[FlowCheque, FlowBank]]): + async def map(self, element: Pair[FlowCheque, FlowBank]) -> tuple[FlowCheque, FlowBank]: + cheque: FlowCheque + bank: FlowBank + cheque, bank = await gather( + element.element0.resolve(), + element.element1.resolve(), + ) + assert isinstance(cheque, FlowCheque) + assert isinstance(bank, FlowBank) + return cheque, bank + + return StateVerification( + Decomposition(), + self + ) diff --git a/rainbowadn/flow13/_flowbank.py b/rainbowadn/flow13/_flowbank.py index 5702d0c..0acbec1 100644 --- a/rainbowadn/flow13/_flowbank.py +++ b/rainbowadn/flow13/_flowbank.py @@ -1,12 +1,51 @@ +from typing import Iterable + +from rainbowadn.core import * from ._flowstandard import * from ._flowtransaction import * __all__ = ('FlowBank',) -class FlowBank: - async def minted(self) -> FlowStandard[FlowCoin]: - raise NotImplementedError +class FlowBank(StaticMentionable, RecursiveMentionable): + @classmethod + def from_bytes(cls, source: bytes, resolver: HashResolver) -> 'FlowBank': + return FlowBank( + FlowStandardFactory.of(FlowCoin.factory()).from_bytes( + source[:HashPoint.HASH_LENGTH], resolver + ), + FlowStandardFactory.of(FlowCoin.factory()).from_bytes( + source[HashPoint.HASH_LENGTH:], resolver + ), + ) - async def used(self) -> FlowStandard[FlowCoin]: - raise NotImplementedError + def points(self) -> Iterable[HashPoint]: + return [*self.minted.points(), *self.used.points()] + + def __bytes__(self): + return bytes(self.minted) + bytes(self.used) + + def __init__( + self, + minted: FlowStandard[FlowCoin], + used: FlowStandard[FlowCoin], + ): + assert isinstance(minted, FlowStandard) + assert isinstance(used, FlowStandard) + self.minted = minted + self.used = used + + @classmethod + def empty(cls) -> 'FlowBank': + return FlowBank( + FlowStandardFactory.empty(FlowCoin.factory()), + FlowStandardFactory.empty(FlowCoin.factory()), + ) + + async def str(self, tab: int) -> str: + assert isinstance(tab, int) + return f'(' \ + f'{tabulate(tab + 1)}bank' \ + f'{tabulate(tab + 1)}(minted)' \ + f'{tabulate(tab + 1)}(used)' \ + f'{tabulate(tab)})' diff --git a/rainbowadn/flow13/_flowblock.py b/rainbowadn/flow13/_flowblock.py new file mode 100644 index 0000000..676438a --- /dev/null +++ b/rainbowadn/flow13/_flowblock.py @@ -0,0 +1,182 @@ +from typing import Generic, Iterable, TypeAlias, TypeVar + +from rainbowadn.core import * +from rainbowadn.flow.verification.core import * +from rainbowadn.nullability import * +from ._flowstandard import * + +__all__ = ('FlowBlock', 'FlowBlockFactory', 'FlowBlockVerification',) + +LinkT = TypeVar('LinkT') + +FBL: TypeAlias = 'FlowBlock[LinkT]' +Index: TypeAlias = FlowStandard[FBL] + + +class FlowBlock(Generic[LinkT], RecursiveMentionable): + def points(self) -> Iterable[HashPoint]: + return [*self.previous.points(), *self.index.points(), self.link] + + def __bytes__(self): + return bytes(self.previous) + bytes(self.index) + bytes(self.link) + + def __factory__(self) -> RainbowFactory[FBL]: + return FlowBlockFactory(self.link.factory) + + def __init__( + self, + previous: NullableReference[FBL], + index: Index, + link: HashPoint[LinkT], + ): + assert isinstance(previous, NullableReference) + assert isinstance(index, FlowStandard) + self.previous = previous + self.index = index + self.link = link + + async def outer(self) -> Index: + return FlowStandard( + ( + await self.index.protocolized.tree.add( + HashPoint.of(self) + ) + ).protocolized() + ) + + @classmethod + async def outer_of(cls, factory: RainbowFactory[LinkT], reference: NullableReference[FBL]) -> Index: + if reference.null(): + return FlowStandardFactory.empty(FlowBlockFactory(factory)) + else: + return await (await reference.resolve()).outer() + + @classmethod + async def link_of(cls, reference: NullableReference[FBL]) -> Nullable[LinkT]: + if reference.null(): + return Null() + else: + return NotNull(await (await reference.resolve()).link.resolve()) + + async def add(self, link: HashPoint[LinkT]) -> FBL: + return FlowBlock( + NullableReference.off(self), + await self.outer(), + link + ) + + @classmethod + async def add_to(cls, reference: NullableReference[FBL], link: HashPoint[LinkT]) -> FBL: + return FlowBlock( + reference, + await cls.outer_of(link.factory, reference), + link + ) + + async def str(self, tab: int) -> str: + assert isinstance(tab, int) + previous_str: str + link_str: str + previous_str, link_str = await gather( + self.previous.str(tab), + hash_point_format(self.link, tab) + ) + assert isinstance(previous_str, str) + assert isinstance(link_str, str) + return f'{previous_str}' \ + f'{tabulate(tab)}(index)' \ + f'{tabulate(tab)}{link_str}' + + +class FlowBlockFactory(RainbowFactory[FBL], Generic[LinkT]): + def __init__(self, factory: RainbowFactory[LinkT]): + assert isinstance(factory, RainbowFactory) + self.factory = factory + + def from_bytes(self, source: bytes, resolver: HashResolver) -> FBL: + assert isinstance(source, bytes) + assert isinstance(resolver, HashResolver) + return FlowBlock( + NullableReferenceFactory(self).from_bytes(source[:HashPoint.HASH_LENGTH], resolver), + FlowStandardFactory.of(self).from_bytes(source[HashPoint.HASH_LENGTH:2 * HashPoint.HASH_LENGTH], resolver), + ResolverOrigin(self.factory, source[2 * HashPoint.HASH_LENGTH:], resolver).hash_point(), + ) + + def loose(self) -> RainbowFactory[FBL]: + return self + + +class FlowBlockIndexedVerification( + Verification[FBL], + Generic[LinkT] +): + def __init__( + self, + index: Index, + verification: Verification[tuple[Nullable[LinkT], LinkT]], + ): + assert isinstance(index, FlowStandard) + assert isinstance(verification, Verification) + self.index = index + self.verification = verification + + async def verify(self, element: FBL) -> bool: + link = await element.link.resolve() + if element.previous.null(): + assert_trues( + await gather( + self.verification.verify((Null(), link)), + element.index.verify_empty(), + ) + ) + else: + previous: FBL = await element.previous.resolve() + + async def verify_link() -> bool: + assert_true(await self.verification.verify((NotNull(await previous.link.resolve()), link))) + return True + + async def verify_contains() -> bool: + assert_true(await self.index.contains(previous)) + return True + + async def verify_index() -> bool: + assert_eq(element.index, await previous.outer()) + return True + + assert_trues( + await gather( + verify_link(), + verify_contains(), + verify_index(), + ) + ) + return True + + def loose(self) -> Verification[FBL]: + return self + + +class FlowBlockVerification( + Verification[Index], + Generic[LinkT] +): + def __init__( + self, + verification: Verification[tuple[Nullable[LinkT], LinkT]], + ): + assert isinstance(verification, Verification) + self.verification = verification + + async def verify(self, element: Index) -> bool: + assert_true( + await ReduceVerification( + FlowBlockIndexedVerification(element, self.verification) + ).verify( + await element.reducer() + ) + ) + return True + + def loose(self) -> Verification[Index]: + return self diff --git a/rainbowadn/flow13/_flowcheque.py b/rainbowadn/flow13/_flowcheque.py index 09ad32c..19470e1 100644 --- a/rainbowadn/flow13/_flowcheque.py +++ b/rainbowadn/flow13/_flowcheque.py @@ -1,7 +1,12 @@ +import itertools +from typing import Iterable + from rainbowadn.collection.keyvalue import * from rainbowadn.core import * from rainbowadn.flow.core import * from rainbowadn.flow.verification.core import * +from rainbowadn.v13 import MINT_CONST +from ._flowiterate import * from ._flowstandard import * from ._flowtransaction import * @@ -19,21 +24,49 @@ class ValueMapper(Mapper[FlowCoin, int]): return await element.int_value() -class FlowCheque: - async def transactions(self) -> FlowStandard[FlowTransaction]: - raise NotImplementedError +class FlowCheque(StaticMentionable, RecursiveMentionable): + @classmethod + def from_bytes(cls, source: bytes, resolver: HashResolver) -> 'FlowCheque': + return FlowCheque( + FlowStandardFactory.of(FlowTransaction.factory()).from_bytes( + source[:HashPoint.HASH_LENGTH], resolver + ), + FlowStandardFactory.of(FlowCoin.factory()).from_bytes( + source[HashPoint.HASH_LENGTH:2 * HashPoint.HASH_LENGTH], resolver + ), + FlowStandardFactory.of(FlowCoin.factory()).from_bytes( + source[2 * HashPoint.HASH_LENGTH:3 * HashPoint.HASH_LENGTH], resolver + ), + FlowStandardFactory.of(KeyValueFactory(FlowCoin.factory(), FlowTransaction.factory()).loose()).from_bytes( + source[3 * HashPoint.HASH_LENGTH:], resolver + ), + ) - async def minted(self) -> FlowStandard[FlowCoin]: - raise NotImplementedError + def points(self) -> Iterable[HashPoint]: + return [*self.transactions.points(), *self.minted.points(), *self.used.points(), *self.usedx.points()] - async def used(self) -> FlowStandard[FlowCoin]: - raise NotImplementedError + def __bytes__(self): + return bytes(self.transactions) + bytes(self.minted) + bytes(self.used) + bytes(self.usedx) - async def usedx(self) -> FlowStandard[KeyValue[FlowCoin, FlowTransaction]]: - raise NotImplementedError + def __init__( + self, + transactions: FlowStandard[FlowTransaction], + minted: FlowStandard[FlowCoin], + used: FlowStandard[FlowCoin], + usedx: FlowStandard[KeyValue[FlowCoin, FlowTransaction]], + ): + assert isinstance(transactions, FlowStandard) + assert isinstance(minted, FlowStandard) + assert isinstance(used, FlowStandard) + assert isinstance(usedx, FlowStandard) + self.transactions = transactions + self.minted = minted + self.used = used + self.usedx = usedx - async def mint(self) -> int: - raise NotImplementedError + @classmethod + async def mint(cls) -> int: + return MINT_CONST @classmethod async def total_of(cls, tree: FlowStandard[FlowCoin]) -> int: @@ -45,10 +78,10 @@ class FlowCheque: return total async def total_minted(self) -> int: - return await self.total_of(await self.minted()) + return await self.total_of(self.minted) async def total_used(self) -> int: - return await self.total_of(await self.used()) + return await self.total_of(self.used) async def extra(self) -> int: mint: int @@ -70,25 +103,25 @@ class FlowCheque: async def _verify_transactions(self) -> bool: assert_true( - await (await self.transactions()).verify(TransactionVerification(self).loose()) + await self.transactions.verify(TransactionVerification(self).loose()) ) return True async def _verify_minted(self) -> bool: assert_true( - await (await self.minted()).verify(MintedVerification(self).loose()) + await self.minted.verify(MintedVerification(self).loose()) ) return True async def _verify_used(self) -> bool: assert_true( - await (await self.used()).verify(UsedVerification(self).loose()) + await self.used.verify(UsedVerification(self).loose()) ) return True async def _verify_usedx(self) -> bool: assert_true( - await (await self.usedx()).verify(UsedXVerification(self).loose()) + await self.usedx.verify(UsedXVerification(self).loose()) ) return True @@ -104,6 +137,87 @@ class FlowCheque: ) return True + @classmethod + async def _transaction_minted(cls, transaction: FlowTransaction) -> Iterable[FlowCoin]: + assert isinstance(transaction, FlowTransaction) + return await (await transaction.minted_reducer()).reduce(FlowIterate([])) + + @classmethod + async def _transaction_used(cls, transaction: FlowTransaction) -> Iterable[FlowCoin]: + assert isinstance(transaction, FlowTransaction) + return await (await transaction.used_reducer()).reduce(FlowIterate([])) + + @classmethod + async def _transaction_usedx(cls, transaction: FlowTransaction) -> Iterable[KeyValue[FlowCoin, FlowTransaction]]: + assert isinstance(transaction, FlowTransaction) + return ( + KeyValue(HashPoint.of(coin), HashPoint.of(transaction)) + for + coin + in + await cls._transaction_used(transaction) + ) + + @classmethod + async def _make_minted(cls, transactions: Iterable[FlowTransaction]) -> FlowStandard[FlowCoin]: + return await FlowStandardFactory.off( + FlowCoin.factory(), itertools.chain( + *(await gather(*(cls._transaction_minted(transaction) for transaction in transactions))) + ) + ) + + @classmethod + async def _make_used(cls, transactions: Iterable[FlowTransaction]) -> FlowStandard[FlowCoin]: + return await FlowStandardFactory.off( + FlowCoin.factory(), itertools.chain( + *(await gather(*(cls._transaction_used(transaction) for transaction in transactions))) + ) + ) + + @classmethod + async def _make_usedx( + cls, + transactions: Iterable[FlowTransaction] + ) -> FlowStandard[KeyValue[FlowCoin, FlowTransaction]]: + return await FlowStandardFactory.off( + KeyValueFactory(FlowCoin.factory(), FlowTransaction.factory()).loose(), itertools.chain( + *(await gather(*(cls._transaction_usedx(transaction) for transaction in transactions))) + ) + ) + + @classmethod + async def make(cls, transactions: Iterable[FlowTransaction]) -> 'FlowCheque': + transactions_standard: FlowStandard[FlowTransaction] + minted: FlowStandard[FlowCoin] + used: FlowStandard[FlowCoin] + usedx: FlowStandard[KeyValue[FlowCoin, FlowTransaction]] + transactions_standard, minted, used, usedx = await gather( + FlowStandardFactory.off(FlowTransaction.factory(), transactions), + cls._make_minted(transactions), + cls._make_used(transactions), + cls._make_usedx(transactions), + ) + assert isinstance(transactions_standard, FlowStandard) + assert isinstance(minted, FlowStandard) + assert isinstance(used, FlowStandard) + assert isinstance(usedx, FlowStandard) + return cls( + transactions_standard, + minted, + used, + usedx, + ) + + async def str(self, tab: int) -> str: + assert isinstance(tab, int) + return f'(' \ + f'{tabulate(tab + 1)}cheque' \ + f'{tabulate(tab + 1)}{await self.transactions.str(tab + 1)}' \ + f'{tabulate(tab + 1)}(minted)' \ + f'{tabulate(tab + 1)}(used)' \ + f'{tabulate(tab + 1)}(usedx)' \ + f'{tabulate(tab)})' + class TransactionVerification( Verification[FlowTransaction] @@ -133,50 +247,27 @@ class TransactionVerification( async def _verify_transaction_minted(self, transaction: FlowTransaction) -> bool: assert isinstance(transaction, FlowTransaction) - minted: FlowStandard[FlowCoin] - minted_reducer: Reducer[FlowCoin, bool] - minted, minted_reducer = await gather( - self.cheque.minted(), - transaction.minted_reducer(), - ) - assert isinstance(minted, FlowStandard) - assert isinstance(minted_reducer, Reducer) assert_true( - await minted.verify_contains_all( - minted_reducer + await self.cheque.minted.verify_contains_all( + await transaction.minted_reducer() ) ) return True async def _verify_transaction_used(self, transaction: FlowTransaction) -> bool: assert isinstance(transaction, FlowTransaction) - minted: FlowStandard[FlowCoin] - minted_reducer: Reducer[FlowCoin, bool] - used, used_reducer = await gather( - self.cheque.used(), - transaction.used_reducer(), - ) - assert isinstance(used, FlowStandard) - assert isinstance(used_reducer, Reducer) assert_true( - await used.verify_contains_all( - used_reducer + await self.cheque.used.verify_contains_all( + await transaction.used_reducer() ) ) return True async def _verify_transaction_usedx(self, transaction: FlowTransaction) -> bool: - minted: FlowStandard[KeyValue[FlowCoin, FlowTransaction]] - minted_reducer: Reducer[FlowCoin, bool] - usedx, used_reducer = await gather( - self.cheque.usedx(), - transaction.used_reducer(), - ) - assert isinstance(usedx, FlowStandard) - assert isinstance(used_reducer, Reducer) + assert isinstance(transaction, FlowTransaction) assert_true( - await usedx.verify_contains_all( - self.usedx_reducer(used_reducer, transaction) + await self.cheque.usedx.verify_contains_all( + self.usedx_reducer(await transaction.used_reducer(), transaction) ) ) return True @@ -205,17 +296,9 @@ class MintedVerification( async def verify(self, element: FlowCoin) -> bool: assert isinstance(element, FlowCoin) - transactions: FlowStandard[FlowTransaction] - transaction: FlowTransaction - transactions, transaction = await gather( - self.cheque.transactions(), - element.transaction.resolve(), - ) - assert isinstance(transactions, FlowStandard) - assert isinstance(transaction, FlowTransaction) assert_true( - await transactions.contains( - transaction + await self.cheque.transactions.contains( + await element.transaction.resolve() ) ) return True @@ -234,7 +317,7 @@ class UsedVerification( async def verify(self, element: FlowCoin) -> bool: assert isinstance(element, FlowCoin) assert_true( - await (await self.cheque.usedx()).contains( + await self.cheque.usedx.contains( KeyValue(HashPoint.of(element), HashPoint.of(FlowTransaction.empty())) ) ) @@ -251,24 +334,33 @@ class UsedXVerification( assert isinstance(cheque, FlowCheque) self.cheque = cheque - async def _verify_transaction(self, transaction: FlowTransaction) -> bool: + async def _verify_transaction_registred(self, transaction: FlowTransaction) -> bool: assert isinstance(transaction, FlowTransaction) assert_true( - await (await self.cheque.transactions()).contains( + await self.cheque.transactions.contains( transaction ) ) return True @classmethod - async def _verify_transaction_contains(cls, transaction: FlowTransaction, coin: HashPoint[FlowCoin]) -> bool: + async def _verify_coin_contained_in_transaction( + cls, transaction: FlowTransaction, coin: HashPoint[FlowCoin] + ) -> bool: assert isinstance(transaction, FlowTransaction) assert isinstance(coin, HashPoint) - in_coin: HashPoint[FlowCoin] - async for in_coin in (await transaction.data_resolved()).iter_in_coins(): - if in_coin == coin: - return True - raise ValueError('used coin not found in transaction') + data_resolved: FlowTransactionData + coin_resoled: FlowCoin + data_resolved, coin_resoled = await gather( + transaction.data_resolved(), + coin.resolve(), + ) + assert_true(await data_resolved.in_coins.contains(coin_resoled)) + return True + + async def _verify_coin_registred_as_used(self, coin: HashPoint[FlowCoin]) -> bool: + assert_true(await self.cheque.used.contains(await coin.resolve())) + return True async def verify(self, element: KeyValue[FlowCoin, FlowTransaction]) -> bool: assert isinstance(element, KeyValue) @@ -276,8 +368,9 @@ class UsedXVerification( assert isinstance(transaction, FlowTransaction) assert_trues( await gather( - self._verify_transaction(transaction), - self._verify_transaction_contains(transaction, element.key), + self._verify_transaction_registred(transaction), + self._verify_coin_contained_in_transaction(transaction, element.key), + self._verify_coin_registred_as_used(element.key), ) ) return True diff --git a/rainbowadn/flow13/_flowiterate.py b/rainbowadn/flow13/_flowiterate.py new file mode 100644 index 0000000..12cc163 --- /dev/null +++ b/rainbowadn/flow13/_flowiterate.py @@ -0,0 +1,29 @@ +from typing import Generic, Iterable, TypeVar + +from rainbowadn.flow.core import * + +__all__ = ('FlowIterate',) + +Element = TypeVar('Element') + + +class FlowIterate( + Reduce[Element, Iterable[Element]], + Generic[Element] +): + async def reduce(self, out: Iterable[Element], element: Element) -> Iterable[Element]: + def wrap() -> Iterable[Element]: + yield from out + yield element + + return wrap() + + async def merge(self, left: Iterable[Element], right: Iterable[Element]) -> Iterable[Element]: + def wrap() -> Iterable[Element]: + yield from left + yield from right + + return wrap() + + def loose(self) -> Reduce[Element, Iterable[Element]]: + return self diff --git a/rainbowadn/flow13/_flowstandard.py b/rainbowadn/flow13/_flowstandard.py index 555a8c5..76ff9db 100644 --- a/rainbowadn/flow13/_flowstandard.py +++ b/rainbowadn/flow13/_flowstandard.py @@ -1,15 +1,19 @@ -from typing import Any, Generic, TypeAlias, TypeVar +from typing import Any, Generic, Iterable, TypeAlias, TypeVar from rainbowadn.atomic import * +from rainbowadn.collection.comparison import * +from rainbowadn.collection.keymetadata import * from rainbowadn.collection.trees.binary import * -from rainbowadn.collection.trees.binary.actions import * from rainbowadn.collection.trees.binary.core import * from rainbowadn.core import * from rainbowadn.flow.core import * +from rainbowadn.flow.primitive import * +from rainbowadn.flow.verification.core import * +from rainbowadn.nullability import * from ._binaryflow import * from ._flowtree import * -__all__ = ('FlowStandard',) +__all__ = ('FlowStandard', 'FlowStandardFactory',) KeyT = TypeVar('KeyT') FS: TypeAlias = 'FlowStandard[KeyT]' @@ -18,15 +22,27 @@ BP: TypeAlias = 'BinaryProtocolized[KeyT, Integer, ABT]' class FlowStandard( + RecursiveMentionable, FlowTree[KeyT, FS], Generic[KeyT] ): + def points(self) -> Iterable[HashPoint]: + return self.protocolized.tree.reference.points() + + def __bytes__(self): + return bytes(self.protocolized.tree.reference) + + def __factory__(self) -> RainbowFactory['FlowStandard[KeyT]']: + return FlowStandardFactory( + self.protocolized.tree.reference.factory + ) + def __init__(self, protocolized: BP): assert isinstance(protocolized, BinaryProtocolized) self.protocolized = protocolized async def contains(self, key: KeyT) -> bool: - return await ContainsAction(key).on(self.protocolized) + return await self.protocolized.tree.contains(HashPoint.of(key)) def _protocolized(self: FS) -> BP: return self.protocolized @@ -41,5 +57,70 @@ class FlowStandard( assert_true(await VerifySubsetAction(reducer).on(self.protocolized)) return True + async def verify_empty(self) -> bool: + assert_true( + await ReduceVerification( + MapperVerification(ConstMapper(False)) + ).verify( + await self.reducer() + ) + ) + return True + async def reducer(self) -> Reducer[KeyT, Any]: return BinaryReducer(self.protocolized).loose() + + def __eq__(self, other): + if isinstance(other, FlowStandard): + return self.protocolized.tree == other.protocolized.tree + else: + return NotImplemented + + async def str(self, tab: int) -> str: + assert isinstance(tab, int) + return await self.protocolized.tree.reference.str(tab) + + +class FlowStandardFactory(RainbowFactory[FlowStandard[KeyT]], Generic[KeyT]): + def __init__( + self, + factory: RainbowFactory[BinaryTree[KeyMetadata[KeyT, Integer]]] + ): + assert isinstance(factory, RainbowFactory) + self.factory = factory + + @classmethod + def of(cls, factory: RainbowFactory[KeyT]) -> 'FlowStandardFactory[KeyT]': + assert isinstance(factory, RainbowFactory) + return FlowStandardFactory( + BinaryTreeFactory(KeyMetadataFactory(factory, Integer.factory())) + ) + + @classmethod + async def off(cls, factory: RainbowFactory[KeyT], keys: Iterable[KeyT]) -> 'FlowStandard[KeyT]': + assert isinstance(factory, RainbowFactory) + abt: ActiveBinaryTree[KeyT, Integer] = cls.empty(factory).protocolized.tree + for key in keys: + abt = await abt.add(HashPoint.of(key)) + return FlowStandard(abt.protocolized()) + + @classmethod + def protocol(cls) -> BinaryBalancing[KeyT, Integer, ABT]: + return AVL(HashComparator(Fail())) + + @classmethod + def empty(cls, factory: RainbowFactory[KeyT]) -> 'FlowStandard[KeyT]': + assert isinstance(factory, RainbowFactory) + return FlowStandard( + ActiveBinaryTree.empty(cls.protocol(), factory).protocolized() + ) + + def from_bytes(self, source: bytes, resolver: HashResolver) -> FlowStandard[KeyT]: + assert isinstance(source, bytes) + assert isinstance(resolver, HashResolver) + return FlowStandard( + ActiveBinaryTree( + self.protocol(), + NullableReferenceFactory(self.factory).from_bytes(source, resolver) + ).protocolized() + ) diff --git a/rainbowadn/flow13/_flowtransaction.py b/rainbowadn/flow13/_flowtransaction.py index 9311d73..51a71dd 100644 --- a/rainbowadn/flow13/_flowtransaction.py +++ b/rainbowadn/flow13/_flowtransaction.py @@ -1,14 +1,14 @@ -from typing import Any, AsyncIterable, Iterable +from typing import Any, Iterable -import nacl.signing +from nacl.signing import SigningKey from rainbowadn.atomic import * -from rainbowadn.collection.linear import * +from rainbowadn.collection.keyvalue import * from rainbowadn.core import * -from rainbowadn.flow.bridge import * from rainbowadn.flow.core import * -from rainbowadn.nullability import * -from rainbowadn.v13 import * +from rainbowadn.flow.verification.core import * +from rainbowadn.v13 import Signature, Subject +from ._flowstandard import * __all__ = ('FlowCoinData', 'FlowCoin', 'FlowTransactionData', 'FlowTransaction',) @@ -64,24 +64,21 @@ class FlowCoin(RecursiveMentionable, StaticMentionable): def __init__( self, data: HashPoint[FlowCoinData], - transaction: HashPoint['FlowTransaction'], - index: HashPoint[Integer] + transaction: HashPoint['FlowTransaction'] ): assert isinstance(data, HashPoint) assert isinstance(transaction, HashPoint) - assert isinstance(index, HashPoint) self.data = data self.transaction = transaction - self.index = index async def data_resolved(self) -> FlowCoinData: return await self.data.resolve() def points(self) -> Iterable[HashPoint]: - return [self.data, self.transaction, self.index] + return [self.data, self.transaction] def __bytes__(self): - return bytes(self.data) + bytes(self.transaction) + bytes(self.index) + return bytes(self.data) + bytes(self.transaction) @classmethod def from_bytes(cls, source: bytes, resolver: HashResolver) -> 'FlowCoin': @@ -92,22 +89,14 @@ class FlowCoin(RecursiveMentionable, StaticMentionable): ResolverOrigin( FlowTransaction.factory(), source[HashPoint.HASH_LENGTH:2 * HashPoint.HASH_LENGTH], resolver ).hash_point(), - ResolverOrigin(Integer.factory(), source[2 * HashPoint.HASH_LENGTH:], resolver).hash_point(), ) async def str(self, tab: int) -> str: assert isinstance(tab, int) - data_str, index_str = await gather( - hash_point_format(self.data, tab + 1), - hash_point_format(self.index, tab + 1), - ) - assert isinstance(data_str, str) - assert isinstance(index_str, str) return f'(' \ f'{tabulate(tab + 1)}coin' \ - f'{tabulate(tab + 1)}{data_str}' \ + f'{tabulate(tab + 1)}{await hash_point_format(self.data, tab + 1)}' \ f'{tabulate(tab + 1)}(origin)' \ - f'{tabulate(tab + 1)}{index_str}' \ f'{tabulate(tab)})' async def int_value(self) -> int: @@ -120,11 +109,11 @@ class FlowCoin(RecursiveMentionable, StaticMentionable): class FlowTransactionData(RecursiveMentionable, StaticMentionable): def __init__( self, - in_coins: NullableReference[Stack[FlowCoin]], - out_coins: NullableReference[Stack[FlowCoinData]], + in_coins: FlowStandard[FlowCoin], + out_coins: FlowStandard[FlowCoinData], ): - assert isinstance(in_coins, NullableReference) - assert isinstance(out_coins, NullableReference) + assert isinstance(in_coins, FlowStandard) + assert isinstance(out_coins, FlowStandard) self.in_coins = in_coins self.out_coins = out_coins self.hash_point = HashPoint.of(self) @@ -140,13 +129,10 @@ class FlowTransactionData(RecursiveMentionable, StaticMentionable): def from_bytes(cls, source: bytes, resolver: HashResolver) -> 'FlowTransactionData': assert isinstance(source, bytes) assert isinstance(resolver, HashResolver) + return cls( - NullableReferenceFactory( - StackFactory(FlowCoin.factory()).loose() - ).from_bytes(source[:HashPoint.HASH_LENGTH], resolver), - NullableReferenceFactory( - StackFactory(FlowCoinData.factory()).loose() - ).from_bytes(source[HashPoint.HASH_LENGTH:], resolver), + FlowStandardFactory.of(FlowCoin.factory()).from_bytes(source[:HashPoint.HASH_LENGTH], resolver), + FlowStandardFactory.of(FlowCoinData.factory()).from_bytes(source[HashPoint.HASH_LENGTH:], resolver), ) async def _signature_verify(self, coin: FlowCoin, signature: Signature) -> bool: @@ -160,48 +146,69 @@ class FlowTransactionData(RecursiveMentionable, StaticMentionable): ) return True - async def _verify_signatures( + @classmethod + def _coin_verification_mapper( + cls, + signatures: FlowStandard[KeyValue[Subject, Signature]], + ) -> Mapper[ + FlowCoin, + bool + ]: + assert isinstance(signatures, FlowStandard) + return CVMapper(signatures) + + def _signature_pair_verification_mapper(self) -> Mapper[ + KeyValue[Subject, Signature], + bool + ]: + return SPVMapper(self.hash_point) + + async def _verify_coin_signatures( self, - signatures: NullableReference[Stack[Signature]] + signatures: FlowStandard[KeyValue[Subject, Signature]], ) -> bool: - assert isinstance(signatures, NullableReference) - assert_trues( - await gather( - *[ - self._signature_verify(coin, signature) - for - coin, signature - in - zip( - await self.in_coins_resolved(), - await Stack.list(signatures), - strict=True - ) - ] + assert isinstance(signatures, FlowStandard) + assert_true( + await ReduceVerification( + MapperVerification(self._coin_verification_mapper(signatures)) + ).loose().verify( + await self.in_coins.reducer() ) ) return True - def iter_in_coins(self) -> AsyncIterable[HashPoint[FlowCoin]]: - return Stack.iter(self.in_coins) + async def _verify_signature_pairs( + self, + signatures: FlowStandard[KeyValue[Subject, Signature]], + ): + assert isinstance(signatures, FlowStandard) + assert_true( + await ReduceVerification( + MapperVerification(self._signature_pair_verification_mapper()) + ).loose().verify( + await signatures.reducer() + ) + ) + return True - async def coins(self) -> list[FlowCoin]: - return [await x.resolve() async for x in self.iter_in_coins()] - - async def in_coins_resolved(self) -> list[FlowCoin]: - return await Stack.list(self.in_coins) - - def iter_out_coins(self) -> AsyncIterable[HashPoint[FlowCoinData]]: - return Stack.iter(self.out_coins) - - async def out_coins_resolved(self) -> list[FlowCoinData]: - return await Stack.list(self.out_coins) + async def _verify_signatures( + self, + signatures: FlowStandard[KeyValue[Subject, Signature]], + ) -> bool: + assert isinstance(signatures, FlowStandard) + assert_trues( + await gather( + self._verify_coin_signatures(signatures), + self._verify_signature_pairs(signatures), + ) + ) + return True async def verify( self, - signatures: NullableReference[Stack[Signature]] + signatures: FlowStandard[KeyValue[Subject, Signature]] ) -> bool: - assert isinstance(signatures, NullableReference) + assert isinstance(signatures, FlowStandard) assert_true(await self._verify_signatures(signatures)) return True @@ -218,15 +225,56 @@ class FlowTransactionData(RecursiveMentionable, StaticMentionable): f'{tabulate(tab)}(out)' \ f'{tabulate(tab)}{out_str}' + @classmethod + def empty(cls) -> 'FlowTransactionData': + return cls( + FlowStandardFactory.empty(FlowCoin.factory()), + FlowStandardFactory.empty(FlowCoinData.factory()), + ) + + +class CVMapper(Mapper[FlowCoin, bool]): + def __init__(self, signatures: FlowStandard[KeyValue[Subject, Signature]]): + assert isinstance(signatures, FlowStandard) + self.signatures = signatures + + async def map(self, element: FlowCoin) -> bool: + assert isinstance(element, FlowCoin) + assert_true( + await self.signatures.contains( + KeyValue((await element.data.resolve()).owner, HashPoint.of(Signature.empty())) + ) + ) + return True + + +class SPVMapper(Mapper[KeyValue[Subject, Signature], bool]): + def __init__(self, hashpoint: HashPoint): + assert isinstance(hashpoint, HashPoint) + self.hashpoint = hashpoint + + async def map(self, element: KeyValue[Subject, Signature]) -> bool: + assert isinstance(element, KeyValue) + subject: Subject + signature: Signature + subject, signature = await gather( + element.key, + element.value, + ) + assert isinstance(subject, Subject) + assert isinstance(signature, Signature) + assert_true(signature.verify(subject, self.hashpoint)) + return True + class FlowTransaction(RecursiveMentionable, StaticMentionable): def __init__( self, data: HashPoint[FlowTransactionData], - signatures: NullableReference[Stack[Signature]] + signatures: FlowStandard[KeyValue[Subject, Signature]] ): assert isinstance(data, HashPoint) - assert isinstance(signatures, NullableReference) + assert isinstance(signatures, FlowStandard) self.data = data self.signatures = signatures self.hash_point = HashPoint.of(self) @@ -245,45 +293,25 @@ class FlowTransaction(RecursiveMentionable, StaticMentionable): def from_bytes(cls, source: bytes, resolver: HashResolver) -> 'FlowTransaction': assert isinstance(source, bytes) assert isinstance(resolver, HashResolver) - signature_factory: RainbowFactory[Signature] = Signature.factory() - assert isinstance(signature_factory, RainbowFactory) - stack_factory: RainbowFactory[Stack[Signature]] = StackFactory(signature_factory).loose() - assert isinstance(stack_factory, RainbowFactory) return cls( ResolverOrigin(FlowTransactionData.factory(), source[:HashPoint.HASH_LENGTH], resolver).hash_point(), - NullableReferenceFactory(stack_factory).from_bytes(source[HashPoint.HASH_LENGTH:], resolver), + FlowStandardFactory.of(KeyValueFactory(Subject.factory(), Signature.factory()).loose()).from_bytes( + source[HashPoint.HASH_LENGTH:], resolver + ), ) - async def iter_coins( - self - ) -> AsyncIterable[FlowCoin]: - transaction_data: FlowTransactionData = await self.data_resolved() - assert isinstance(transaction_data, FlowTransactionData) - index = 0 - out_coin: HashPoint[FlowCoinData] - async for out_coin in transaction_data.iter_out_coins(): - assert isinstance(out_coin, HashPoint) - coin: FlowCoin = FlowCoin(out_coin, self.hash_point, HashPoint.of(Integer(index))) - assert isinstance(coin, FlowCoin) - yield coin - index += 1 - - async def coins( - self - ) -> list[FlowCoin]: - return [coin async for coin in self.iter_coins()] + def _coin(self, data: FlowCoinData) -> FlowCoin: + return FlowCoin(HashPoint.of(data), self.hash_point) async def used_reducer(self) -> Reducer[FlowCoin, Any]: transaction_data: FlowTransactionData = await self.data_resolved() assert isinstance(transaction_data, FlowTransactionData) - bridge: Reducer[FlowCoin, Any] = ListBridge(await transaction_data.coins()) - assert isinstance(bridge, Reducer) - return bridge + return await transaction_data.in_coins.reducer() async def minted_reducer(self) -> Reducer[FlowCoin, Any]: - bridge: Reducer[FlowCoin, Any] = ListBridge(await self.coins()) - assert isinstance(bridge, Reducer) - return bridge + transaction_data: FlowTransactionData = await self.data_resolved() + assert isinstance(transaction_data, FlowTransactionData) + return MapReducer(CallableMapper(self._coin), await transaction_data.out_coins.reducer()) async def verify(self): data: FlowTransactionData = await self.data_resolved() @@ -293,41 +321,49 @@ class FlowTransaction(RecursiveMentionable, StaticMentionable): async def str(self, tab: int) -> str: assert isinstance(tab, int) - data_str, signatures_str = await gather( - hash_point_format(self.data, tab + 1), - self.signatures.str(tab + 1), - ) - assert isinstance(data_str, str) - assert isinstance(signatures_str, str) return f'(' \ f'{tabulate(tab + 1)}transaction' \ - f'{tabulate(tab + 1)}{data_str}' \ - f'{tabulate(tab + 1)}{signatures_str}' \ + f'{tabulate(tab + 1)}{await hash_point_format(self.data, tab + 1)}' \ + f'{tabulate(tab + 1)}(signatures)' \ f'{tabulate(tab)})' @classmethod - def make( - cls, - in_coins: list[FlowCoin], - out_coins: list[FlowCoinData], - keys: list[nacl.signing.SigningKey], - ) -> 'FlowTransaction': - assert isinstance(in_coins, list) - assert isinstance(out_coins, list) - assert isinstance(keys, list) - transaction_data = FlowTransactionData( - Stack.off(FlowCoin.factory(), reversed(in_coins)), - Stack.off(FlowCoinData.factory(), reversed(out_coins)), - ) - assert isinstance(transaction_data, FlowTransactionData) - return FlowTransaction( - HashPoint.of(transaction_data), - Stack.off( - Signature.factory(), - (Signature.sign(key, HashPoint.of(transaction_data)) for key in reversed(keys)) - ) + def empty(cls): + return cls( + HashPoint.of(FlowTransactionData.empty()), + FlowStandardFactory.empty(KeyValueFactory(Subject.factory(), Signature.factory()).loose()), ) @classmethod - def empty(cls): - return cls.make([], [], []) + async def make( + cls, + used: Iterable[FlowCoin], + minted: Iterable[FlowCoinData], + keys: Iterable[SigningKey], + ) -> 'FlowTransaction': + used_std: FlowStandard[FlowCoin] + minted_std: FlowStandard[FlowCoinData] + used_std, minted_std = await gather( + FlowStandardFactory.off(FlowCoin.factory(), used), + FlowStandardFactory.off(FlowCoinData.factory(), minted), + ) + assert isinstance(used_std, FlowStandard) + assert isinstance(minted_std, FlowStandard) + transaction_data: FlowTransactionData = FlowTransactionData( + used_std, + minted_std, + ) + signatures: list[KeyValue[Subject, Signature]] = [ + KeyValue( + HashPoint.of(Subject(signing_key.verify_key)), + HashPoint.of(Signature.sign(signing_key, transaction_data.hash_point)), + ) + for + signing_key + in + keys + ] + return cls( + HashPoint.of(transaction_data), + await FlowStandardFactory.off(KeyValueFactory(Subject.factory(), Signature.factory()).loose(), signatures) + ) diff --git a/rainbowadn/flow13/_flowunion.py b/rainbowadn/flow13/_flowunion.py new file mode 100644 index 0000000..8b638a0 --- /dev/null +++ b/rainbowadn/flow13/_flowunion.py @@ -0,0 +1,11 @@ +from typing import TypeVar + +from ._flowstandard import * + +__all__ = ('flow_union',) + +KeyT = TypeVar('KeyT') + + +async def flow_union(left: FlowStandard[KeyT], right: FlowStandard[KeyT]) -> FlowStandard[KeyT]: + return FlowStandard((await left.protocolized.tree.union(right.protocolized.tree)).protocolized()) diff --git a/rainbowadn/testing/test_bridge.py b/rainbowadn/testing/test_bridge.py index 407c984..4de6b33 100644 --- a/rainbowadn/testing/test_bridge.py +++ b/rainbowadn/testing/test_bridge.py @@ -1,6 +1,10 @@ import os +import random import time import unittest +from typing import Any + +from nacl.signing import SigningKey from rainbowadn.atomic import * from rainbowadn.collection.comparison import * @@ -14,6 +18,7 @@ from rainbowadn.flow.sequence import * from rainbowadn.flow.stacked import * from rainbowadn.flow13 import * from rainbowadn.nullability import * +from rainbowadn.v13 import Subject class PrintDispatch(SequenceDispatch[HashPoint, None]): @@ -71,7 +76,7 @@ class TestBridge(unittest.IsolatedAsyncioTestCase): return CallableMapper(cls.element_of) @classmethod - async def bridge(cls) -> Reducer[SequenceDispatcher[HashPoint[Plain], None], None]: + async def bridge(cls) -> Reducer[SequenceDispatcher[HashPoint[Plain], Any], Any]: hp: HashPoint[Stack[Plain]] = Stack.off(Plain.factory(), [Plain(b'A'), Plain(b'B'), Plain(b'C')]).hashpoint() print(await hash_point_format(hp, 0)) bridge = StackBridge(hp).over_elements() @@ -91,6 +96,11 @@ class TestBridge(unittest.IsolatedAsyncioTestCase): await StackedReducer(bridge).loose().reduce(PrintReduce().loose()) ) + async def test_iterator(self): + set_gather_linear() + bridge = ListBridge(list(range(13))) + print(list(await bridge.reduce(FlowIterate([]).loose()))) + @classmethod async def abt_of(cls, *plains: bytes) -> ActiveBinaryTree[Plain, Integer]: abt: ActiveBinaryTree[Plain, Integer] = ActiveBinaryTree.empty(AVL(PlainComparator(Fail())), Plain.factory()) @@ -102,7 +112,9 @@ class TestBridge(unittest.IsolatedAsyncioTestCase): set_gather_linear() set0 = {os.urandom(8).hex().encode() for _ in range(64)} abt0: ActiveBinaryTree[Plain, Integer] = await self.abt_of(*set0) - abt1: ActiveBinaryTree[Plain, Integer] = await abt0.add(HashPoint.of(Plain(os.urandom(8).hex().encode()))) + abt1: ActiveBinaryTree[Plain, Integer] = abt0 + for _ in range(16): + abt1 = await abt1.add(HashPoint.of(Plain(os.urandom(8).hex().encode()))) fs0 = FlowStandard(abt0.protocolized()) fs1 = FlowStandard(abt1.protocolized()) _t = time.process_time() @@ -111,3 +123,41 @@ class TestBridge(unittest.IsolatedAsyncioTestCase): with self.assertRaises(ValueError): await fs1.verify_subset(UnitReducer(fs0)) print('verification time', time.process_time() - _t) + + async def test_flow13(self): + set_gather_linear() + key = SigningKey.generate() + subject = Subject(key.verify_key) + transaction = await FlowTransaction.make( + [], + [FlowCoinData.of(subject, 1000)], + [], + ) + print(assert_true(await transaction.verify())) + cheque = await FlowCheque.make([transaction]) + print(assert_true(await cheque.verify())) + block = await BankBlock.empty().add(cheque) + print(assert_true(await block.verify())) + enable_newline() + print(await block.reference.str(0)) + disable_newline() + + async def test_13_stress(self): + set_gather_linear() + block: BankBlock = BankBlock.empty() + for _ in range(8): + block = await block.add( + await FlowCheque.make( + [ + await FlowTransaction.make( + [], + [ + FlowCoinData.of(Subject(SigningKey.generate().verify_key), 0) + for _ in range(random.randint(4, 7)) + ], + [] + ) + for _ in range(random.randint(4, 7)) + ] + ) + ) diff --git a/rainbowadn/testing/test_trees.py b/rainbowadn/testing/test_trees.py index bc9cfb0..9966042 100644 --- a/rainbowadn/testing/test_trees.py +++ b/rainbowadn/testing/test_trees.py @@ -95,3 +95,28 @@ class TestTrees(unittest.IsolatedAsyncioTestCase): for i in range(250): tree = await tree.add(HashPoint.of(Plain(os.urandom(16)))) print(await AVL.height(tree.protocolized())) + + async def test_split(self): + set_gather_linear() + protocol = AVL(PlainComparator(Replace())) + tree: ActiveBinaryTree[Plain, Integer] = ActiveBinaryTree.empty( + protocol, Plain.factory() + ) + for i in range(26): + tree = await tree.add(HashPoint.of(Plain(bytes([ord('A') + i])))) + treel, treer = await tree.split(HashPoint.of(Plain(b'J'))) + print(await treel.reference.str(0), ' << split >> ', await treer.reference.str(0)) + + async def test_union(self): + set_gather_linear() + protocol = AVL(PlainComparator(Replace())) + tree0: ActiveBinaryTree[Plain, Integer] = ActiveBinaryTree.empty(protocol, Plain.factory()) + for i in range(10): + tree0 = await tree0.add(HashPoint.of(Plain(os.urandom(2).hex().encode()))) + print(await tree0.reference.str(0)) + tree1: ActiveBinaryTree[Plain, Integer] = ActiveBinaryTree.empty(protocol, Plain.factory()) + for i in range(10): + tree1 = await tree1.add(HashPoint.of(Plain(os.urandom(2).hex().encode()))) + print(await tree1.reference.str(0)) + tree = await tree0.union(tree1) + print(await tree.reference.str(0)) diff --git a/rainbowadn/v13/signature.py b/rainbowadn/v13/signature.py index c4df406..acc9f56 100644 --- a/rainbowadn/v13/signature.py +++ b/rainbowadn/v13/signature.py @@ -19,6 +19,10 @@ class Signature(Atomic): assert_eq(len(source), nacl.bindings.crypto_sign_BYTES) self.source = source + @classmethod + def empty(cls) -> 'Signature': + return Signature(bytes(nacl.bindings.crypto_sign_BYTES)) + @classmethod def sign(cls, key: nacl.signing.SigningKey, hash_point: HashPoint) -> 'Signature': assert isinstance(key, nacl.signing.SigningKey) diff --git a/trace.py b/trace_chain.py similarity index 66% rename from trace.py rename to trace_chain.py index 1d28e77..94648b5 100644 --- a/trace.py +++ b/trace_chain.py @@ -1,9 +1,5 @@ import asyncio -import json -import os import random -import shutil -import time from contextlib import ExitStack from nacl.signing import SigningKey @@ -17,37 +13,7 @@ from rainbowadn.instrument import * from rainbowadn.nullability import * from rainbowadn.testing.resolvers import * from rainbowadn.v13 import * - - -def get_dr() -> ExtendableResolver: - dr = DictResolver() - dr = DelayedResolver(dr, lambda: random.uniform(0.200, 0.500)) - # dr = CachingResolver(dr) - return dr - - -def target_str(target) -> str: - match target: - case type(__name__=name): - return name - case object(__class__=type(__name__=name)): - return name - - -def jsonify(dumped: Instrumentation) -> dict: - prefix = f'{target_str(dumped.target)}:{dumped.methodname}' - match dumped: - case Counter(counter=ctr): - return {f'{prefix}:counter': ctr} - case Concurrency(log=log): - return {f'{prefix}:concurrency': log} - case EntryExit(entry_log=entry_log, exit_log=exit_log): - return { - f'{prefix}:entry': entry_log, - f'{prefix}:exit': exit_log, - } - case _: - return {} +from trace_common import * async def mock(bank: BankChain) -> BankChain: @@ -137,15 +103,6 @@ async def _instrument(bank: BankChain) -> list[Instrumentation]: return instrumentations -class DeintrumentationSize(Instrumentation): - def instrument(self, method, *args, **kwargs): - print( - f'deinstrumentation size @ {target_str(self.target)}:{self.methodname}', - len(self.deinstrumentation) - ) - return method(*args, **kwargs) - - async def _trace(): set_gather_linear() bank = await _generate( @@ -163,37 +120,12 @@ async def _trace(): return instrumentations -def _fn() -> str: - return f'trace/{int(time.time())}-{os.urandom(2).hex()}.json' - - -def _jsonify(instrumentations: list[Instrumentation]) -> dict: - jsonified = {} - for dumped in instrumentations: - jsonified |= jsonify(dumped) - return jsonified - - -def _dump(fn: str, jsonified: dict) -> None: - with open(fn, 'w') as file: - json.dump( - jsonified, - file - ) - print('dumped') - - -def _copy(fn: str) -> None: - shutil.copy(fn, f'trace/latest.json') - print('copied') - - async def main(): instrumentations = await _trace() - fn = _fn() - jsonified = _jsonify(instrumentations) - _dump(fn, jsonified) - _copy(fn) + fn = get_fn() + jsonified = jsonify_list(instrumentations) + dump(fn, jsonified) + copy(fn) plot(fn) print('plotted') diff --git a/trace_common.py b/trace_common.py new file mode 100644 index 0000000..ec52630 --- /dev/null +++ b/trace_common.py @@ -0,0 +1,76 @@ +import json +import os +import random +import shutil +import time + +from rainbowadn.core import * +from rainbowadn.instrument import * +from rainbowadn.testing.resolvers import * + +__all__ = ('get_dr', 'target_str', 'jsonify', 'get_fn', 'jsonify_list', 'dump', 'copy', 'DeintrumentationSize',) + + +def get_dr() -> ExtendableResolver: + dr = DictResolver() + dr = DelayedResolver(dr, lambda: random.uniform(0.200, 0.500)) + # dr = CachingResolver(dr) + return dr + + +def target_str(target) -> str: + match target: + case type(__name__=name): + return name + case object(__class__=type(__name__=name)): + return name + + +def jsonify(dumped: Instrumentation) -> dict: + prefix = f'{target_str(dumped.target)}:{dumped.methodname}' + match dumped: + case Counter(counter=ctr): + return {f'{prefix}:counter': ctr} + case Concurrency(log=log): + return {f'{prefix}:concurrency': log} + case EntryExit(entry_log=entry_log, exit_log=exit_log): + return { + f'{prefix}:entry': entry_log, + f'{prefix}:exit': exit_log, + } + case _: + return {} + + +def get_fn() -> str: + return f'trace/{int(time.time())}-{os.urandom(2).hex()}.json' + + +def jsonify_list(instrumentations: list[Instrumentation]) -> dict: + jsonified = {} + for dumped in instrumentations: + jsonified |= jsonify(dumped) + return jsonified + + +def dump(fn: str, jsonified: dict) -> None: + with open(fn, 'w') as file: + json.dump( + jsonified, + file + ) + print('dumped') + + +def copy(fn: str) -> None: + shutil.copy(fn, f'trace/latest.json') + print('copied') + + +class DeintrumentationSize(Instrumentation): + def instrument(self, method, *args, **kwargs): + print( + f'deinstrumentation size @ {target_str(self.target)}:{self.methodname}', + len(self.deinstrumentation) + ) + return method(*args, **kwargs) diff --git a/trace_flow.py b/trace_flow.py new file mode 100644 index 0000000..9f75fda --- /dev/null +++ b/trace_flow.py @@ -0,0 +1,107 @@ +import asyncio +import random +from contextlib import ExitStack + +from nacl.signing import SigningKey + +from plot import * +from rainbowadn.collection.trees.binary import * +from rainbowadn.collection.trees.binary.actions import * +from rainbowadn.core import * +from rainbowadn.flow13 import * +from rainbowadn.instrument import * +from rainbowadn.testing.resolvers import * +from rainbowadn.v13 import Subject +from trace_common import * + + +def get_instrumentations() -> list[Instrumentation]: + sleep_cc = Concurrency(DelayedResolver, 'sleep') + return [ + sleep_cc, + Concurrency(ActiveBinaryTree, 'add'), + Concurrency(ActiveBinaryTree, 'contains'), + Concurrency(BinaryAction, 'on'), + ] + + +async def _generate( + blocks: int, + subjects_min: int, + subjects_max: int, + transactions_min: int, + transactions_max: int, +) -> BankBlock: + bank: BankBlock = BankBlock.empty() + for _ in range(blocks): + bank = await bank.add( + await FlowCheque.make( + [ + await FlowTransaction.make( + [], + [ + FlowCoinData.of(Subject(SigningKey.generate().verify_key), 0) + for _ in range(random.randint(subjects_min, subjects_max)) + ], + [] + ) + for _ in range(random.randint(transactions_min, transactions_max)) + ] + ) + ) + print('generated') + return bank + + +async def _migrate(bank: BankBlock) -> BankBlock: + assert_true(await bank.verify()) + bank = BankBlock(await get_dr().migrate_resolved(bank.reference)) + print('migrated') + return bank + + +async def _instrument(bank: BankBlock) -> list[Instrumentation]: + with ExitStack() as estack: + instrumentations: list[Instrumentation] = get_instrumentations() + for stacked in instrumentations: + stacked.enter(estack) + assert_true(await bank.verify()) + print('deinstrumentation (should be empty):', Instrumentation.deinstrumentation) + print('instrumented') + return instrumentations + + +async def _trace(): + set_gather_linear() + bank = await _generate( + 16, + 8, 15, + 8, 15, + ) + # bank = await _generate( + # 8, + # 8, 8, + # 8, 8, + # ) + bank = await _migrate(bank) + set_gather_asyncio() + with DeintrumentationSize(Instrumentation, 'deinstrument'): + with Counter(DeintrumentationSize, 'instrument') as de_ctr: + instrumentations = await _instrument(bank) + print(jsonify(de_ctr)) + print('traced') + return instrumentations + + +async def main(): + instrumentations = await _trace() + fn = get_fn() + jsonified = jsonify_list(instrumentations) + dump(fn, jsonified) + copy(fn) + plot(fn) + print('plotted') + + +if __name__ == '__main__': + asyncio.run(main())