koi-net 1.2.0b1__py3-none-any.whl → 1.2.0b3__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 -1
- koi_net/assembler.py +95 -0
- koi_net/cli/models.py +2 -2
- koi_net/config/core.py +71 -0
- koi_net/config/full_node.py +31 -0
- koi_net/config/loader.py +46 -0
- koi_net/config/partial_node.py +18 -0
- koi_net/core.py +43 -206
- koi_net/default_actions.py +1 -2
- koi_net/effector.py +27 -15
- koi_net/entrypoints/__init__.py +2 -0
- koi_net/entrypoints/base.py +5 -0
- koi_net/{poller.py → entrypoints/poller.py} +14 -12
- koi_net/entrypoints/server.py +94 -0
- koi_net/handshaker.py +5 -5
- koi_net/identity.py +3 -4
- koi_net/lifecycle.py +42 -34
- koi_net/logger.py +176 -0
- koi_net/network/error_handler.py +7 -7
- koi_net/network/event_queue.py +9 -7
- 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 +14 -14
- koi_net/network/response_handler.py +74 -9
- koi_net/{context.py → processor/context.py} +11 -19
- koi_net/processor/handler.py +4 -1
- koi_net/processor/{default_handlers.py → knowledge_handlers.py} +32 -31
- koi_net/processor/knowledge_object.py +2 -3
- koi_net/processor/kobj_queue.py +4 -4
- koi_net/processor/{knowledge_pipeline.py → pipeline.py} +25 -28
- 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/node.py +3 -3
- koi_net/protocol/secure.py +14 -8
- koi_net/secure.py +6 -7
- koi_net/workers/__init__.py +2 -0
- koi_net/{worker.py → workers/base.py} +7 -0
- koi_net/{processor → workers}/event_worker.py +19 -23
- koi_net/{kobj_worker.py → workers/kobj_worker.py} +12 -13
- {koi_net-1.2.0b1.dist-info → koi_net-1.2.0b3.dist-info}/METADATA +2 -1
- koi_net-1.2.0b3.dist-info/RECORD +56 -0
- koi_net/behaviors.py +0 -51
- koi_net/config.py +0 -161
- koi_net/models.py +0 -14
- koi_net/poll_event_buffer.py +0 -17
- koi_net/server.py +0 -145
- koi_net-1.2.0b1.dist-info/RECORD +0 -49
- {koi_net-1.2.0b1.dist-info → koi_net-1.2.0b3.dist-info}/WHEEL +0 -0
- {koi_net-1.2.0b1.dist-info → koi_net-1.2.0b3.dist-info}/entry_points.txt +0 -0
- {koi_net-1.2.0b1.dist-info → koi_net-1.2.0b3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from rid_lib.types import KoiNetNode
|
|
2
|
+
|
|
3
|
+
from koi_net.protocol.event import Event
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PollEventBuffer:
|
|
7
|
+
buffers: dict[KoiNetNode, list[Event]]
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.buffers = dict()
|
|
11
|
+
|
|
12
|
+
def push(self, node: KoiNetNode, event: Event):
|
|
13
|
+
event_buf = self.buffers.setdefault(node, [])
|
|
14
|
+
event_buf.append(event)
|
|
15
|
+
|
|
16
|
+
def flush(self, node: KoiNetNode, limit: int = 0):
|
|
17
|
+
event_buf = self.buffers.get(node, [])
|
|
18
|
+
|
|
19
|
+
if limit and len(event_buf) > limit:
|
|
20
|
+
to_return = event_buf[:limit]
|
|
21
|
+
self.buffers[node] = event_buf[limit:]
|
|
22
|
+
else:
|
|
23
|
+
to_return = event_buf.copy()
|
|
24
|
+
self.buffers[node] = []
|
|
25
|
+
|
|
26
|
+
return to_return
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
import httpx
|
|
3
3
|
from rid_lib import RID
|
|
4
4
|
from rid_lib.ext import Cache
|
|
5
5
|
from rid_lib.types.koi_net_node import KoiNetNode
|
|
6
6
|
|
|
7
|
+
from koi_net.protocol.model_map import API_MODEL_MAP
|
|
8
|
+
|
|
7
9
|
from ..identity import NodeIdentity
|
|
8
10
|
from ..protocol.api_models import (
|
|
9
11
|
RidsPayload,
|
|
@@ -30,8 +32,7 @@ from ..protocol.node import NodeProfile, NodeType
|
|
|
30
32
|
from ..secure import Secure
|
|
31
33
|
from .error_handler import ErrorHandler
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
logger = logging.getLogger(__name__)
|
|
35
|
+
log = structlog.stdlib.get_logger()
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
# Custom error types for request handling
|
|
@@ -74,7 +75,7 @@ class RequestHandler:
|
|
|
74
75
|
def get_url(self, node_rid: KoiNetNode) -> str:
|
|
75
76
|
"""Retrieves URL of a node from its RID."""
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
log.debug(f"Getting URL for {node_rid!r}")
|
|
78
79
|
node_url = None
|
|
79
80
|
|
|
80
81
|
if node_rid == self.identity.rid:
|
|
@@ -84,20 +85,20 @@ class RequestHandler:
|
|
|
84
85
|
|
|
85
86
|
if node_bundle:
|
|
86
87
|
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
87
|
-
|
|
88
|
+
log.debug(f"Found node profile: {node_profile}")
|
|
88
89
|
if node_profile.node_type != NodeType.FULL:
|
|
89
90
|
raise PartialNodeQueryError("Can't query partial node")
|
|
90
91
|
node_url = node_profile.base_url
|
|
91
92
|
|
|
92
93
|
else:
|
|
93
94
|
if node_rid == self.identity.config.koi_net.first_contact.rid:
|
|
94
|
-
|
|
95
|
+
log.debug("Found URL of first contact")
|
|
95
96
|
node_url = self.identity.config.koi_net.first_contact.url
|
|
96
97
|
|
|
97
98
|
if not node_url:
|
|
98
99
|
raise NodeNotFoundError("Node not found")
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
log.debug(f"Resolved {node_rid!r} to {node_url}")
|
|
101
102
|
return node_url
|
|
102
103
|
|
|
103
104
|
def make_request(
|
|
@@ -108,7 +109,7 @@ class RequestHandler:
|
|
|
108
109
|
) -> ResponseModels | None:
|
|
109
110
|
"""Makes a request to a node."""
|
|
110
111
|
url = self.get_url(node) + path
|
|
111
|
-
|
|
112
|
+
log.info(f"Making request to {url}")
|
|
112
113
|
|
|
113
114
|
signed_envelope = self.secure.create_envelope(
|
|
114
115
|
payload=request,
|
|
@@ -116,9 +117,12 @@ class RequestHandler:
|
|
|
116
117
|
)
|
|
117
118
|
|
|
118
119
|
try:
|
|
119
|
-
result = httpx.post(
|
|
120
|
+
result = httpx.post(
|
|
121
|
+
url,
|
|
122
|
+
data=signed_envelope.model_dump_json(exclude_none=True)
|
|
123
|
+
)
|
|
120
124
|
except httpx.ConnectError as err:
|
|
121
|
-
|
|
125
|
+
log.debug("Failed to connect")
|
|
122
126
|
self.error_handler.handle_connection_error(node)
|
|
123
127
|
raise err
|
|
124
128
|
|
|
@@ -127,20 +131,11 @@ class RequestHandler:
|
|
|
127
131
|
self.error_handler.handle_protocol_error(resp.error, node)
|
|
128
132
|
return resp
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
EnvelopeModel = SignedEnvelope[EventsPayload]
|
|
134
|
-
elif path == FETCH_RIDS_PATH:
|
|
135
|
-
EnvelopeModel = SignedEnvelope[RidsPayload]
|
|
136
|
-
elif path == FETCH_MANIFESTS_PATH:
|
|
137
|
-
EnvelopeModel = SignedEnvelope[ManifestsPayload]
|
|
138
|
-
elif path == FETCH_BUNDLES_PATH:
|
|
139
|
-
EnvelopeModel = SignedEnvelope[BundlesPayload]
|
|
140
|
-
else:
|
|
141
|
-
raise UnknownPathError(f"Unknown path '{path}'")
|
|
134
|
+
resp_env_model = API_MODEL_MAP[path].response_envelope
|
|
135
|
+
if resp_env_model is None:
|
|
136
|
+
return
|
|
142
137
|
|
|
143
|
-
resp_envelope =
|
|
138
|
+
resp_envelope = resp_env_model.model_validate_json(result.text)
|
|
144
139
|
self.secure.validate_envelope(resp_envelope)
|
|
145
140
|
|
|
146
141
|
return resp_envelope.payload
|
|
@@ -157,7 +152,7 @@ class RequestHandler:
|
|
|
157
152
|
"""
|
|
158
153
|
request = req or EventsPayload.model_validate(kwargs)
|
|
159
154
|
self.make_request(node, BROADCAST_EVENTS_PATH, request)
|
|
160
|
-
|
|
155
|
+
log.info(f"Broadcasted {len(request.events)} event(s) to {node!r}")
|
|
161
156
|
|
|
162
157
|
def poll_events(
|
|
163
158
|
self,
|
|
@@ -172,7 +167,7 @@ class RequestHandler:
|
|
|
172
167
|
request = req or PollEvents.model_validate(kwargs)
|
|
173
168
|
resp = self.make_request(node, POLL_EVENTS_PATH, request)
|
|
174
169
|
if type(resp) != ErrorResponse:
|
|
175
|
-
|
|
170
|
+
log.info(f"Polled {len(resp.events)} events from {node!r}")
|
|
176
171
|
return resp
|
|
177
172
|
|
|
178
173
|
def fetch_rids(
|
|
@@ -188,7 +183,7 @@ class RequestHandler:
|
|
|
188
183
|
request = req or FetchRids.model_validate(kwargs)
|
|
189
184
|
resp = self.make_request(node, FETCH_RIDS_PATH, request)
|
|
190
185
|
if type(resp) != ErrorResponse:
|
|
191
|
-
|
|
186
|
+
log.info(f"Fetched {len(resp.rids)} RID(s) from {node!r}")
|
|
192
187
|
return resp
|
|
193
188
|
|
|
194
189
|
def fetch_manifests(
|
|
@@ -204,7 +199,7 @@ class RequestHandler:
|
|
|
204
199
|
request = req or FetchManifests.model_validate(kwargs)
|
|
205
200
|
resp = self.make_request(node, FETCH_MANIFESTS_PATH, request)
|
|
206
201
|
if type(resp) != ErrorResponse:
|
|
207
|
-
|
|
202
|
+
log.info(f"Fetched {len(resp.manifests)} manifest(s) from {node!r}")
|
|
208
203
|
return resp
|
|
209
204
|
|
|
210
205
|
def fetch_bundles(
|
|
@@ -220,5 +215,5 @@ class RequestHandler:
|
|
|
220
215
|
request = req or FetchBundles.model_validate(kwargs)
|
|
221
216
|
resp = self.make_request(node, FETCH_BUNDLES_PATH, request)
|
|
222
217
|
if type(resp) != ErrorResponse:
|
|
223
|
-
|
|
218
|
+
log.info(f"Fetched {len(resp.bundles)} bundle(s) from {node!r}")
|
|
224
219
|
return resp
|
koi_net/network/resolver.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
import httpx
|
|
3
3
|
from rid_lib import RID
|
|
4
4
|
from rid_lib.core import RIDType
|
|
@@ -11,9 +11,9 @@ from ..protocol.node import NodeProfile, NodeType
|
|
|
11
11
|
from ..protocol.event import Event
|
|
12
12
|
from ..protocol.api_models import ErrorResponse
|
|
13
13
|
from ..identity import NodeIdentity
|
|
14
|
-
from ..config import NodeConfig
|
|
14
|
+
from ..config.core import NodeConfig
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
log = structlog.stdlib.get_logger()
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class NetworkResolver:
|
|
@@ -45,7 +45,7 @@ class NetworkResolver:
|
|
|
45
45
|
def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
|
|
46
46
|
"""Returns list of node RIDs which provide state for specified RID type."""
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
log.debug(f"Looking for state providers of {rid_type}")
|
|
49
49
|
provider_nodes = []
|
|
50
50
|
for node_rid in self.cache.list_rids(rid_types=[KoiNetNode]):
|
|
51
51
|
if node_rid == self.identity.rid:
|
|
@@ -56,17 +56,17 @@ class NetworkResolver:
|
|
|
56
56
|
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
57
57
|
|
|
58
58
|
if (node_profile.node_type == NodeType.FULL) and (rid_type in node_profile.provides.state):
|
|
59
|
-
|
|
59
|
+
log.debug(f"Found provider {node_rid!r}")
|
|
60
60
|
provider_nodes.append(node_rid)
|
|
61
61
|
|
|
62
62
|
if not provider_nodes:
|
|
63
|
-
|
|
63
|
+
log.debug("Failed to find providers")
|
|
64
64
|
return provider_nodes
|
|
65
65
|
|
|
66
66
|
def fetch_remote_bundle(self, rid: RID) -> tuple[Bundle | None, KoiNetNode | None]:
|
|
67
67
|
"""Attempts to fetch a bundle by RID from known peer nodes."""
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
log.debug(f"Fetching remote bundle {rid!r}")
|
|
70
70
|
remote_bundle, node_rid = None, None
|
|
71
71
|
for node_rid in self.get_state_providers(type(rid)):
|
|
72
72
|
payload = self.request_handler.fetch_bundles(
|
|
@@ -74,18 +74,18 @@ class NetworkResolver:
|
|
|
74
74
|
|
|
75
75
|
if payload.bundles:
|
|
76
76
|
remote_bundle = payload.bundles[0]
|
|
77
|
-
|
|
77
|
+
log.debug(f"Got bundle from {node_rid!r}")
|
|
78
78
|
break
|
|
79
79
|
|
|
80
80
|
if not remote_bundle:
|
|
81
|
-
|
|
81
|
+
log.warning("Failed to fetch remote bundle")
|
|
82
82
|
|
|
83
83
|
return remote_bundle, node_rid
|
|
84
84
|
|
|
85
85
|
def fetch_remote_manifest(self, rid: RID) -> tuple[Bundle | None, KoiNetNode | None]:
|
|
86
86
|
"""Attempts to fetch a manifest by RID from known peer nodes."""
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
log.debug(f"Fetching remote manifest {rid!r}")
|
|
89
89
|
remote_manifest, node_rid = None, None
|
|
90
90
|
for node_rid in self.get_state_providers(type(rid)):
|
|
91
91
|
payload = self.request_handler.fetch_manifests(
|
|
@@ -93,11 +93,11 @@ class NetworkResolver:
|
|
|
93
93
|
|
|
94
94
|
if payload.manifests:
|
|
95
95
|
remote_manifest = payload.manifests[0]
|
|
96
|
-
|
|
96
|
+
log.debug(f"Got bundle from {node_rid!r}")
|
|
97
97
|
break
|
|
98
98
|
|
|
99
99
|
if not remote_manifest:
|
|
100
|
-
|
|
100
|
+
log.warning("Failed to fetch remote bundle")
|
|
101
101
|
|
|
102
102
|
return remote_manifest, node_rid
|
|
103
103
|
|
|
@@ -136,12 +136,12 @@ class NetworkResolver:
|
|
|
136
136
|
continue
|
|
137
137
|
|
|
138
138
|
if payload.events:
|
|
139
|
-
|
|
139
|
+
log.debug(f"Received {len(payload.events)} events from {node_rid!r}")
|
|
140
140
|
|
|
141
141
|
event_dict[node_rid] = payload.events
|
|
142
142
|
|
|
143
143
|
except httpx.ConnectError:
|
|
144
|
-
|
|
144
|
+
log.debug(f"Failed to reach node {node_rid!r}")
|
|
145
145
|
continue
|
|
146
146
|
|
|
147
147
|
return event_dict
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import structlog
|
|
2
2
|
from rid_lib import RID
|
|
3
3
|
from rid_lib.types import KoiNetNode
|
|
4
4
|
from rid_lib.ext import Manifest, Cache
|
|
5
5
|
from rid_lib.ext.bundle import Bundle
|
|
6
6
|
|
|
7
|
+
from koi_net.network.poll_event_buffer import PollEventBuffer
|
|
8
|
+
from koi_net.processor.kobj_queue import KobjQueue
|
|
9
|
+
from koi_net.protocol.consts import BROADCAST_EVENTS_PATH, FETCH_BUNDLES_PATH, FETCH_MANIFESTS_PATH, FETCH_RIDS_PATH, POLL_EVENTS_PATH
|
|
10
|
+
from koi_net.protocol.envelope import SignedEnvelope
|
|
11
|
+
from koi_net.protocol.model_map import API_MODEL_MAP
|
|
12
|
+
from koi_net.secure import Secure
|
|
13
|
+
|
|
7
14
|
from ..protocol.api_models import (
|
|
15
|
+
ApiModels,
|
|
16
|
+
EventsPayload,
|
|
17
|
+
PollEvents,
|
|
8
18
|
RidsPayload,
|
|
9
19
|
ManifestsPayload,
|
|
10
20
|
BundlesPayload,
|
|
@@ -13,30 +23,81 @@ from ..protocol.api_models import (
|
|
|
13
23
|
FetchBundles,
|
|
14
24
|
)
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
log = structlog.stdlib.get_logger()
|
|
17
27
|
|
|
18
28
|
|
|
19
29
|
class ResponseHandler:
|
|
20
30
|
"""Handles generating responses to requests from other KOI nodes."""
|
|
21
31
|
|
|
22
32
|
cache: Cache
|
|
33
|
+
kobj_queue: KobjQueue
|
|
34
|
+
poll_event_buf: PollEventBuffer
|
|
23
35
|
|
|
24
36
|
def __init__(
|
|
25
37
|
self,
|
|
26
|
-
cache: Cache,
|
|
38
|
+
cache: Cache,
|
|
39
|
+
kobj_queue: KobjQueue,
|
|
40
|
+
poll_event_buf: PollEventBuffer,
|
|
41
|
+
secure: Secure
|
|
27
42
|
):
|
|
28
43
|
self.cache = cache
|
|
44
|
+
self.kobj_queue = kobj_queue
|
|
45
|
+
self.poll_event_buf = poll_event_buf
|
|
46
|
+
self.secure = secure
|
|
47
|
+
|
|
48
|
+
def handle_response(self, path: str, req: SignedEnvelope):
|
|
49
|
+
self.secure.validate_envelope(req)
|
|
50
|
+
|
|
51
|
+
response_map = {
|
|
52
|
+
BROADCAST_EVENTS_PATH: self.broadcast_events_handler,
|
|
53
|
+
POLL_EVENTS_PATH: self.poll_events_handler,
|
|
54
|
+
FETCH_RIDS_PATH: self.fetch_rids_handler,
|
|
55
|
+
FETCH_MANIFESTS_PATH: self.fetch_manifests_handler,
|
|
56
|
+
FETCH_BUNDLES_PATH: self.fetch_bundles_handler
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
response = response_map[path](req.payload, req.source_node)
|
|
60
|
+
|
|
61
|
+
if response is None:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
return self.secure.create_envelope(
|
|
65
|
+
payload=response,
|
|
66
|
+
target=req.source_node
|
|
67
|
+
)
|
|
29
68
|
|
|
30
|
-
def
|
|
69
|
+
def broadcast_events_handler(self, req: EventsPayload, source: KoiNetNode):
|
|
70
|
+
log.info(f"Request to broadcast events, received {len(req.events)} event(s)")
|
|
71
|
+
|
|
72
|
+
for event in req.events:
|
|
73
|
+
self.kobj_queue.push(event=event, source=source)
|
|
74
|
+
|
|
75
|
+
def poll_events_handler(
|
|
76
|
+
self,
|
|
77
|
+
req: PollEvents,
|
|
78
|
+
source: KoiNetNode
|
|
79
|
+
) -> EventsPayload:
|
|
80
|
+
log.info(f"Request to poll events")
|
|
81
|
+
events = self.poll_event_buf.flush(source, limit=req.limit)
|
|
82
|
+
return EventsPayload(events=events)
|
|
83
|
+
|
|
84
|
+
def fetch_rids_handler(
|
|
85
|
+
self,
|
|
86
|
+
req: FetchRids,
|
|
87
|
+
source: KoiNetNode
|
|
88
|
+
) -> RidsPayload:
|
|
31
89
|
"""Returns response to fetch RIDs request."""
|
|
32
|
-
|
|
90
|
+
log.info(f"Request to fetch rids, allowed types {req.rid_types}")
|
|
33
91
|
rids = self.cache.list_rids(req.rid_types)
|
|
34
92
|
|
|
35
93
|
return RidsPayload(rids=rids)
|
|
36
94
|
|
|
37
|
-
def
|
|
95
|
+
def fetch_manifests_handler(self,
|
|
96
|
+
req: FetchManifests,
|
|
97
|
+
source: KoiNetNode
|
|
98
|
+
) -> ManifestsPayload:
|
|
38
99
|
"""Returns response to fetch manifests request."""
|
|
39
|
-
|
|
100
|
+
log.info(f"Request to fetch manifests, allowed types {req.rid_types}, rids {req.rids}")
|
|
40
101
|
|
|
41
102
|
manifests: list[Manifest] = []
|
|
42
103
|
not_found: list[RID] = []
|
|
@@ -50,9 +111,13 @@ class ResponseHandler:
|
|
|
50
111
|
|
|
51
112
|
return ManifestsPayload(manifests=manifests, not_found=not_found)
|
|
52
113
|
|
|
53
|
-
def
|
|
114
|
+
def fetch_bundles_handler(
|
|
115
|
+
self,
|
|
116
|
+
req: FetchBundles,
|
|
117
|
+
source: KoiNetNode
|
|
118
|
+
) -> BundlesPayload:
|
|
54
119
|
"""Returns response to fetch bundles request."""
|
|
55
|
-
|
|
120
|
+
log.info(f"Request to fetch bundles, requested rids {req.rids}")
|
|
56
121
|
|
|
57
122
|
bundles: list[Bundle] = []
|
|
58
123
|
not_found: list[RID] = []
|
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
from rid_lib.ext import Cache
|
|
2
2
|
|
|
3
|
+
from koi_net.effector import Effector
|
|
3
4
|
from koi_net.network.resolver import NetworkResolver
|
|
4
|
-
from .
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from .
|
|
5
|
+
from ..config.core import NodeConfig
|
|
6
|
+
from ..network.graph import NetworkGraph
|
|
7
|
+
from ..network.event_queue import EventQueue
|
|
8
|
+
from ..network.request_handler import RequestHandler
|
|
9
|
+
from ..identity import NodeIdentity
|
|
10
|
+
from .kobj_queue import KobjQueue
|
|
10
11
|
|
|
11
12
|
|
|
12
|
-
class ActionContext:
|
|
13
|
-
"""Provides action handlers access to other subsystems."""
|
|
14
|
-
|
|
15
|
-
identity: NodeIdentity
|
|
16
|
-
|
|
17
|
-
def __init__(
|
|
18
|
-
self,
|
|
19
|
-
identity: NodeIdentity,
|
|
20
|
-
):
|
|
21
|
-
self.identity = identity
|
|
22
|
-
|
|
23
|
-
|
|
24
13
|
class HandlerContext:
|
|
25
14
|
"""Provides knowledge handlers access to other subsystems."""
|
|
26
15
|
|
|
@@ -32,6 +21,7 @@ class HandlerContext:
|
|
|
32
21
|
graph: NetworkGraph
|
|
33
22
|
request_handler: RequestHandler
|
|
34
23
|
resolver: NetworkResolver
|
|
24
|
+
effector: Effector
|
|
35
25
|
|
|
36
26
|
def __init__(
|
|
37
27
|
self,
|
|
@@ -43,6 +33,7 @@ class HandlerContext:
|
|
|
43
33
|
graph: NetworkGraph,
|
|
44
34
|
request_handler: RequestHandler,
|
|
45
35
|
resolver: NetworkResolver,
|
|
36
|
+
effector: Effector
|
|
46
37
|
):
|
|
47
38
|
self.identity = identity
|
|
48
39
|
self.config = config
|
|
@@ -51,4 +42,5 @@ class HandlerContext:
|
|
|
51
42
|
self.kobj_queue = kobj_queue
|
|
52
43
|
self.graph = graph
|
|
53
44
|
self.request_handler = request_handler
|
|
54
|
-
self.resolver = resolver
|
|
45
|
+
self.resolver = resolver
|
|
46
|
+
self.effector = effector
|
koi_net/processor/handler.py
CHANGED
|
@@ -2,7 +2,10 @@ from dataclasses import dataclass
|
|
|
2
2
|
from enum import StrEnum
|
|
3
3
|
from typing import Callable
|
|
4
4
|
from rid_lib import RIDType
|
|
5
|
+
|
|
5
6
|
from ..protocol.event import EventType
|
|
7
|
+
from .knowledge_object import KnowledgeObject
|
|
8
|
+
from .context import HandlerContext
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class StopChain:
|
|
@@ -32,7 +35,7 @@ class HandlerType(StrEnum):
|
|
|
32
35
|
class KnowledgeHandler:
|
|
33
36
|
"""Handles knowledge processing events of the provided types."""
|
|
34
37
|
|
|
35
|
-
func: Callable
|
|
38
|
+
func: Callable[[HandlerContext, KnowledgeObject], None | KnowledgeObject | StopChain]
|
|
36
39
|
handler_type: HandlerType
|
|
37
40
|
rid_types: list[RIDType] | None
|
|
38
41
|
event_types: list[EventType | None] | None = None
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
"""Implementation of default knowledge handlers."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import structlog
|
|
4
4
|
from rid_lib.ext import Bundle
|
|
5
5
|
from rid_lib.ext.utils import sha256_hash
|
|
6
6
|
from rid_lib.types import KoiNetNode, KoiNetEdge
|
|
7
7
|
from koi_net.protocol.node import NodeType
|
|
8
8
|
from .handler import KnowledgeHandler, HandlerType, STOP_CHAIN
|
|
9
9
|
from .knowledge_object import KnowledgeObject
|
|
10
|
-
from
|
|
10
|
+
from .context import HandlerContext
|
|
11
11
|
from ..protocol.event import Event, EventType
|
|
12
|
-
from ..protocol.edge import EdgeProfile, EdgeStatus, EdgeType
|
|
12
|
+
from ..protocol.edge import EdgeProfile, EdgeStatus, EdgeType
|
|
13
13
|
from ..protocol.node import NodeProfile
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
log = structlog.stdlib.get_logger()
|
|
16
|
+
|
|
16
17
|
|
|
17
18
|
# RID handlers
|
|
18
19
|
|
|
@@ -24,7 +25,7 @@ def basic_rid_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
24
25
|
RID is known to this node.
|
|
25
26
|
"""
|
|
26
27
|
if (kobj.rid == ctx.identity.rid and kobj.source):
|
|
27
|
-
|
|
28
|
+
log.debug("Don't let anyone else tell me who I am!")
|
|
28
29
|
return STOP_CHAIN
|
|
29
30
|
|
|
30
31
|
if kobj.event_type == EventType.FORGET:
|
|
@@ -45,17 +46,17 @@ def basic_manifest_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
45
46
|
|
|
46
47
|
if prev_bundle:
|
|
47
48
|
if kobj.manifest.sha256_hash == prev_bundle.manifest.sha256_hash:
|
|
48
|
-
|
|
49
|
+
log.debug("Hash of incoming manifest is same as existing knowledge, ignoring")
|
|
49
50
|
return STOP_CHAIN
|
|
50
51
|
if kobj.manifest.timestamp <= prev_bundle.manifest.timestamp:
|
|
51
|
-
|
|
52
|
+
log.debug("Timestamp of incoming manifest is the same or older than existing knowledge, ignoring")
|
|
52
53
|
return STOP_CHAIN
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
log.debug("RID previously known to me, labeling as 'UPDATE'")
|
|
55
56
|
kobj.normalized_event_type = EventType.UPDATE
|
|
56
57
|
|
|
57
58
|
else:
|
|
58
|
-
|
|
59
|
+
log.debug("RID previously unknown to me, labeling as 'NEW'")
|
|
59
60
|
kobj.normalized_event_type = EventType.NEW
|
|
60
61
|
|
|
61
62
|
return kobj
|
|
@@ -78,7 +79,7 @@ def secure_profile_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
78
79
|
node_rid: KoiNetNode = kobj.rid
|
|
79
80
|
|
|
80
81
|
if sha256_hash(node_profile.public_key) != node_rid.hash:
|
|
81
|
-
|
|
82
|
+
log.warning(f"Public key hash mismatch for {node_rid!r}!")
|
|
82
83
|
return STOP_CHAIN
|
|
83
84
|
|
|
84
85
|
@KnowledgeHandler.create(
|
|
@@ -104,13 +105,13 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
104
105
|
if edge_profile.status != EdgeStatus.PROPOSED:
|
|
105
106
|
return
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
log.debug("Handling edge negotiation")
|
|
108
109
|
|
|
109
110
|
peer_rid = edge_profile.target
|
|
110
111
|
peer_bundle = ctx.cache.read(peer_rid)
|
|
111
112
|
|
|
112
113
|
if not peer_bundle:
|
|
113
|
-
|
|
114
|
+
log.warning(f"Peer {peer_rid!r} unknown to me")
|
|
114
115
|
return STOP_CHAIN
|
|
115
116
|
|
|
116
117
|
peer_profile = peer_bundle.validate_contents(NodeProfile)
|
|
@@ -125,30 +126,30 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
125
126
|
abort = False
|
|
126
127
|
if (edge_profile.edge_type == EdgeType.WEBHOOK and
|
|
127
128
|
peer_profile.node_type == NodeType.PARTIAL):
|
|
128
|
-
|
|
129
|
+
log.debug("Partial nodes cannot use webhooks")
|
|
129
130
|
abort = True
|
|
130
131
|
|
|
131
132
|
if not set(edge_profile.rid_types).issubset(provided_events):
|
|
132
|
-
|
|
133
|
+
log.debug("Requested RID types not provided by this node")
|
|
133
134
|
abort = True
|
|
134
135
|
|
|
135
136
|
if abort:
|
|
136
137
|
event = Event.from_rid(EventType.FORGET, kobj.rid)
|
|
137
|
-
ctx.event_queue.
|
|
138
|
+
ctx.event_queue.push(event, peer_rid, flush=True)
|
|
138
139
|
return STOP_CHAIN
|
|
139
140
|
|
|
140
141
|
else:
|
|
141
142
|
# approve edge profile
|
|
142
|
-
|
|
143
|
+
log.debug("Approving proposed edge")
|
|
143
144
|
edge_profile.status = EdgeStatus.APPROVED
|
|
144
145
|
updated_bundle = Bundle.generate(kobj.rid, edge_profile.model_dump())
|
|
145
146
|
|
|
146
|
-
ctx.kobj_queue.
|
|
147
|
+
ctx.kobj_queue.push(bundle=updated_bundle, event_type=EventType.UPDATE)
|
|
147
148
|
return
|
|
148
149
|
|
|
149
150
|
elif edge_profile.target == ctx.identity.rid:
|
|
150
151
|
if edge_profile.status == EdgeStatus.APPROVED:
|
|
151
|
-
|
|
152
|
+
log.debug("Edge approved by other node!")
|
|
152
153
|
|
|
153
154
|
|
|
154
155
|
# Network handlers
|
|
@@ -176,8 +177,8 @@ def node_contact_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
176
177
|
if not available_rid_types:
|
|
177
178
|
return
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
log.info("Identified a coordinator!")
|
|
181
|
+
log.info("Proposing new edge")
|
|
181
182
|
|
|
182
183
|
# already have an edge established
|
|
183
184
|
edge_rid = ctx.graph.get_edge(
|
|
@@ -215,9 +216,9 @@ def node_contact_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
215
216
|
|
|
216
217
|
# queued for processing
|
|
217
218
|
edge_bundle = Bundle.generate(edge_rid, edge_profile.model_dump())
|
|
218
|
-
ctx.kobj_queue.
|
|
219
|
+
ctx.kobj_queue.push(bundle=edge_bundle)
|
|
219
220
|
|
|
220
|
-
|
|
221
|
+
log.info("Catching up on network state")
|
|
221
222
|
|
|
222
223
|
payload = ctx.request_handler.fetch_rids(
|
|
223
224
|
node=kobj.rid,
|
|
@@ -225,16 +226,16 @@ def node_contact_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
225
226
|
)
|
|
226
227
|
for rid in payload.rids:
|
|
227
228
|
if rid == ctx.identity.rid:
|
|
228
|
-
|
|
229
|
+
log.info("Skipping myself")
|
|
229
230
|
continue
|
|
230
231
|
if ctx.cache.exists(rid):
|
|
231
|
-
|
|
232
|
+
log.info(f"Skipping known RID {rid!r}")
|
|
232
233
|
continue
|
|
233
234
|
|
|
234
235
|
# marked as external since we are handling RIDs from another node
|
|
235
236
|
# will fetch remotely instead of checking local cache
|
|
236
|
-
ctx.kobj_queue.
|
|
237
|
-
|
|
237
|
+
ctx.kobj_queue.push(rid=rid, source=kobj.rid)
|
|
238
|
+
log.info("Done")
|
|
238
239
|
|
|
239
240
|
|
|
240
241
|
@KnowledgeHandler.create(HandlerType.Network)
|
|
@@ -260,12 +261,12 @@ def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
260
261
|
edge_profile = kobj.bundle.validate_contents(EdgeProfile)
|
|
261
262
|
|
|
262
263
|
if edge_profile.source == ctx.identity.rid:
|
|
263
|
-
|
|
264
|
+
log.debug(f"Adding edge target '{edge_profile.target!r}' to network targets")
|
|
264
265
|
kobj.network_targets.update([edge_profile.target])
|
|
265
266
|
involves_me = True
|
|
266
267
|
|
|
267
268
|
elif edge_profile.target == ctx.identity.rid:
|
|
268
|
-
|
|
269
|
+
log.debug(f"Adding edge source '{edge_profile.source!r}' to network targets")
|
|
269
270
|
kobj.network_targets.update([edge_profile.source])
|
|
270
271
|
involves_me = True
|
|
271
272
|
|
|
@@ -276,7 +277,7 @@ def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
276
277
|
allowed_type=type(kobj.rid)
|
|
277
278
|
)
|
|
278
279
|
|
|
279
|
-
|
|
280
|
+
log.debug(f"Updating network targets with '{type(kobj.rid)}' subscribers: {subscribers}")
|
|
280
281
|
kobj.network_targets.update(subscribers)
|
|
281
282
|
|
|
282
283
|
return kobj
|
|
@@ -294,5 +295,5 @@ def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
294
295
|
edge_profile = edge_bundle.validate_contents(EdgeProfile)
|
|
295
296
|
|
|
296
297
|
if kobj.rid in (edge_profile.source, edge_profile.target):
|
|
297
|
-
|
|
298
|
-
ctx.kobj_queue.
|
|
298
|
+
log.debug("Identified edge with forgotten node")
|
|
299
|
+
ctx.kobj_queue.push(rid=edge_rid, event_type=EventType.FORGET)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from pydantic import BaseModel
|
|
2
2
|
from rid_lib import RID
|
|
3
|
-
from rid_lib.ext import Manifest
|
|
4
|
-
from rid_lib.
|
|
5
|
-
from rid_lib.types.koi_net_node import KoiNetNode
|
|
3
|
+
from rid_lib.ext import Manifest, Bundle
|
|
4
|
+
from rid_lib.types import KoiNetNode
|
|
6
5
|
from ..protocol.event import Event, EventType
|
|
7
6
|
|
|
8
7
|
|