koi-net 1.2.0b1__py3-none-any.whl → 1.2.0b2__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 +2 -1
- koi_net/assembler.py +82 -0
- koi_net/context.py +5 -13
- koi_net/core.py +51 -206
- koi_net/effector.py +26 -14
- koi_net/handshaker.py +3 -3
- koi_net/identity.py +2 -3
- koi_net/interfaces/entrypoint.py +5 -0
- koi_net/{worker.py → interfaces/worker.py} +7 -0
- koi_net/lifecycle.py +40 -32
- koi_net/logger.py +176 -0
- koi_net/network/error_handler.py +6 -6
- koi_net/network/event_queue.py +8 -6
- koi_net/network/graph.py +8 -8
- koi_net/network/poll_event_buffer.py +26 -0
- koi_net/network/request_handler.py +23 -28
- koi_net/network/resolver.py +13 -13
- koi_net/network/response_handler.py +74 -9
- koi_net/poller.py +7 -5
- koi_net/processor/event_worker.py +14 -18
- koi_net/processor/{default_handlers.py → handlers.py} +26 -25
- koi_net/processor/kobj_queue.py +3 -3
- koi_net/{kobj_worker.py → processor/kobj_worker.py} +12 -13
- koi_net/processor/{knowledge_pipeline.py → pipeline.py} +24 -27
- koi_net/protocol/api_models.py +5 -2
- koi_net/protocol/envelope.py +5 -6
- koi_net/protocol/model_map.py +61 -0
- koi_net/protocol/secure.py +8 -8
- koi_net/secure.py +5 -5
- koi_net/sentry.py +13 -0
- koi_net/server.py +36 -86
- {koi_net-1.2.0b1.dist-info → koi_net-1.2.0b2.dist-info}/METADATA +2 -1
- koi_net-1.2.0b2.dist-info/RECORD +52 -0
- koi_net/behaviors.py +0 -51
- koi_net/models.py +0 -14
- koi_net/poll_event_buffer.py +0 -17
- koi_net-1.2.0b1.dist-info/RECORD +0 -49
- {koi_net-1.2.0b1.dist-info → koi_net-1.2.0b2.dist-info}/WHEEL +0 -0
- {koi_net-1.2.0b1.dist-info → koi_net-1.2.0b2.dist-info}/entry_points.txt +0 -0
- {koi_net-1.2.0b1.dist-info → koi_net-1.2.0b2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
from rid_lib.types import KoiNetEdge, KoiNetNode
|
|
3
3
|
from rid_lib.ext import Cache
|
|
4
4
|
from ..protocol.event import EventType
|
|
@@ -13,16 +13,13 @@ from .handler import (
|
|
|
13
13
|
StopChain
|
|
14
14
|
)
|
|
15
15
|
from .knowledge_object import KnowledgeObject
|
|
16
|
+
from ..context import HandlerContext
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
if TYPE_CHECKING:
|
|
19
|
-
from ..context import HandlerContext
|
|
20
|
-
|
|
21
|
-
logger = logging.getLogger(__name__)
|
|
18
|
+
log = structlog.stdlib.get_logger()
|
|
22
19
|
|
|
23
20
|
|
|
24
21
|
class KnowledgePipeline:
|
|
25
|
-
handler_context:
|
|
22
|
+
handler_context: HandlerContext
|
|
26
23
|
cache: Cache
|
|
27
24
|
identity: NodeIdentity
|
|
28
25
|
request_handler: RequestHandler
|
|
@@ -32,7 +29,7 @@ class KnowledgePipeline:
|
|
|
32
29
|
|
|
33
30
|
def __init__(
|
|
34
31
|
self,
|
|
35
|
-
handler_context:
|
|
32
|
+
handler_context: HandlerContext,
|
|
36
33
|
cache: Cache,
|
|
37
34
|
request_handler: RequestHandler,
|
|
38
35
|
event_queue: EventQueue,
|
|
@@ -71,7 +68,7 @@ class KnowledgePipeline:
|
|
|
71
68
|
if handler.event_types and kobj.event_type not in handler.event_types:
|
|
72
69
|
continue
|
|
73
70
|
|
|
74
|
-
|
|
71
|
+
log.debug(f"Calling {handler_type} handler '{handler.func.__name__}'")
|
|
75
72
|
|
|
76
73
|
resp = handler.func(
|
|
77
74
|
ctx=self.handler_context,
|
|
@@ -80,7 +77,7 @@ class KnowledgePipeline:
|
|
|
80
77
|
|
|
81
78
|
# stops handler chain execution
|
|
82
79
|
if resp is STOP_CHAIN:
|
|
83
|
-
|
|
80
|
+
log.debug(f"Handler chain stopped by {handler.func.__name__}")
|
|
84
81
|
return STOP_CHAIN
|
|
85
82
|
# kobj unmodified
|
|
86
83
|
elif resp is None:
|
|
@@ -88,7 +85,7 @@ class KnowledgePipeline:
|
|
|
88
85
|
# kobj modified by handler
|
|
89
86
|
elif isinstance(resp, KnowledgeObject):
|
|
90
87
|
kobj = resp
|
|
91
|
-
|
|
88
|
+
log.debug(f"Knowledge object modified by {handler.func.__name__}")
|
|
92
89
|
else:
|
|
93
90
|
raise ValueError(f"Handler {handler.func.__name__} returned invalid response '{resp}'")
|
|
94
91
|
|
|
@@ -107,36 +104,36 @@ class KnowledgePipeline:
|
|
|
107
104
|
The pipeline may be stopped by any point by a single handler returning the `STOP_CHAIN` sentinel. In that case, the process will exit immediately. Further handlers of that type and later handler chains will not be called.
|
|
108
105
|
"""
|
|
109
106
|
|
|
110
|
-
|
|
107
|
+
log.debug(f"Handling {kobj!r}")
|
|
111
108
|
kobj = self.call_handler_chain(HandlerType.RID, kobj)
|
|
112
109
|
if kobj is STOP_CHAIN: return
|
|
113
110
|
|
|
114
111
|
if kobj.event_type == EventType.FORGET:
|
|
115
112
|
bundle = self.cache.read(kobj.rid)
|
|
116
113
|
if not bundle:
|
|
117
|
-
|
|
114
|
+
log.debug("Local bundle not found")
|
|
118
115
|
return
|
|
119
116
|
|
|
120
117
|
# the bundle (to be deleted) attached to kobj for downstream analysis
|
|
121
|
-
|
|
118
|
+
log.debug("Adding local bundle (to be deleted) to knowledge object")
|
|
122
119
|
kobj.manifest = bundle.manifest
|
|
123
120
|
kobj.contents = bundle.contents
|
|
124
121
|
|
|
125
122
|
else:
|
|
126
123
|
# attempt to retrieve manifest
|
|
127
124
|
if not kobj.manifest:
|
|
128
|
-
|
|
125
|
+
log.debug("Manifest not found")
|
|
129
126
|
if not kobj.source:
|
|
130
127
|
return
|
|
131
128
|
|
|
132
|
-
|
|
129
|
+
log.debug("Attempting to fetch remote manifest from source")
|
|
133
130
|
payload = self.request_handler.fetch_manifests(
|
|
134
131
|
node=kobj.source,
|
|
135
132
|
rids=[kobj.rid]
|
|
136
133
|
)
|
|
137
134
|
|
|
138
135
|
if not payload.manifests:
|
|
139
|
-
|
|
136
|
+
log.debug("Failed to find manifest")
|
|
140
137
|
return
|
|
141
138
|
|
|
142
139
|
kobj.manifest = payload.manifests[0]
|
|
@@ -146,24 +143,24 @@ class KnowledgePipeline:
|
|
|
146
143
|
|
|
147
144
|
# attempt to retrieve bundle
|
|
148
145
|
if not kobj.bundle:
|
|
149
|
-
|
|
146
|
+
log.debug("Bundle not found")
|
|
150
147
|
if kobj.source is None:
|
|
151
148
|
return
|
|
152
149
|
|
|
153
|
-
|
|
150
|
+
log.debug("Attempting to fetch remote bundle from source")
|
|
154
151
|
payload = self.request_handler.fetch_bundles(
|
|
155
152
|
node=kobj.source,
|
|
156
153
|
rids=[kobj.rid]
|
|
157
154
|
)
|
|
158
155
|
|
|
159
156
|
if not payload.bundles:
|
|
160
|
-
|
|
157
|
+
log.debug("Failed to find bundle")
|
|
161
158
|
return
|
|
162
159
|
|
|
163
160
|
bundle = payload.bundles[0]
|
|
164
161
|
|
|
165
162
|
if kobj.manifest != bundle.manifest:
|
|
166
|
-
|
|
163
|
+
log.warning("Retrieved bundle contains a different manifest")
|
|
167
164
|
|
|
168
165
|
kobj.manifest = bundle.manifest
|
|
169
166
|
kobj.contents = bundle.contents
|
|
@@ -172,28 +169,28 @@ class KnowledgePipeline:
|
|
|
172
169
|
if kobj is STOP_CHAIN: return
|
|
173
170
|
|
|
174
171
|
if kobj.normalized_event_type in (EventType.UPDATE, EventType.NEW):
|
|
175
|
-
|
|
172
|
+
log.info(f"Writing to cache: {kobj!r}")
|
|
176
173
|
self.cache.write(kobj.bundle)
|
|
177
174
|
|
|
178
175
|
elif kobj.normalized_event_type == EventType.FORGET:
|
|
179
|
-
|
|
176
|
+
log.info(f"Deleting from cache: {kobj!r}")
|
|
180
177
|
self.cache.delete(kobj.rid)
|
|
181
178
|
|
|
182
179
|
else:
|
|
183
|
-
|
|
180
|
+
log.debug("Normalized event type was never set, no cache or network operations will occur")
|
|
184
181
|
return
|
|
185
182
|
|
|
186
183
|
if type(kobj.rid) in (KoiNetNode, KoiNetEdge):
|
|
187
|
-
|
|
184
|
+
log.debug("Change to node or edge, regenerating network graph")
|
|
188
185
|
self.graph.generate()
|
|
189
186
|
|
|
190
187
|
kobj = self.call_handler_chain(HandlerType.Network, kobj)
|
|
191
188
|
if kobj is STOP_CHAIN: return
|
|
192
189
|
|
|
193
190
|
if kobj.network_targets:
|
|
194
|
-
|
|
191
|
+
log.debug(f"Broadcasting event to {len(kobj.network_targets)} network target(s)")
|
|
195
192
|
else:
|
|
196
|
-
|
|
193
|
+
log.debug("No network targets set")
|
|
197
194
|
|
|
198
195
|
for node in kobj.network_targets:
|
|
199
196
|
self.event_queue.push_event_to(kobj.normalized_event, node)
|
koi_net/protocol/api_models.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Pydantic models for request and response objects in the KOI-net API."""
|
|
2
2
|
|
|
3
|
-
from typing import Literal
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
from rid_lib import RID, RIDType
|
|
6
6
|
from rid_lib.ext import Bundle, Manifest
|
|
@@ -60,4 +60,7 @@ class ErrorResponse(BaseModel):
|
|
|
60
60
|
|
|
61
61
|
type RequestModels = EventsPayload | PollEvents | FetchRids | FetchManifests | FetchBundles
|
|
62
62
|
type ResponseModels = RidsPayload | ManifestsPayload | BundlesPayload | EventsPayload | ErrorResponse
|
|
63
|
-
type ApiModels =
|
|
63
|
+
type ApiModels = Annotated[
|
|
64
|
+
RequestModels | ResponseModels,
|
|
65
|
+
Field(discriminator="type")
|
|
66
|
+
]
|
koi_net/protocol/envelope.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
from typing import Generic, TypeVar
|
|
3
3
|
from pydantic import BaseModel, ConfigDict
|
|
4
4
|
from rid_lib.types import KoiNetNode
|
|
@@ -6,8 +6,7 @@ from rid_lib.types import KoiNetNode
|
|
|
6
6
|
from .secure import PrivateKey, PublicKey
|
|
7
7
|
from .api_models import RequestModels, ResponseModels
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
9
|
+
log = structlog.stdlib.get_logger()
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
T = TypeVar("T", bound=RequestModels | ResponseModels)
|
|
@@ -28,7 +27,7 @@ class SignedEnvelope(BaseModel, Generic[T]):
|
|
|
28
27
|
target_node=self.target_node
|
|
29
28
|
)
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
log.debug(f"Verifying envelope: {unsigned_envelope.model_dump_json(exclude_none=True)}")
|
|
32
31
|
|
|
33
32
|
pub_key.verify(
|
|
34
33
|
self.signature,
|
|
@@ -43,8 +42,8 @@ class UnsignedEnvelope(BaseModel, Generic[T]):
|
|
|
43
42
|
target_node: KoiNetNode
|
|
44
43
|
|
|
45
44
|
def sign_with(self, priv_key: PrivateKey) -> SignedEnvelope[T]:
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
log.debug(f"Signing envelope: {self.model_dump_json(exclude_none=True)}")
|
|
46
|
+
log.debug(f"Type: [{type(self.payload)}]")
|
|
48
47
|
|
|
49
48
|
signature = priv_key.sign(
|
|
50
49
|
self.model_dump_json(exclude_none=True).encode()
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
API_MODEL_MAP: dict[str, Models] = {
|
|
31
|
+
BROADCAST_EVENTS_PATH: Models(
|
|
32
|
+
request=EventsPayload,
|
|
33
|
+
response=None,
|
|
34
|
+
request_envelope=SignedEnvelope[EventsPayload],
|
|
35
|
+
response_envelope=None
|
|
36
|
+
),
|
|
37
|
+
POLL_EVENTS_PATH: Models(
|
|
38
|
+
request=PollEvents,
|
|
39
|
+
response=EventsPayload,
|
|
40
|
+
request_envelope=SignedEnvelope[PollEvents],
|
|
41
|
+
response_envelope=SignedEnvelope[EventsPayload]
|
|
42
|
+
),
|
|
43
|
+
FETCH_BUNDLES_PATH: Models(
|
|
44
|
+
request=FetchBundles,
|
|
45
|
+
response=BundlesPayload,
|
|
46
|
+
request_envelope=SignedEnvelope[FetchBundles],
|
|
47
|
+
response_envelope=SignedEnvelope[BundlesPayload]
|
|
48
|
+
),
|
|
49
|
+
FETCH_MANIFESTS_PATH: Models(
|
|
50
|
+
request=FetchManifests,
|
|
51
|
+
response=ManifestsPayload,
|
|
52
|
+
request_envelope=SignedEnvelope[FetchManifests],
|
|
53
|
+
response_envelope=SignedEnvelope[ManifestsPayload]
|
|
54
|
+
),
|
|
55
|
+
FETCH_RIDS_PATH: Models(
|
|
56
|
+
request=FetchRids,
|
|
57
|
+
response=RidsPayload,
|
|
58
|
+
request_envelope=SignedEnvelope[FetchRids],
|
|
59
|
+
response_envelope=SignedEnvelope[RidsPayload]
|
|
60
|
+
)
|
|
61
|
+
}
|
koi_net/protocol/secure.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
from base64 import b64decode, b64encode
|
|
3
3
|
from cryptography.hazmat.primitives import hashes
|
|
4
4
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
@@ -9,7 +9,7 @@ from cryptography.hazmat.primitives.asymmetric.utils import (
|
|
|
9
9
|
encode_dss_signature
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
log = structlog.stdlib.get_logger()
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def der_to_raw_signature(der_signature: bytes, curve=ec.SECP256R1()) -> bytes:
|
|
@@ -91,9 +91,9 @@ class PrivateKey:
|
|
|
91
91
|
|
|
92
92
|
signature = b64encode(raw_signature_bytes).decode()
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
log.debug(f"Signing message with [{self.public_key().to_der()}]")
|
|
95
|
+
log.debug(f"hash: {hashed_message}")
|
|
96
|
+
log.debug(f"signature: {signature}")
|
|
97
97
|
|
|
98
98
|
return signature
|
|
99
99
|
|
|
@@ -144,9 +144,9 @@ class PublicKey:
|
|
|
144
144
|
# print()
|
|
145
145
|
# print(message.decode())
|
|
146
146
|
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
147
|
+
# log.debug(f"Verifying message with [{self.to_der()}]")
|
|
148
|
+
# log.debug(f"hash: {hashed_message}")
|
|
149
|
+
# log.debug(f"signature: {signature}")
|
|
150
150
|
|
|
151
151
|
raw_signature_bytes = b64decode(signature)
|
|
152
152
|
der_signature_bytes = raw_to_der_signature(raw_signature_bytes)
|
koi_net/secure.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
from functools import wraps
|
|
3
3
|
|
|
4
4
|
import cryptography.exceptions
|
|
@@ -20,7 +20,7 @@ from .protocol.errors import (
|
|
|
20
20
|
)
|
|
21
21
|
from .config import NodeConfig
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
log = structlog.stdlib.get_logger()
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class Secure:
|
|
@@ -120,15 +120,15 @@ class Secure:
|
|
|
120
120
|
"""
|
|
121
121
|
@wraps(func)
|
|
122
122
|
async def wrapper(req: SignedEnvelope, *args, **kwargs) -> SignedEnvelope | None:
|
|
123
|
-
|
|
123
|
+
log.info("Validating envelope")
|
|
124
124
|
|
|
125
125
|
self.validate_envelope(req)
|
|
126
|
-
|
|
126
|
+
log.info("Calling endpoint handler")
|
|
127
127
|
|
|
128
128
|
result = await func(req, *args, **kwargs)
|
|
129
129
|
|
|
130
130
|
if result is not None:
|
|
131
|
-
|
|
131
|
+
log.info("Creating response envelope")
|
|
132
132
|
return self.create_envelope(
|
|
133
133
|
payload=result,
|
|
134
134
|
target=req.source_node
|
koi_net/sentry.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# import sentry_sdk
|
|
2
|
+
# from sentry_sdk.integrations.logging import LoggingIntegration
|
|
3
|
+
|
|
4
|
+
# sentry_sdk.init(
|
|
5
|
+
# dsn="https://7bbafef3c7dbd652506db3cb2aca9f98@o4510149352357888.ingest.us.sentry.io/4510149355765760",
|
|
6
|
+
# # Add data like request headers and IP for users,
|
|
7
|
+
# # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
|
|
8
|
+
# send_default_pii=True,
|
|
9
|
+
# enable_logs=True,
|
|
10
|
+
# integrations=[
|
|
11
|
+
# LoggingIntegration(sentry_logs_level=None)
|
|
12
|
+
# ]
|
|
13
|
+
# )
|
koi_net/server.py
CHANGED
|
@@ -1,46 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
import uvicorn
|
|
3
3
|
from contextlib import asynccontextmanager
|
|
4
4
|
from fastapi import FastAPI, APIRouter
|
|
5
5
|
from fastapi.responses import JSONResponse
|
|
6
6
|
|
|
7
|
-
from koi_net.
|
|
7
|
+
from koi_net.interfaces.entrypoint import EntryPoint
|
|
8
|
+
|
|
8
9
|
from .network.response_handler import ResponseHandler
|
|
9
|
-
from .
|
|
10
|
-
from .protocol.api_models import
|
|
11
|
-
PollEvents,
|
|
12
|
-
FetchRids,
|
|
13
|
-
FetchManifests,
|
|
14
|
-
FetchBundles,
|
|
15
|
-
EventsPayload,
|
|
16
|
-
RidsPayload,
|
|
17
|
-
ManifestsPayload,
|
|
18
|
-
BundlesPayload,
|
|
19
|
-
ErrorResponse
|
|
20
|
-
)
|
|
10
|
+
from .protocol.model_map import API_MODEL_MAP
|
|
11
|
+
from .protocol.api_models import ErrorResponse
|
|
21
12
|
from .protocol.errors import ProtocolError
|
|
22
|
-
from .protocol.envelope import SignedEnvelope
|
|
23
|
-
from .protocol.consts import (
|
|
24
|
-
BROADCAST_EVENTS_PATH,
|
|
25
|
-
POLL_EVENTS_PATH,
|
|
26
|
-
FETCH_RIDS_PATH,
|
|
27
|
-
FETCH_MANIFESTS_PATH,
|
|
28
|
-
FETCH_BUNDLES_PATH
|
|
29
|
-
)
|
|
30
|
-
from .secure import Secure
|
|
31
13
|
from .lifecycle import NodeLifecycle
|
|
32
14
|
from .config import NodeConfig
|
|
33
15
|
|
|
34
|
-
|
|
16
|
+
log = structlog.stdlib.get_logger()
|
|
35
17
|
|
|
36
18
|
|
|
37
|
-
class NodeServer:
|
|
19
|
+
class NodeServer(EntryPoint):
|
|
38
20
|
"""Manages FastAPI server and event handling for full nodes."""
|
|
39
21
|
config: NodeConfig
|
|
40
22
|
lifecycle: NodeLifecycle
|
|
41
|
-
secure: Secure
|
|
42
|
-
kobj_queue: KobjQueue
|
|
43
|
-
poll_event_buf: PollEventBuffer
|
|
44
23
|
response_handler: ResponseHandler
|
|
45
24
|
app: FastAPI
|
|
46
25
|
router: APIRouter
|
|
@@ -49,16 +28,10 @@ class NodeServer:
|
|
|
49
28
|
self,
|
|
50
29
|
config: NodeConfig,
|
|
51
30
|
lifecycle: NodeLifecycle,
|
|
52
|
-
|
|
53
|
-
kobj_queue: KobjQueue,
|
|
54
|
-
poll_event_buf: PollEventBuffer,
|
|
55
|
-
response_handler: ResponseHandler
|
|
31
|
+
response_handler: ResponseHandler,
|
|
56
32
|
):
|
|
57
33
|
self.config = config
|
|
58
34
|
self.lifecycle = lifecycle
|
|
59
|
-
self.secure = secure
|
|
60
|
-
self.kobj_queue = kobj_queue
|
|
61
|
-
self.poll_event_buf = poll_event_buf
|
|
62
35
|
self.response_handler = response_handler
|
|
63
36
|
self._build_app()
|
|
64
37
|
|
|
@@ -75,71 +48,48 @@ class NodeServer:
|
|
|
75
48
|
version="1.0.0"
|
|
76
49
|
)
|
|
77
50
|
|
|
78
|
-
self.router = APIRouter(prefix="/koi-net")
|
|
79
51
|
self.app.add_exception_handler(ProtocolError, self.protocol_error_handler)
|
|
80
52
|
|
|
81
|
-
|
|
53
|
+
self.router = APIRouter(prefix="/koi-net")
|
|
54
|
+
|
|
55
|
+
for path, models in API_MODEL_MAP.items():
|
|
56
|
+
def create_endpoint(path: str):
|
|
57
|
+
async def endpoint(req):
|
|
58
|
+
return self.response_handler.handle_response(path, req)
|
|
59
|
+
|
|
60
|
+
# programmatically setting type hint annotations for FastAPI's model validation
|
|
61
|
+
endpoint.__annotations__ = {
|
|
62
|
+
"req": models.request_envelope,
|
|
63
|
+
"return": models.response_envelope
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return endpoint
|
|
67
|
+
|
|
82
68
|
self.router.add_api_route(
|
|
83
69
|
path=path,
|
|
84
|
-
endpoint=
|
|
70
|
+
endpoint=create_endpoint(path),
|
|
85
71
|
methods=["POST"],
|
|
86
72
|
response_model_exclude_none=True
|
|
87
73
|
)
|
|
88
74
|
|
|
89
|
-
_add_endpoint(BROADCAST_EVENTS_PATH, self.broadcast_events)
|
|
90
|
-
_add_endpoint(POLL_EVENTS_PATH, self.poll_events)
|
|
91
|
-
_add_endpoint(FETCH_RIDS_PATH, self.fetch_rids)
|
|
92
|
-
_add_endpoint(FETCH_MANIFESTS_PATH, self.fetch_manifests)
|
|
93
|
-
_add_endpoint(FETCH_BUNDLES_PATH, self.fetch_bundles)
|
|
94
|
-
|
|
95
75
|
self.app.include_router(self.router)
|
|
96
|
-
|
|
97
|
-
def run(self):
|
|
98
|
-
"""Starts FastAPI server and event handler."""
|
|
99
|
-
uvicorn.run(
|
|
100
|
-
app=self.app,
|
|
101
|
-
host=self.config.server.host,
|
|
102
|
-
port=self.config.server.port
|
|
103
|
-
)
|
|
104
76
|
|
|
105
77
|
def protocol_error_handler(self, request, exc: ProtocolError):
|
|
106
78
|
"""Catches `ProtocolError` and returns as `ErrorResponse`."""
|
|
107
|
-
|
|
79
|
+
log.info(f"caught protocol error: {exc}")
|
|
108
80
|
resp = ErrorResponse(error=exc.error_type)
|
|
109
|
-
|
|
81
|
+
log.info(f"returning error response: {resp}")
|
|
110
82
|
return JSONResponse(
|
|
111
83
|
status_code=400,
|
|
112
84
|
content=resp.model_dump(mode="json")
|
|
113
85
|
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
self.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"""Handles poll events endpoint."""
|
|
125
|
-
logger.info(f"Request to {POLL_EVENTS_PATH}")
|
|
126
|
-
events = self.poll_event_buf.flush(req.source_node)
|
|
127
|
-
return EventsPayload(events=events)
|
|
128
|
-
|
|
129
|
-
async def fetch_rids(
|
|
130
|
-
self, req: SignedEnvelope[FetchRids]
|
|
131
|
-
) -> SignedEnvelope[RidsPayload] | ErrorResponse:
|
|
132
|
-
"""Handles fetch RIDs endpoint."""
|
|
133
|
-
return self.response_handler.fetch_rids(req.payload, req.source_node)
|
|
134
|
-
|
|
135
|
-
async def fetch_manifests(
|
|
136
|
-
self, req: SignedEnvelope[FetchManifests]
|
|
137
|
-
) -> SignedEnvelope[ManifestsPayload] | ErrorResponse:
|
|
138
|
-
"""Handles fetch manifests endpoint."""
|
|
139
|
-
return self.response_handler.fetch_manifests(req.payload, req.source_node)
|
|
140
|
-
|
|
141
|
-
async def fetch_bundles(
|
|
142
|
-
self, req: SignedEnvelope[FetchBundles]
|
|
143
|
-
) -> SignedEnvelope[BundlesPayload] | ErrorResponse:
|
|
144
|
-
"""Handles fetch bundles endpoint."""
|
|
145
|
-
return self.response_handler.fetch_bundles(req.payload, req.source_node)
|
|
86
|
+
|
|
87
|
+
def run(self):
|
|
88
|
+
"""Starts FastAPI server and event handler."""
|
|
89
|
+
uvicorn.run(
|
|
90
|
+
app=self.app,
|
|
91
|
+
host=self.config.server.host,
|
|
92
|
+
port=self.config.server.port,
|
|
93
|
+
log_config=None,
|
|
94
|
+
access_log=False
|
|
95
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: koi-net
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.0b2
|
|
4
4
|
Summary: Implementation of KOI-net protocol in Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/BlockScience/koi-net/
|
|
6
6
|
Author-email: Luke Miller <luke@block.science>
|
|
@@ -36,6 +36,7 @@ Requires-Dist: python-dotenv>=1.1.0
|
|
|
36
36
|
Requires-Dist: rich>=14.1.0
|
|
37
37
|
Requires-Dist: rid-lib>=3.2.7
|
|
38
38
|
Requires-Dist: ruamel-yaml>=0.18.10
|
|
39
|
+
Requires-Dist: structlog>=25.4.0
|
|
39
40
|
Requires-Dist: uvicorn>=0.34.2
|
|
40
41
|
Provides-Extra: dev
|
|
41
42
|
Requires-Dist: build; extra == 'dev'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
koi_net/__init__.py,sha256=ZJQHfYaMtu3Qv6ZtKxq49N0T1GbVd3dvrjXZdeNYnQw,41
|
|
2
|
+
koi_net/assembler.py,sha256=kV7qB1xnPvAVUrunaNgx4f9lb2qQlvlA4zZs1xKq62Q,2831
|
|
3
|
+
koi_net/config.py,sha256=yV53GFQrJchyKKRs3sOhj27pyM6rwiUhIgDZ81rvZ48,5238
|
|
4
|
+
koi_net/context.py,sha256=mBKMR1o6Tqy_cyhj7_niGkMSMOQeEMPhP9cpNDlML1s,1323
|
|
5
|
+
koi_net/core.py,sha256=owbJYN_kCMwKz3ecWXt_tQXOEECL_q1uq4CcFAuR6EA,2428
|
|
6
|
+
koi_net/default_actions.py,sha256=y9oaDeb3jJGSvVbuonmyGWkKWlzpEX3V32MjgEuYWc8,622
|
|
7
|
+
koi_net/effector.py,sha256=0hW2NnHWjVZyRPXWh_X_0EBxqpCJBBhJCHFvB6uC3SM,4941
|
|
8
|
+
koi_net/handshaker.py,sha256=sd2bYe7Fh5hCTq5q18_knKWAMzvH5a6zHzCcBKbkhWQ,1229
|
|
9
|
+
koi_net/identity.py,sha256=jZjTSlgO8aJhfOx6-ppQVveHih6h0dP2Gtbsw6GGdTI,569
|
|
10
|
+
koi_net/lifecycle.py,sha256=AhJCkXHuBhUHMXXAzI3akz0pTRE77aNEHwjsnbe98TE,4775
|
|
11
|
+
koi_net/logger.py,sha256=EzpQNvNrbi5LmMCobyuN1_wOz5VuMDeaPbLcyMrLKGI,6172
|
|
12
|
+
koi_net/poller.py,sha256=OZLYAMHxXxM75ENetHd_nnNaiiZTSpMgeP3xxAphVEg,1463
|
|
13
|
+
koi_net/secure.py,sha256=qayAzO7xWF8wa33rmNCTupVVaYXwNuimlXpPFWJfQT8,4911
|
|
14
|
+
koi_net/sentry.py,sha256=9UCVMPoEzua3KMUHe4inciqEnqTF_gNSQO2zhpUN8ts,506
|
|
15
|
+
koi_net/server.py,sha256=GJbkHp2BmDhSHWdF6PObLus7f0BiMZuhcZALj7kNiF4,3087
|
|
16
|
+
koi_net/utils.py,sha256=4K6BqtifExDUfPP8zJAICgPV1mcabz_MaFB0P0fSnic,411
|
|
17
|
+
koi_net/cli/__init__.py,sha256=Vpq0yLN0ROyF0qQYsyiuWFbjs-zsa90G1DpAv13qaiw,25
|
|
18
|
+
koi_net/cli/commands.py,sha256=5eVsvJAnHjwJdt8MNYrk5zSzWWm1ooiP35bJeQxYIq4,2592
|
|
19
|
+
koi_net/cli/models.py,sha256=ON-rqMgHDng3J2hbBbGYzkynaWc9Tq4QfOJIu_6pFuA,1190
|
|
20
|
+
koi_net/interfaces/entrypoint.py,sha256=8FEG1r0YIws_1AojTtIPWB-Th3GOS3U2qi89RlyBJYs,80
|
|
21
|
+
koi_net/interfaces/worker.py,sha256=IvtXBTVmthUrURmB9Cn9wffeVv9_Cphsk97_GCLb5NQ,294
|
|
22
|
+
koi_net/network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
koi_net/network/error_handler.py,sha256=4i_r7EWBW52_bhdmyU-1A_Cnu0Q-jHO9O1EXgO1NGws,1784
|
|
24
|
+
koi_net/network/event_queue.py,sha256=JqSMRk_PvBn5hlnUKF2jl9uxaOCX793fwZsblx-pEKs,743
|
|
25
|
+
koi_net/network/graph.py,sha256=ZJ1CY-SnYhnZAMm3e9YCbBflk80ZDETnoQKaBk5kLgo,4161
|
|
26
|
+
koi_net/network/poll_event_buffer.py,sha256=PO9HnmlhQ0V-uPGOf5b15GhbJtjAzEfMDf6TtsQLtPA,734
|
|
27
|
+
koi_net/network/request_handler.py,sha256=XSX-JAqRua42YOakmia--9NnKcT32Fpoc-7aYfjDpwg,6752
|
|
28
|
+
koi_net/network/resolver.py,sha256=i8YsdIYVo9--YYW5AK9PO-f_lpXIVKZONImJ9wYMySY,5277
|
|
29
|
+
koi_net/network/response_handler.py,sha256=OyEd_5ltQf-sdGDI4QalPuHVLqrSMpAjRQCDtlNR8WU,4216
|
|
30
|
+
koi_net/processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
koi_net/processor/event_worker.py,sha256=z6Zef5TFDokwywSNzaMh6oDmEaAoss6m--dRHHmU3YY,4190
|
|
32
|
+
koi_net/processor/handler.py,sha256=PJlQCPfKm3zYsJrwmGIw5xZmc6vArKz_UoeJuXBj16Y,2356
|
|
33
|
+
koi_net/processor/handlers.py,sha256=rGvjrud72szKBM8rhZRrQ2qO8Ng97oGLIfUG9Af-oxw,10934
|
|
34
|
+
koi_net/processor/knowledge_object.py,sha256=0VyBOqkCoINMK3fqYhLlXU-Ri7gKEpJloDHfhaGITxg,4172
|
|
35
|
+
koi_net/processor/kobj_queue.py,sha256=A4jdS0WCrL-fg3Kl8bHPbzIjSfYNGOBBqzCbSJhfMZ4,1837
|
|
36
|
+
koi_net/processor/kobj_worker.py,sha256=26DG8fU3hLbKZmS6mcuOEnUWcVLkq-1fl8AGNLrYFY0,1237
|
|
37
|
+
koi_net/processor/pipeline.py,sha256=FpU3nORIeJxJuX8QHTSqIji8iMU69GuTR4UMeThOzUo,8668
|
|
38
|
+
koi_net/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
+
koi_net/protocol/api_models.py,sha256=lPd_w-sncyrQlTQ__iae9Li1lFARu8nKiMMlXu7-Hls,1856
|
|
40
|
+
koi_net/protocol/consts.py,sha256=bisbVEojPIHlLhkLafBzfIhH25TjNfvTORF1g6YXzIM,243
|
|
41
|
+
koi_net/protocol/edge.py,sha256=PzdEhC43T1KO5iMSEu7I4tiz-7sZxtz41dJfWf-oHA0,1034
|
|
42
|
+
koi_net/protocol/envelope.py,sha256=4SKF4tP1P1ex7UEmYOJt7IP9_8fCWsa_o6ZBOu8SOKk,1877
|
|
43
|
+
koi_net/protocol/errors.py,sha256=uKPQ-TGLouZuK0xd2pXuCQoRTyu_JFsydSCLml13Cz8,595
|
|
44
|
+
koi_net/protocol/event.py,sha256=HxzLN-iCXPyr2YzrswMIkgZYeUdFbBpa5v98dAB06lQ,1328
|
|
45
|
+
koi_net/protocol/model_map.py,sha256=W0quo25HqVkzp8HQ7l_FNTtN4ZX0u7MIqp68Ir81fuk,1671
|
|
46
|
+
koi_net/protocol/node.py,sha256=KH_SjHDzW-V4pn2tqpTDhIzfuCgKgM1YqCafyofLN3k,466
|
|
47
|
+
koi_net/protocol/secure.py,sha256=y3-l0ldQ4oQ6ReEmunDy0ncF836kZvLdGFeEgmuJw44,5110
|
|
48
|
+
koi_net-1.2.0b2.dist-info/METADATA,sha256=t4T3T0cGyG24XvptQg6vy10B8_gHCWGW-s0PntA9aX8,37347
|
|
49
|
+
koi_net-1.2.0b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
50
|
+
koi_net-1.2.0b2.dist-info/entry_points.txt,sha256=l7He_JTyXrfKIHkttnPWXHI717v8WpLLfCduL5QcEWA,40
|
|
51
|
+
koi_net-1.2.0b2.dist-info/licenses/LICENSE,sha256=03mgCL5qth2aD9C3F3qNVs4sFJSpK9kjtYCyOwdSp7s,1069
|
|
52
|
+
koi_net-1.2.0b2.dist-info/RECORD,,
|
koi_net/behaviors.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
from logging import getLogger
|
|
2
|
-
from rid_lib.ext import Cache
|
|
3
|
-
from rid_lib.types import KoiNetNode
|
|
4
|
-
from rid_lib import RIDType
|
|
5
|
-
from koi_net.identity import NodeIdentity
|
|
6
|
-
from koi_net.network.event_queue import EventQueue
|
|
7
|
-
from koi_net.network.request_handler import RequestHandler
|
|
8
|
-
from koi_net.network.resolver import NetworkResolver
|
|
9
|
-
from koi_net.processor.kobj_queue import KobjQueue
|
|
10
|
-
from koi_net.protocol.api_models import ErrorResponse
|
|
11
|
-
from .protocol.event import Event, EventType
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
logger = getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Behaviors:
|
|
19
|
-
def __init__(self, cache: Cache, identity: NodeIdentity, event_queue: EventQueue, resolver: NetworkResolver, request_handler: RequestHandler, kobj_queue: KobjQueue):
|
|
20
|
-
self.cache = cache
|
|
21
|
-
self.identity = identity
|
|
22
|
-
self.event_queue = event_queue
|
|
23
|
-
self.resolver = resolver
|
|
24
|
-
self.request_handler = request_handler
|
|
25
|
-
self.kobj_queue = kobj_queue
|
|
26
|
-
|
|
27
|
-
def identify_coordinators(self) -> list[KoiNetNode]:
|
|
28
|
-
"""Returns node's providing state for `orn:koi-net.node`."""
|
|
29
|
-
return self.resolver.get_state_providers(KoiNetNode)
|
|
30
|
-
|
|
31
|
-
def catch_up_with(self, target: KoiNetNode, rid_types: list[RIDType] = []):
|
|
32
|
-
"""Fetches and processes knowledge objects from target node.
|
|
33
|
-
Args:
|
|
34
|
-
target: Node to catch up with
|
|
35
|
-
rid_types: RID types to fetch from target (all types if list is empty)
|
|
36
|
-
"""
|
|
37
|
-
logger.debug(f"catching up with {target} on {rid_types or 'all types'}")
|
|
38
|
-
payload = self.request_handler.fetch_manifests(
|
|
39
|
-
node=target,
|
|
40
|
-
rid_types=rid_types
|
|
41
|
-
)
|
|
42
|
-
if type(payload) == ErrorResponse:
|
|
43
|
-
logger.debug("failed to reach node")
|
|
44
|
-
return
|
|
45
|
-
for manifest in payload.manifests:
|
|
46
|
-
if manifest.rid == self.identity.rid:
|
|
47
|
-
continue
|
|
48
|
-
self.kobj_queue.put_kobj(
|
|
49
|
-
manifest=manifest,
|
|
50
|
-
source=target
|
|
51
|
-
)
|