koi-net 1.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of koi-net might be problematic. Click here for more details.
- koi_net/__init__.py +1 -0
- koi_net/behaviors/handshaker.py +68 -0
- koi_net/behaviors/profile_monitor.py +23 -0
- koi_net/behaviors/sync_manager.py +68 -0
- koi_net/build/artifact.py +209 -0
- koi_net/build/assembler.py +60 -0
- koi_net/build/comp_order.py +6 -0
- koi_net/build/comp_type.py +7 -0
- koi_net/build/consts.py +18 -0
- koi_net/build/container.py +46 -0
- koi_net/cache.py +81 -0
- koi_net/config/core.py +113 -0
- koi_net/config/full_node.py +45 -0
- koi_net/config/loader.py +60 -0
- koi_net/config/partial_node.py +26 -0
- koi_net/config/proxy.py +20 -0
- koi_net/core.py +78 -0
- koi_net/effector.py +147 -0
- koi_net/entrypoints/__init__.py +2 -0
- koi_net/entrypoints/base.py +8 -0
- koi_net/entrypoints/poller.py +43 -0
- koi_net/entrypoints/server.py +85 -0
- koi_net/exceptions.py +107 -0
- koi_net/identity.py +20 -0
- koi_net/log_system.py +133 -0
- koi_net/network/__init__.py +0 -0
- koi_net/network/error_handler.py +63 -0
- koi_net/network/event_buffer.py +91 -0
- koi_net/network/event_queue.py +31 -0
- koi_net/network/graph.py +123 -0
- koi_net/network/request_handler.py +244 -0
- koi_net/network/resolver.py +152 -0
- koi_net/network/response_handler.py +130 -0
- koi_net/processor/__init__.py +0 -0
- koi_net/processor/context.py +36 -0
- koi_net/processor/handler.py +61 -0
- koi_net/processor/knowledge_handlers.py +302 -0
- koi_net/processor/knowledge_object.py +135 -0
- koi_net/processor/kobj_queue.py +51 -0
- koi_net/processor/pipeline.py +222 -0
- koi_net/protocol/__init__.py +0 -0
- koi_net/protocol/api_models.py +67 -0
- koi_net/protocol/consts.py +7 -0
- koi_net/protocol/edge.py +50 -0
- koi_net/protocol/envelope.py +65 -0
- koi_net/protocol/errors.py +24 -0
- koi_net/protocol/event.py +51 -0
- koi_net/protocol/model_map.py +62 -0
- koi_net/protocol/node.py +18 -0
- koi_net/protocol/secure.py +167 -0
- koi_net/secure_manager.py +115 -0
- koi_net/workers/__init__.py +2 -0
- koi_net/workers/base.py +26 -0
- koi_net/workers/event_worker.py +111 -0
- koi_net/workers/kobj_worker.py +51 -0
- koi_net-1.2.4.dist-info/METADATA +485 -0
- koi_net-1.2.4.dist-info/RECORD +59 -0
- koi_net-1.2.4.dist-info/WHEEL +4 -0
- koi_net-1.2.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
from rid_lib.types import KoiNetEdge, KoiNetNode
|
|
3
|
+
from rid_lib.ext import Cache
|
|
4
|
+
|
|
5
|
+
from ..exceptions import RequestError
|
|
6
|
+
from ..protocol.event import EventType
|
|
7
|
+
from ..network.request_handler import RequestHandler
|
|
8
|
+
from ..network.event_queue import EventQueue
|
|
9
|
+
from ..network.graph import NetworkGraph
|
|
10
|
+
from ..identity import NodeIdentity
|
|
11
|
+
from .handler import (
|
|
12
|
+
KnowledgeHandler,
|
|
13
|
+
HandlerType,
|
|
14
|
+
STOP_CHAIN,
|
|
15
|
+
StopChain
|
|
16
|
+
)
|
|
17
|
+
from .knowledge_object import KnowledgeObject
|
|
18
|
+
from .context import HandlerContext
|
|
19
|
+
|
|
20
|
+
log = structlog.stdlib.get_logger()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class KnowledgePipeline:
|
|
24
|
+
handler_context: HandlerContext
|
|
25
|
+
cache: Cache
|
|
26
|
+
identity: NodeIdentity
|
|
27
|
+
request_handler: RequestHandler
|
|
28
|
+
event_queue: EventQueue
|
|
29
|
+
graph: NetworkGraph
|
|
30
|
+
knowledge_handlers: list[KnowledgeHandler]
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
handler_context: HandlerContext,
|
|
35
|
+
cache: Cache,
|
|
36
|
+
request_handler: RequestHandler,
|
|
37
|
+
event_queue: EventQueue,
|
|
38
|
+
graph: NetworkGraph,
|
|
39
|
+
knowledge_handlers: list[KnowledgeHandler]
|
|
40
|
+
):
|
|
41
|
+
self.handler_context = handler_context
|
|
42
|
+
self.cache = cache
|
|
43
|
+
self.request_handler = request_handler
|
|
44
|
+
self.event_queue = event_queue
|
|
45
|
+
self.graph = graph
|
|
46
|
+
self.knowledge_handlers = knowledge_handlers
|
|
47
|
+
|
|
48
|
+
def call_handler_chain(
|
|
49
|
+
self,
|
|
50
|
+
handler_type: HandlerType,
|
|
51
|
+
kobj: KnowledgeObject
|
|
52
|
+
) -> KnowledgeObject | StopChain:
|
|
53
|
+
"""Calls handlers of provided type, chaining their inputs and outputs together.
|
|
54
|
+
|
|
55
|
+
The knowledge object provided when this function is called will be passed to the first handler. A handler may return one of three types:
|
|
56
|
+
- `KnowledgeObject` - to modify the knowledge object for the next handler in the chain
|
|
57
|
+
- `None` - to keep the same knowledge object for the next handler in the chain
|
|
58
|
+
- `STOP_CHAIN` - to stop the handler chain and immediately exit the processing pipeline
|
|
59
|
+
|
|
60
|
+
Handlers will only be called in the chain if their handler and RID type match that of the inputted knowledge object.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
for handler in self.knowledge_handlers:
|
|
64
|
+
if handler_type != handler.handler_type:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
if handler.rid_types and type(kobj.rid) not in handler.rid_types:
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if handler.event_types and kobj.event_type not in handler.event_types:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
log.debug(f"Calling {handler_type} handler '{handler.func.__name__}'")
|
|
74
|
+
|
|
75
|
+
resp = handler(
|
|
76
|
+
ctx=self.handler_context,
|
|
77
|
+
kobj=kobj.model_copy()
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# stops handler chain execution
|
|
81
|
+
if resp is STOP_CHAIN:
|
|
82
|
+
log.debug(f"Handler chain stopped by {handler.func.__name__}")
|
|
83
|
+
return STOP_CHAIN
|
|
84
|
+
|
|
85
|
+
# kobj unmodified
|
|
86
|
+
elif resp is None:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
# kobj modified by handler
|
|
90
|
+
elif isinstance(resp, KnowledgeObject):
|
|
91
|
+
kobj = resp
|
|
92
|
+
log.debug(f"Knowledge object modified by {handler.func.__name__}")
|
|
93
|
+
|
|
94
|
+
else:
|
|
95
|
+
raise ValueError(f"Handler {handler.func.__name__} returned invalid response '{resp}'")
|
|
96
|
+
|
|
97
|
+
return kobj
|
|
98
|
+
|
|
99
|
+
def process(self, kobj: KnowledgeObject):
|
|
100
|
+
"""Sends knowledge object through knowledge processing pipeline.
|
|
101
|
+
|
|
102
|
+
Handler chains are called in between major events in the
|
|
103
|
+
pipeline, indicated by their handler type. Each handler type is
|
|
104
|
+
guaranteed to have access to certain knowledge, and may affect a
|
|
105
|
+
subsequent action in the pipeline. The five handler types are as
|
|
106
|
+
follows:
|
|
107
|
+
- RID - provided RID; if event type is `FORGET`, this handler
|
|
108
|
+
decides whether to delete the knowledge from the cache by
|
|
109
|
+
setting the normalized event type to `FORGET`, otherwise this
|
|
110
|
+
handler decides whether to validate the manifest (and fetch it
|
|
111
|
+
if not provided). After processing, if event type is `FORGET`,
|
|
112
|
+
the manifest and contents will be retrieved from the local cache,
|
|
113
|
+
and indicate the last state of the knowledge before it was
|
|
114
|
+
deleted.
|
|
115
|
+
- Manifest - provided RID, manifest; decides whether to validate
|
|
116
|
+
the bundle (and fetch it if not provided).
|
|
117
|
+
- Bundle - provided RID, manifest, contents (bundle); decides
|
|
118
|
+
whether to write knowledge to the cache by setting the
|
|
119
|
+
normalized event type to `NEW` or `UPDATE`.
|
|
120
|
+
- Network - provided RID, manifest, contents (bundle); decides
|
|
121
|
+
which nodes (if any) to broadcast an event about this knowledge
|
|
122
|
+
to.
|
|
123
|
+
- Final - provided RID, manifests, contents (bundle); final
|
|
124
|
+
action taken after network broadcast.
|
|
125
|
+
|
|
126
|
+
The pipeline may be stopped by any point by a single handler
|
|
127
|
+
returning the `STOP_CHAIN` sentinel. In that case, the process
|
|
128
|
+
will exit immediately. Further handlers of that type and later
|
|
129
|
+
handler chains will not be called.
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
log.debug(f"Handling {kobj!r}")
|
|
133
|
+
kobj = self.call_handler_chain(HandlerType.RID, kobj)
|
|
134
|
+
if kobj is STOP_CHAIN: return
|
|
135
|
+
|
|
136
|
+
if kobj.event_type == EventType.FORGET:
|
|
137
|
+
bundle = self.cache.read(kobj.rid)
|
|
138
|
+
if not bundle:
|
|
139
|
+
log.debug("Local bundle not found")
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
# the bundle (to be deleted) attached to kobj for downstream analysis
|
|
143
|
+
log.debug("Adding local bundle (to be deleted) to knowledge object")
|
|
144
|
+
kobj.manifest = bundle.manifest
|
|
145
|
+
kobj.contents = bundle.contents
|
|
146
|
+
|
|
147
|
+
else:
|
|
148
|
+
# attempt to retrieve manifest
|
|
149
|
+
if not kobj.manifest:
|
|
150
|
+
log.debug("Manifest not found")
|
|
151
|
+
if not kobj.source:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
log.debug("Attempting to fetch remote manifest from source")
|
|
155
|
+
try:
|
|
156
|
+
payload = self.request_handler.fetch_manifests(
|
|
157
|
+
node=kobj.source,
|
|
158
|
+
rids=[kobj.rid])
|
|
159
|
+
except RequestError:
|
|
160
|
+
log.debug("Failed to find manifest")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
kobj.manifest = payload.manifests[0]
|
|
164
|
+
|
|
165
|
+
kobj = self.call_handler_chain(HandlerType.Manifest, kobj)
|
|
166
|
+
if kobj is STOP_CHAIN: return
|
|
167
|
+
|
|
168
|
+
# attempt to retrieve bundle
|
|
169
|
+
if not kobj.contents:
|
|
170
|
+
log.debug("Bundle not found")
|
|
171
|
+
if kobj.source is None:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
log.debug("Attempting to fetch remote bundle from source")
|
|
175
|
+
try:
|
|
176
|
+
payload = self.request_handler.fetch_bundles(
|
|
177
|
+
node=kobj.source,
|
|
178
|
+
rids=[kobj.rid]
|
|
179
|
+
)
|
|
180
|
+
except RequestError:
|
|
181
|
+
log.debug("Failed to find bundle")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
bundle = payload.bundles[0]
|
|
185
|
+
|
|
186
|
+
if kobj.manifest != bundle.manifest:
|
|
187
|
+
log.warning("Retrieved bundle contains a different manifest")
|
|
188
|
+
|
|
189
|
+
kobj.manifest = bundle.manifest
|
|
190
|
+
kobj.contents = bundle.contents
|
|
191
|
+
|
|
192
|
+
kobj = self.call_handler_chain(HandlerType.Bundle, kobj)
|
|
193
|
+
if kobj is STOP_CHAIN: return
|
|
194
|
+
|
|
195
|
+
if kobj.normalized_event_type in (EventType.UPDATE, EventType.NEW):
|
|
196
|
+
log.info(f"Writing to cache: {kobj!r}")
|
|
197
|
+
self.cache.write(kobj.bundle)
|
|
198
|
+
|
|
199
|
+
elif kobj.normalized_event_type == EventType.FORGET:
|
|
200
|
+
log.info(f"Deleting from cache: {kobj!r}")
|
|
201
|
+
self.cache.delete(kobj.rid)
|
|
202
|
+
|
|
203
|
+
else:
|
|
204
|
+
log.debug("Normalized event type was not set, no cache or network operations will occur")
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
if type(kobj.rid) in (KoiNetNode, KoiNetEdge):
|
|
208
|
+
log.debug("Change to node or edge, regenerating network graph")
|
|
209
|
+
self.graph.generate()
|
|
210
|
+
|
|
211
|
+
kobj = self.call_handler_chain(HandlerType.Network, kobj)
|
|
212
|
+
if kobj is STOP_CHAIN: return
|
|
213
|
+
|
|
214
|
+
if kobj.network_targets:
|
|
215
|
+
log.debug(f"Broadcasting event to {len(kobj.network_targets)} network target(s)")
|
|
216
|
+
else:
|
|
217
|
+
log.debug("No network targets set")
|
|
218
|
+
|
|
219
|
+
for node in kobj.network_targets:
|
|
220
|
+
self.event_queue.push(kobj.normalized_event, node)
|
|
221
|
+
|
|
222
|
+
kobj = self.call_handler_chain(HandlerType.Final, kobj)
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Pydantic models for request and response objects in the KOI-net API."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from rid_lib import RID, RIDType
|
|
6
|
+
from rid_lib.ext import Bundle, Manifest
|
|
7
|
+
from .event import Event
|
|
8
|
+
from .errors import ErrorType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# REQUEST MODELS
|
|
12
|
+
|
|
13
|
+
class PollEvents(BaseModel):
|
|
14
|
+
type: Literal["poll_events"] = Field("poll_events")
|
|
15
|
+
limit: int = 0
|
|
16
|
+
|
|
17
|
+
class FetchRids(BaseModel):
|
|
18
|
+
type: Literal["fetch_rids"] = Field("fetch_rids")
|
|
19
|
+
rid_types: list[RIDType] = []
|
|
20
|
+
|
|
21
|
+
class FetchManifests(BaseModel):
|
|
22
|
+
type: Literal["fetch_manifests"] = Field("fetch_manifests")
|
|
23
|
+
rid_types: list[RIDType] = []
|
|
24
|
+
rids: list[RID] = []
|
|
25
|
+
|
|
26
|
+
class FetchBundles(BaseModel):
|
|
27
|
+
type: Literal["fetch_bundles"] = Field("fetch_bundles")
|
|
28
|
+
rids: list[RID]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# RESPONSE/PAYLOAD MODELS
|
|
32
|
+
|
|
33
|
+
class RidsPayload(BaseModel):
|
|
34
|
+
type: Literal["rids_payload"] = Field("rids_payload")
|
|
35
|
+
rids: list[RID]
|
|
36
|
+
|
|
37
|
+
class ManifestsPayload(BaseModel):
|
|
38
|
+
type: Literal["manifests_payload"] = Field("manifests_payload")
|
|
39
|
+
manifests: list[Manifest]
|
|
40
|
+
not_found: list[RID] = []
|
|
41
|
+
|
|
42
|
+
class BundlesPayload(BaseModel):
|
|
43
|
+
type: Literal["bundles_payload"] = Field("bundles_payload")
|
|
44
|
+
bundles: list[Bundle]
|
|
45
|
+
not_found: list[RID] = []
|
|
46
|
+
deferred: list[RID] = []
|
|
47
|
+
|
|
48
|
+
class EventsPayload(BaseModel):
|
|
49
|
+
type: Literal["events_payload"] = Field("events_payload")
|
|
50
|
+
events: list[Event]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ERROR MODELS
|
|
54
|
+
|
|
55
|
+
class ErrorResponse(BaseModel):
|
|
56
|
+
type: Literal["error_response"] = Field("error_response")
|
|
57
|
+
error: ErrorType
|
|
58
|
+
|
|
59
|
+
# TYPES
|
|
60
|
+
|
|
61
|
+
type RequestModels = EventsPayload | PollEvents | FetchRids | FetchManifests | FetchBundles
|
|
62
|
+
type ResponseModels = RidsPayload | ManifestsPayload | BundlesPayload | EventsPayload | ErrorResponse
|
|
63
|
+
|
|
64
|
+
type ApiModels = Annotated[
|
|
65
|
+
RequestModels | ResponseModels,
|
|
66
|
+
Field(discriminator="type")
|
|
67
|
+
]
|
koi_net/protocol/edge.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from rid_lib import RIDType
|
|
4
|
+
from rid_lib.ext.bundle import Bundle
|
|
5
|
+
from rid_lib.ext.utils import sha256_hash
|
|
6
|
+
from rid_lib.types import KoiNetEdge, KoiNetNode
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EdgeStatus(StrEnum):
|
|
10
|
+
PROPOSED = "PROPOSED"
|
|
11
|
+
APPROVED = "APPROVED"
|
|
12
|
+
|
|
13
|
+
class EdgeType(StrEnum):
|
|
14
|
+
WEBHOOK = "WEBHOOK"
|
|
15
|
+
POLL = "POLL"
|
|
16
|
+
|
|
17
|
+
class EdgeProfile(BaseModel):
|
|
18
|
+
source: KoiNetNode
|
|
19
|
+
target: KoiNetNode
|
|
20
|
+
edge_type: EdgeType
|
|
21
|
+
status: EdgeStatus
|
|
22
|
+
rid_types: list[RIDType]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def generate_edge_bundle(
|
|
26
|
+
source: KoiNetNode,
|
|
27
|
+
target: KoiNetNode,
|
|
28
|
+
rid_types: list[RIDType],
|
|
29
|
+
edge_type: EdgeType
|
|
30
|
+
) -> Bundle:
|
|
31
|
+
"""Returns edge bundle."""
|
|
32
|
+
|
|
33
|
+
edge_rid = KoiNetEdge(sha256_hash(
|
|
34
|
+
str(source) + str(target)
|
|
35
|
+
))
|
|
36
|
+
|
|
37
|
+
edge_profile = EdgeProfile(
|
|
38
|
+
source=source,
|
|
39
|
+
target=target,
|
|
40
|
+
rid_types=rid_types,
|
|
41
|
+
edge_type=edge_type,
|
|
42
|
+
status=EdgeStatus.PROPOSED
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
edge_bundle = Bundle.generate(
|
|
46
|
+
edge_rid,
|
|
47
|
+
edge_profile.model_dump()
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return edge_bundle
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
from typing import Generic, TypeVar
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
from rid_lib.types import KoiNetNode
|
|
5
|
+
|
|
6
|
+
from .secure import PrivateKey, PublicKey
|
|
7
|
+
from .api_models import RequestModels, ResponseModels
|
|
8
|
+
|
|
9
|
+
log = structlog.stdlib.get_logger()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T", bound=RequestModels | ResponseModels)
|
|
13
|
+
|
|
14
|
+
class SignedEnvelope(BaseModel, Generic[T]):
|
|
15
|
+
payload: T
|
|
16
|
+
source_node: KoiNetNode
|
|
17
|
+
target_node: KoiNetNode
|
|
18
|
+
signature: str
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(exclude_none=True)
|
|
21
|
+
|
|
22
|
+
def verify_with(self, pub_key: PublicKey):
|
|
23
|
+
"""Verifies signed envelope with public key.
|
|
24
|
+
|
|
25
|
+
Raises `cryptography.exceptions.InvalidSignature` on failure.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# IMPORTANT: calling `model_dump()` loses all typing! when converting between SignedEnvelope and UnsignedEnvelope, use the Pydantic classes, not the dictionary form
|
|
29
|
+
|
|
30
|
+
unsigned_envelope = UnsignedEnvelope[T](
|
|
31
|
+
payload=self.payload,
|
|
32
|
+
source_node=self.source_node,
|
|
33
|
+
target_node=self.target_node
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
log.debug(f"Verifying envelope: {unsigned_envelope.model_dump_json(exclude_none=True)}")
|
|
37
|
+
|
|
38
|
+
pub_key.verify(
|
|
39
|
+
self.signature,
|
|
40
|
+
unsigned_envelope.model_dump_json(exclude_none=True).encode()
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
class UnsignedEnvelope(BaseModel, Generic[T]):
|
|
44
|
+
payload: T
|
|
45
|
+
source_node: KoiNetNode
|
|
46
|
+
target_node: KoiNetNode
|
|
47
|
+
|
|
48
|
+
model_config = ConfigDict(exclude_none=True)
|
|
49
|
+
|
|
50
|
+
def sign_with(self, priv_key: PrivateKey) -> SignedEnvelope[T]:
|
|
51
|
+
"""Signs with private key and returns `SignedEnvelope`."""
|
|
52
|
+
|
|
53
|
+
log.debug(f"Signing envelope: {self.model_dump_json(exclude_none=True)}")
|
|
54
|
+
log.debug(f"Type: [{type(self.payload)}]")
|
|
55
|
+
|
|
56
|
+
signature = priv_key.sign(
|
|
57
|
+
self.model_dump_json(exclude_none=True).encode()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return SignedEnvelope(
|
|
61
|
+
payload=self.payload,
|
|
62
|
+
source_node=self.source_node,
|
|
63
|
+
target_node=self.target_node,
|
|
64
|
+
signature=signature
|
|
65
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Defines KOI-net protocol errors."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from ..exceptions import (
|
|
5
|
+
ProtocolError,
|
|
6
|
+
UnknownNodeError,
|
|
7
|
+
InvalidKeyError,
|
|
8
|
+
InvalidSignatureError,
|
|
9
|
+
InvalidTargetError
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ErrorType(StrEnum):
|
|
14
|
+
UnknownNode = "unknown_node"
|
|
15
|
+
InvalidKey = "invalid_key"
|
|
16
|
+
InvalidSignature = "invalid_signature"
|
|
17
|
+
InvalidTarget = "invalid_target"
|
|
18
|
+
|
|
19
|
+
EXCEPTION_TO_ERROR_TYPE: dict[ProtocolError, ErrorType] = {
|
|
20
|
+
UnknownNodeError: ErrorType.UnknownNode,
|
|
21
|
+
InvalidKeyError: ErrorType.InvalidKey,
|
|
22
|
+
InvalidSignatureError: ErrorType.InvalidSignature,
|
|
23
|
+
InvalidTargetError: ErrorType.InvalidTarget
|
|
24
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from rid_lib import RID
|
|
4
|
+
from rid_lib.ext import Manifest, Bundle
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EventType(StrEnum):
|
|
8
|
+
NEW = "NEW"
|
|
9
|
+
UPDATE = "UPDATE"
|
|
10
|
+
FORGET = "FORGET"
|
|
11
|
+
|
|
12
|
+
class Event(BaseModel):
|
|
13
|
+
rid: RID
|
|
14
|
+
event_type: EventType
|
|
15
|
+
manifest: Manifest | None = None
|
|
16
|
+
contents: dict | None = None
|
|
17
|
+
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return f"<Event '{self.rid}' event type: '{self.event_type}'>"
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_bundle(cls, event_type: EventType, bundle: Bundle):
|
|
23
|
+
return cls(
|
|
24
|
+
rid=bundle.manifest.rid,
|
|
25
|
+
event_type=event_type,
|
|
26
|
+
manifest=bundle.manifest,
|
|
27
|
+
contents=bundle.contents
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_manifest(cls, event_type: EventType, manifest: Manifest):
|
|
32
|
+
return cls(
|
|
33
|
+
rid=manifest.rid,
|
|
34
|
+
event_type=event_type,
|
|
35
|
+
manifest=manifest
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_rid(cls, event_type: EventType, rid: RID):
|
|
40
|
+
return cls(
|
|
41
|
+
rid=rid,
|
|
42
|
+
event_type=event_type
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def bundle(self):
|
|
47
|
+
if self.manifest is not None and self.contents is not None:
|
|
48
|
+
return Bundle(
|
|
49
|
+
manifest=self.manifest,
|
|
50
|
+
contents=self.contents
|
|
51
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import NamedTuple
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from .envelope import SignedEnvelope
|
|
4
|
+
from .consts import (
|
|
5
|
+
BROADCAST_EVENTS_PATH,
|
|
6
|
+
POLL_EVENTS_PATH,
|
|
7
|
+
FETCH_BUNDLES_PATH,
|
|
8
|
+
FETCH_MANIFESTS_PATH,
|
|
9
|
+
FETCH_RIDS_PATH
|
|
10
|
+
)
|
|
11
|
+
from .api_models import (
|
|
12
|
+
EventsPayload,
|
|
13
|
+
PollEvents,
|
|
14
|
+
FetchBundles,
|
|
15
|
+
BundlesPayload,
|
|
16
|
+
FetchManifests,
|
|
17
|
+
ManifestsPayload,
|
|
18
|
+
FetchRids,
|
|
19
|
+
RidsPayload
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Models(NamedTuple):
|
|
24
|
+
request: type[BaseModel]
|
|
25
|
+
response: type[BaseModel] | None
|
|
26
|
+
request_envelope: type[SignedEnvelope]
|
|
27
|
+
response_envelope: type[SignedEnvelope] | None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# maps API paths to request and response models
|
|
31
|
+
API_MODEL_MAP: dict[str, Models] = {
|
|
32
|
+
BROADCAST_EVENTS_PATH: Models(
|
|
33
|
+
request=EventsPayload,
|
|
34
|
+
response=None,
|
|
35
|
+
request_envelope=SignedEnvelope[EventsPayload],
|
|
36
|
+
response_envelope=None
|
|
37
|
+
),
|
|
38
|
+
POLL_EVENTS_PATH: Models(
|
|
39
|
+
request=PollEvents,
|
|
40
|
+
response=EventsPayload,
|
|
41
|
+
request_envelope=SignedEnvelope[PollEvents],
|
|
42
|
+
response_envelope=SignedEnvelope[EventsPayload]
|
|
43
|
+
),
|
|
44
|
+
FETCH_BUNDLES_PATH: Models(
|
|
45
|
+
request=FetchBundles,
|
|
46
|
+
response=BundlesPayload,
|
|
47
|
+
request_envelope=SignedEnvelope[FetchBundles],
|
|
48
|
+
response_envelope=SignedEnvelope[BundlesPayload]
|
|
49
|
+
),
|
|
50
|
+
FETCH_MANIFESTS_PATH: Models(
|
|
51
|
+
request=FetchManifests,
|
|
52
|
+
response=ManifestsPayload,
|
|
53
|
+
request_envelope=SignedEnvelope[FetchManifests],
|
|
54
|
+
response_envelope=SignedEnvelope[ManifestsPayload]
|
|
55
|
+
),
|
|
56
|
+
FETCH_RIDS_PATH: Models(
|
|
57
|
+
request=FetchRids,
|
|
58
|
+
response=RidsPayload,
|
|
59
|
+
request_envelope=SignedEnvelope[FetchRids],
|
|
60
|
+
response_envelope=SignedEnvelope[RidsPayload]
|
|
61
|
+
)
|
|
62
|
+
}
|
koi_net/protocol/node.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from rid_lib import RIDType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NodeType(StrEnum):
|
|
7
|
+
FULL = "FULL"
|
|
8
|
+
PARTIAL = "PARTIAL"
|
|
9
|
+
|
|
10
|
+
class NodeProvides(BaseModel):
|
|
11
|
+
event: list[RIDType] = []
|
|
12
|
+
state: list[RIDType] = []
|
|
13
|
+
|
|
14
|
+
class NodeProfile(BaseModel):
|
|
15
|
+
base_url: str | None = None
|
|
16
|
+
node_type: NodeType
|
|
17
|
+
provides: NodeProvides = NodeProvides()
|
|
18
|
+
public_key: str | None = None
|