koi-net 1.0.0b19__py3-none-any.whl → 1.1.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/config.py +44 -18
- koi_net/context.py +55 -0
- koi_net/core.py +134 -84
- koi_net/default_actions.py +15 -0
- koi_net/effector.py +139 -0
- koi_net/identity.py +4 -22
- koi_net/lifecycle.py +76 -0
- koi_net/network/__init__.py +0 -1
- koi_net/network/behavior.py +42 -0
- koi_net/network/error_handler.py +50 -0
- koi_net/network/{interface.py → event_queue.py} +52 -131
- koi_net/network/graph.py +18 -33
- koi_net/network/request_handler.py +112 -66
- koi_net/network/resolver.py +150 -0
- koi_net/network/response_handler.py +11 -3
- koi_net/poller.py +45 -0
- koi_net/processor/__init__.py +0 -1
- koi_net/processor/default_handlers.py +57 -41
- koi_net/processor/handler.py +3 -7
- koi_net/processor/interface.py +15 -214
- koi_net/processor/knowledge_object.py +10 -17
- koi_net/processor/knowledge_pipeline.py +220 -0
- koi_net/protocol/api_models.py +7 -1
- koi_net/protocol/edge.py +23 -1
- koi_net/protocol/envelope.py +54 -0
- koi_net/protocol/errors.py +23 -0
- koi_net/protocol/node.py +2 -1
- koi_net/protocol/secure.py +106 -0
- koi_net/secure.py +117 -0
- koi_net/server.py +128 -0
- {koi_net-1.0.0b19.dist-info → koi_net-1.1.0b2.dist-info}/METADATA +5 -4
- koi_net-1.1.0b2.dist-info/RECORD +38 -0
- koi_net/protocol/helpers.py +0 -25
- koi_net-1.0.0b19.dist-info/RECORD +0 -25
- {koi_net-1.0.0b19.dist-info → koi_net-1.1.0b2.dist-info}/WHEEL +0 -0
- {koi_net-1.0.0b19.dist-info → koi_net-1.1.0b2.dist-info}/licenses/LICENSE +0 -0
koi_net/config.py
CHANGED
|
@@ -1,32 +1,43 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import TypeVar
|
|
3
2
|
from ruamel.yaml import YAML
|
|
4
|
-
from koi_net.protocol.node import NodeProfile
|
|
5
|
-
from rid_lib.types import KoiNetNode
|
|
6
3
|
from pydantic import BaseModel, Field, PrivateAttr
|
|
7
4
|
from dotenv import load_dotenv
|
|
5
|
+
from rid_lib.ext.utils import sha256_hash
|
|
6
|
+
from rid_lib.types import KoiNetNode
|
|
7
|
+
from .protocol.secure import PrivateKey
|
|
8
|
+
from .protocol.node import NodeProfile, NodeType
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ServerConfig(BaseModel):
|
|
11
|
-
host: str
|
|
12
|
-
port: int
|
|
12
|
+
host: str = "127.0.0.1"
|
|
13
|
+
port: int = 8000
|
|
13
14
|
path: str | None = "/koi-net"
|
|
14
15
|
|
|
15
16
|
@property
|
|
16
17
|
def url(self) -> str:
|
|
17
18
|
return f"http://{self.host}:{self.port}{self.path or ''}"
|
|
18
19
|
|
|
20
|
+
class NodeContact(BaseModel):
|
|
21
|
+
rid: KoiNetNode | None = None
|
|
22
|
+
url: str | None = None
|
|
23
|
+
|
|
19
24
|
class KoiNetConfig(BaseModel):
|
|
20
25
|
node_name: str
|
|
21
26
|
node_rid: KoiNetNode | None = None
|
|
22
27
|
node_profile: NodeProfile
|
|
23
28
|
|
|
24
|
-
cache_directory_path: str
|
|
25
|
-
event_queues_path: str
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
cache_directory_path: str = ".rid_cache"
|
|
30
|
+
event_queues_path: str = "event_queues.json"
|
|
31
|
+
private_key_pem_path: str = "priv_key.pem"
|
|
32
|
+
polling_interval: int = 5
|
|
33
|
+
|
|
34
|
+
first_contact: NodeContact = Field(default_factory=NodeContact)
|
|
35
|
+
|
|
36
|
+
_priv_key: PrivateKey | None = PrivateAttr(default=None)
|
|
28
37
|
|
|
29
38
|
class EnvConfig(BaseModel):
|
|
39
|
+
priv_key_password: str | None = "PRIV_KEY_PASSWORD"
|
|
40
|
+
|
|
30
41
|
def __init__(self, **kwargs):
|
|
31
42
|
super().__init__(**kwargs)
|
|
32
43
|
load_dotenv()
|
|
@@ -41,8 +52,10 @@ class EnvConfig(BaseModel):
|
|
|
41
52
|
return value
|
|
42
53
|
|
|
43
54
|
class NodeConfig(BaseModel):
|
|
44
|
-
server: ServerConfig
|
|
55
|
+
server: ServerConfig = Field(default_factory=ServerConfig)
|
|
45
56
|
koi_net: KoiNetConfig
|
|
57
|
+
env: EnvConfig = Field(default_factory=EnvConfig)
|
|
58
|
+
|
|
46
59
|
_file_path: str = PrivateAttr(default="config.yaml")
|
|
47
60
|
_file_content: str | None = PrivateAttr(default=None)
|
|
48
61
|
|
|
@@ -72,13 +85,27 @@ class NodeConfig(BaseModel):
|
|
|
72
85
|
|
|
73
86
|
config._file_path = file_path
|
|
74
87
|
|
|
75
|
-
if generate_missing:
|
|
76
|
-
config.koi_net.node_rid
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
config.koi_net.
|
|
81
|
-
|
|
88
|
+
if generate_missing:
|
|
89
|
+
if not config.koi_net.node_rid:
|
|
90
|
+
priv_key = PrivateKey.generate()
|
|
91
|
+
pub_key = priv_key.public_key()
|
|
92
|
+
|
|
93
|
+
config.koi_net.node_rid = KoiNetNode(
|
|
94
|
+
config.koi_net.node_name,
|
|
95
|
+
sha256_hash(pub_key.to_der())
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
with open(config.koi_net.private_key_pem_path, "w") as f:
|
|
99
|
+
f.write(
|
|
100
|
+
priv_key.to_pem(config.env.priv_key_password)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
config.koi_net.node_profile.public_key = pub_key.to_der()
|
|
104
|
+
|
|
105
|
+
if config.koi_net.node_profile.node_type == NodeType.FULL:
|
|
106
|
+
config.koi_net.node_profile.base_url = (
|
|
107
|
+
config.koi_net.node_profile.base_url or config.server.url
|
|
108
|
+
)
|
|
82
109
|
|
|
83
110
|
config.save_to_yaml()
|
|
84
111
|
|
|
@@ -98,4 +125,3 @@ class NodeConfig(BaseModel):
|
|
|
98
125
|
f.write(self._file_content)
|
|
99
126
|
raise e
|
|
100
127
|
|
|
101
|
-
ConfigType = TypeVar("ConfigType", bound=NodeConfig)
|
koi_net/context.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
|
|
2
|
+
from koi_net.effector import Effector
|
|
3
|
+
from rid_lib.ext import Cache
|
|
4
|
+
from .network.graph import NetworkGraph
|
|
5
|
+
from .network.event_queue import NetworkEventQueue
|
|
6
|
+
from .network.request_handler import RequestHandler
|
|
7
|
+
from .identity import NodeIdentity
|
|
8
|
+
from .processor.interface import ProcessorInterface
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ActionContext:
|
|
12
|
+
identity: NodeIdentity
|
|
13
|
+
effector: Effector
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
identity: NodeIdentity,
|
|
18
|
+
effector: Effector
|
|
19
|
+
):
|
|
20
|
+
self.identity = identity
|
|
21
|
+
self.effector = effector
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HandlerContext:
|
|
25
|
+
identity: NodeIdentity
|
|
26
|
+
cache: Cache
|
|
27
|
+
event_queue: NetworkEventQueue
|
|
28
|
+
graph: NetworkGraph
|
|
29
|
+
request_handler: RequestHandler
|
|
30
|
+
effector: Effector
|
|
31
|
+
_processor: ProcessorInterface | None
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
identity: NodeIdentity,
|
|
36
|
+
cache: Cache,
|
|
37
|
+
event_queue: NetworkEventQueue,
|
|
38
|
+
graph: NetworkGraph,
|
|
39
|
+
request_handler: RequestHandler,
|
|
40
|
+
effector: Effector
|
|
41
|
+
):
|
|
42
|
+
self.identity = identity
|
|
43
|
+
self.cache = cache
|
|
44
|
+
self.event_queue = event_queue
|
|
45
|
+
self.graph = graph
|
|
46
|
+
self.request_handler = request_handler
|
|
47
|
+
self.effector = effector
|
|
48
|
+
self._processor = None
|
|
49
|
+
|
|
50
|
+
def set_processor(self, processor: ProcessorInterface):
|
|
51
|
+
self._processor = processor
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def handle(self):
|
|
55
|
+
return self._processor.handle
|
koi_net/core.py
CHANGED
|
@@ -1,51 +1,101 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from
|
|
3
|
-
import
|
|
4
|
-
from
|
|
5
|
-
from .network import
|
|
6
|
-
from .
|
|
2
|
+
from koi_net.protocol.node import NodeType
|
|
3
|
+
from rid_lib.ext import Cache
|
|
4
|
+
from .network.resolver import NetworkResolver
|
|
5
|
+
from .network.event_queue import NetworkEventQueue
|
|
6
|
+
from .network.graph import NetworkGraph
|
|
7
|
+
from .network.request_handler import RequestHandler
|
|
8
|
+
from .network.response_handler import ResponseHandler
|
|
9
|
+
from .network.error_handler import ErrorHandler
|
|
10
|
+
from .network.behavior import Actor
|
|
11
|
+
from .processor.interface import ProcessorInterface
|
|
7
12
|
from .processor import default_handlers
|
|
8
13
|
from .processor.handler import KnowledgeHandler
|
|
14
|
+
from .processor.knowledge_pipeline import KnowledgePipeline
|
|
9
15
|
from .identity import NodeIdentity
|
|
10
|
-
from .
|
|
11
|
-
from .config import
|
|
16
|
+
from .secure import Secure
|
|
17
|
+
from .config import NodeConfig
|
|
18
|
+
from .context import HandlerContext, ActionContext
|
|
19
|
+
from .effector import Effector
|
|
20
|
+
from .server import NodeServer
|
|
21
|
+
from .lifecycle import NodeLifecycle
|
|
22
|
+
from .poller import NodePoller
|
|
23
|
+
from . import default_actions
|
|
12
24
|
|
|
13
25
|
logger = logging.getLogger(__name__)
|
|
14
26
|
|
|
15
27
|
|
|
16
28
|
|
|
17
|
-
class NodeInterface
|
|
18
|
-
config:
|
|
29
|
+
class NodeInterface:
|
|
30
|
+
config: NodeConfig
|
|
19
31
|
cache: Cache
|
|
20
32
|
identity: NodeIdentity
|
|
21
|
-
|
|
33
|
+
resolver: NetworkResolver
|
|
34
|
+
event_queue: NetworkEventQueue
|
|
35
|
+
graph: NetworkGraph
|
|
22
36
|
processor: ProcessorInterface
|
|
37
|
+
secure: Secure
|
|
38
|
+
server: NodeServer
|
|
23
39
|
|
|
24
40
|
use_kobj_processor_thread: bool
|
|
25
41
|
|
|
26
42
|
def __init__(
|
|
27
|
-
self,
|
|
28
|
-
config:
|
|
43
|
+
self,
|
|
44
|
+
config: NodeConfig,
|
|
29
45
|
use_kobj_processor_thread: bool = False,
|
|
30
|
-
|
|
31
46
|
handlers: list[KnowledgeHandler] | None = None,
|
|
32
|
-
|
|
33
47
|
cache: Cache | None = None,
|
|
34
|
-
network: NetworkInterface | None = None,
|
|
35
48
|
processor: ProcessorInterface | None = None
|
|
36
49
|
):
|
|
37
|
-
self.config
|
|
50
|
+
self.config = config
|
|
38
51
|
self.cache = cache or Cache(
|
|
39
|
-
self.config.koi_net.cache_directory_path
|
|
52
|
+
directory_path=self.config.koi_net.cache_directory_path
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
self.identity = NodeIdentity(config=self.config)
|
|
56
|
+
self.effector = Effector(cache=self.cache)
|
|
57
|
+
|
|
58
|
+
self.graph = NetworkGraph(
|
|
59
|
+
cache=self.cache,
|
|
60
|
+
identity=self.identity
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
self.secure = Secure(
|
|
64
|
+
identity=self.identity,
|
|
65
|
+
effector=self.effector,
|
|
66
|
+
config=self.config
|
|
67
|
+
)
|
|
40
68
|
|
|
41
|
-
self.
|
|
69
|
+
self.request_handler = RequestHandler(
|
|
70
|
+
effector=self.effector,
|
|
71
|
+
identity=self.identity,
|
|
72
|
+
secure=self.secure
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
self.response_handler = ResponseHandler(self.cache, self.effector)
|
|
76
|
+
|
|
77
|
+
self.resolver = NetworkResolver(
|
|
42
78
|
config=self.config,
|
|
43
|
-
cache=self.cache
|
|
79
|
+
cache=self.cache,
|
|
80
|
+
identity=self.identity,
|
|
81
|
+
graph=self.graph,
|
|
82
|
+
request_handler=self.request_handler,
|
|
83
|
+
effector=self.effector
|
|
84
|
+
)
|
|
44
85
|
|
|
45
|
-
self.
|
|
86
|
+
self.event_queue = NetworkEventQueue(
|
|
46
87
|
config=self.config,
|
|
47
88
|
cache=self.cache,
|
|
48
|
-
identity=self.identity
|
|
89
|
+
identity=self.identity,
|
|
90
|
+
graph=self.graph,
|
|
91
|
+
request_handler=self.request_handler,
|
|
92
|
+
effector=self.effector
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.actor = Actor(
|
|
96
|
+
identity=self.identity,
|
|
97
|
+
effector=self.effector,
|
|
98
|
+
event_queue=self.event_queue
|
|
49
99
|
)
|
|
50
100
|
|
|
51
101
|
# pull all handlers defined in default_handlers module
|
|
@@ -56,71 +106,71 @@ class NodeInterface(Generic[ConfigType]):
|
|
|
56
106
|
]
|
|
57
107
|
|
|
58
108
|
self.use_kobj_processor_thread = use_kobj_processor_thread
|
|
109
|
+
|
|
110
|
+
self.action_context = ActionContext(
|
|
111
|
+
identity=self.identity,
|
|
112
|
+
effector=self.effector
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
self.handler_context = HandlerContext(
|
|
116
|
+
identity=self.identity,
|
|
117
|
+
cache=self.cache,
|
|
118
|
+
event_queue=self.event_queue,
|
|
119
|
+
graph=self.graph,
|
|
120
|
+
request_handler=self.request_handler,
|
|
121
|
+
effector=self.effector
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
self.pipeline = KnowledgePipeline(
|
|
125
|
+
handler_context=self.handler_context,
|
|
126
|
+
cache=self.cache,
|
|
127
|
+
request_handler=self.request_handler,
|
|
128
|
+
event_queue=self.event_queue,
|
|
129
|
+
graph=self.graph,
|
|
130
|
+
default_handlers=handlers
|
|
131
|
+
)
|
|
132
|
+
|
|
59
133
|
self.processor = processor or ProcessorInterface(
|
|
134
|
+
pipeline=self.pipeline,
|
|
135
|
+
use_kobj_processor_thread=self.use_kobj_processor_thread
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
self.error_handler = ErrorHandler(
|
|
139
|
+
processor=self.processor,
|
|
140
|
+
actor=self.actor
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
self.request_handler.set_error_handler(self.error_handler)
|
|
144
|
+
|
|
145
|
+
self.handler_context.set_processor(self.processor)
|
|
146
|
+
|
|
147
|
+
self.effector.set_processor(self.processor)
|
|
148
|
+
self.effector.set_resolver(self.resolver)
|
|
149
|
+
self.effector.set_action_context(self.action_context)
|
|
150
|
+
|
|
151
|
+
self.lifecycle = NodeLifecycle(
|
|
60
152
|
config=self.config,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
153
|
+
identity=self.identity,
|
|
154
|
+
graph=self.graph,
|
|
155
|
+
processor=self.processor,
|
|
156
|
+
effector=self.effector,
|
|
157
|
+
actor=self.actor,
|
|
158
|
+
use_kobj_processor_thread=use_kobj_processor_thread
|
|
66
159
|
)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
self.
|
|
76
|
-
|
|
77
|
-
self.network._load_event_queues()
|
|
78
|
-
self.network.graph.generate()
|
|
79
|
-
|
|
80
|
-
self.processor.handle(
|
|
81
|
-
bundle=Bundle.generate(
|
|
82
|
-
rid=self.identity.rid,
|
|
83
|
-
contents=self.identity.profile.model_dump()
|
|
84
|
-
)
|
|
160
|
+
|
|
161
|
+
# if self.config.koi_net.node_profile.node_type == NodeType.FULL:
|
|
162
|
+
self.server = NodeServer(
|
|
163
|
+
config=self.config,
|
|
164
|
+
lifecycle=self.lifecycle,
|
|
165
|
+
secure=self.secure,
|
|
166
|
+
processor=self.processor,
|
|
167
|
+
event_queue=self.event_queue,
|
|
168
|
+
response_handler=self.response_handler
|
|
85
169
|
)
|
|
86
170
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
self.
|
|
90
|
-
|
|
91
|
-
self.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if not self.network.graph.get_neighbors() and self.config.koi_net.first_contact:
|
|
95
|
-
logger.debug(f"I don't have any neighbors, reaching out to first contact {self.config.koi_net.first_contact}")
|
|
96
|
-
|
|
97
|
-
events = [
|
|
98
|
-
Event.from_rid(EventType.FORGET, self.identity.rid),
|
|
99
|
-
Event.from_bundle(EventType.NEW, self.identity.bundle)
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
try:
|
|
103
|
-
self.network.request_handler.broadcast_events(
|
|
104
|
-
url=self.config.koi_net.first_contact,
|
|
105
|
-
events=events
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
except httpx.ConnectError:
|
|
109
|
-
logger.warning("Failed to reach first contact")
|
|
110
|
-
return
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def stop(self):
|
|
114
|
-
"""Stops a node, call this method last.
|
|
115
|
-
|
|
116
|
-
Finishes processing knowledge object queue. Saves event queues to storage.
|
|
117
|
-
"""
|
|
118
|
-
logger.info("Stopping node...")
|
|
119
|
-
|
|
120
|
-
if self.use_kobj_processor_thread:
|
|
121
|
-
logger.info(f"Waiting for kobj queue to empty ({self.processor.kobj_queue.unfinished_tasks} tasks remaining)")
|
|
122
|
-
self.processor.kobj_queue.join()
|
|
123
|
-
else:
|
|
124
|
-
self.processor.flush_kobj_queue()
|
|
125
|
-
|
|
126
|
-
self.network._save_event_queues()
|
|
171
|
+
self.poller = NodePoller(
|
|
172
|
+
processor=self.processor,
|
|
173
|
+
lifecycle=self.lifecycle,
|
|
174
|
+
resolver=self.resolver,
|
|
175
|
+
config=self.config
|
|
176
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .context import ActionContext
|
|
2
|
+
from rid_lib.types import KoiNetNode
|
|
3
|
+
from rid_lib.ext import Bundle
|
|
4
|
+
from .effector import Effector
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@Effector.register_default_action(KoiNetNode)
|
|
8
|
+
def dereference_koi_node(ctx: ActionContext, rid: KoiNetNode) -> Bundle:
|
|
9
|
+
if rid != ctx.identity.rid:
|
|
10
|
+
return
|
|
11
|
+
|
|
12
|
+
return Bundle.generate(
|
|
13
|
+
rid=ctx.identity.rid,
|
|
14
|
+
contents=ctx.identity.profile.model_dump()
|
|
15
|
+
)
|
koi_net/effector.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Callable
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from rid_lib.ext import Cache, Bundle
|
|
5
|
+
from rid_lib.core import RID, RIDType
|
|
6
|
+
from rid_lib.types import KoiNetNode
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .network.resolver import NetworkResolver
|
|
12
|
+
from .processor.interface import ProcessorInterface
|
|
13
|
+
from .context import ActionContext
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BundleSource(StrEnum):
|
|
19
|
+
CACHE = "CACHE"
|
|
20
|
+
ACTION = "ACTION"
|
|
21
|
+
|
|
22
|
+
class Effector:
|
|
23
|
+
cache: Cache
|
|
24
|
+
resolver: "NetworkResolver | None"
|
|
25
|
+
processor: "ProcessorInterface | None"
|
|
26
|
+
action_context: "ActionContext | None"
|
|
27
|
+
_action_table: dict[
|
|
28
|
+
type[RID],
|
|
29
|
+
Callable[
|
|
30
|
+
["ActionContext", RID],
|
|
31
|
+
Bundle | None
|
|
32
|
+
]
|
|
33
|
+
] = dict()
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
cache: Cache,
|
|
38
|
+
):
|
|
39
|
+
self.cache = cache
|
|
40
|
+
self.resolver = None
|
|
41
|
+
self.processor = None
|
|
42
|
+
self.action_context = None
|
|
43
|
+
self._action_table = self.__class__._action_table.copy()
|
|
44
|
+
|
|
45
|
+
def set_processor(self, processor: "ProcessorInterface"):
|
|
46
|
+
self.processor = processor
|
|
47
|
+
|
|
48
|
+
def set_resolver(self, resolver: "NetworkResolver"):
|
|
49
|
+
self.resolver = resolver
|
|
50
|
+
|
|
51
|
+
def set_action_context(self, action_context: "ActionContext"):
|
|
52
|
+
self.action_context = action_context
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def register_default_action(cls, rid_type: RIDType):
|
|
56
|
+
def decorator(func: Callable) -> Callable:
|
|
57
|
+
cls._action_table[rid_type] = func
|
|
58
|
+
return func
|
|
59
|
+
return decorator
|
|
60
|
+
|
|
61
|
+
def register_action(self, rid_type: RIDType):
|
|
62
|
+
def decorator(func: Callable) -> Callable:
|
|
63
|
+
self._action_table[rid_type] = func
|
|
64
|
+
return func
|
|
65
|
+
return decorator
|
|
66
|
+
|
|
67
|
+
def _try_cache(self, rid: RID) -> tuple[Bundle, BundleSource] | None:
|
|
68
|
+
bundle = self.cache.read(rid)
|
|
69
|
+
|
|
70
|
+
if bundle:
|
|
71
|
+
logger.debug("Cache hit")
|
|
72
|
+
return bundle, BundleSource.CACHE
|
|
73
|
+
else:
|
|
74
|
+
logger.debug("Cache miss")
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def _try_action(self, rid: RID) -> tuple[Bundle, BundleSource] | None:
|
|
78
|
+
if type(rid) not in self._action_table:
|
|
79
|
+
logger.debug("No action available")
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
logger.debug("Action available")
|
|
83
|
+
func = self._action_table[type(rid)]
|
|
84
|
+
bundle = func(
|
|
85
|
+
ctx=self.action_context,
|
|
86
|
+
rid=rid
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if bundle:
|
|
90
|
+
logger.debug("Action hit")
|
|
91
|
+
return bundle, BundleSource.ACTION
|
|
92
|
+
else:
|
|
93
|
+
logger.debug("Action miss")
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _try_network(self, rid: RID) -> tuple[Bundle, KoiNetNode] | None:
|
|
98
|
+
bundle, source = self.resolver.fetch_remote_bundle(rid)
|
|
99
|
+
|
|
100
|
+
if bundle:
|
|
101
|
+
logger.debug("Network hit")
|
|
102
|
+
return bundle, source
|
|
103
|
+
else:
|
|
104
|
+
logger.debug("Network miss")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def deref(
|
|
109
|
+
self,
|
|
110
|
+
rid: RID,
|
|
111
|
+
refresh_cache: bool = False,
|
|
112
|
+
use_network: bool = False,
|
|
113
|
+
handle_result: bool = True
|
|
114
|
+
) -> Bundle | None:
|
|
115
|
+
logger.debug(f"Dereferencing {rid!r}")
|
|
116
|
+
|
|
117
|
+
bundle, source = (
|
|
118
|
+
# if `refresh_cache`, skip try cache
|
|
119
|
+
not refresh_cache and self._try_cache(rid) or
|
|
120
|
+
self._try_action(rid) or
|
|
121
|
+
use_network and self._try_network(rid) or
|
|
122
|
+
# if not found, bundle and source set to None
|
|
123
|
+
(None, None)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
handle_result
|
|
128
|
+
and bundle is not None
|
|
129
|
+
and source != BundleSource.CACHE
|
|
130
|
+
):
|
|
131
|
+
self.processor.handle(
|
|
132
|
+
bundle=bundle,
|
|
133
|
+
source=source if type(source) is KoiNetNode else None
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# TODO: refactor for general solution, param to write through to cache before continuing
|
|
137
|
+
# like `self.processor.kobj_queue.join()``
|
|
138
|
+
|
|
139
|
+
return bundle
|
koi_net/identity.py
CHANGED
|
@@ -1,33 +1,19 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from rid_lib.ext.bundle import Bundle
|
|
3
|
-
from rid_lib.ext.cache import Cache
|
|
4
2
|
from rid_lib.types.koi_net_node import KoiNetNode
|
|
5
|
-
|
|
6
3
|
from .config import NodeConfig
|
|
7
4
|
from .protocol.node import NodeProfile
|
|
8
5
|
|
|
6
|
+
|
|
9
7
|
logger = logging.getLogger(__name__)
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
class NodeIdentity:
|
|
13
|
-
"""Represents a node's identity (RID, profile
|
|
11
|
+
"""Represents a node's identity (RID, profile)."""
|
|
14
12
|
|
|
15
13
|
config: NodeConfig
|
|
16
|
-
cache: Cache
|
|
17
14
|
|
|
18
|
-
def __init__(
|
|
19
|
-
self,
|
|
20
|
-
config: NodeConfig,
|
|
21
|
-
cache: Cache
|
|
22
|
-
):
|
|
23
|
-
"""Initializes node identity from a name and profile.
|
|
24
|
-
|
|
25
|
-
Attempts to read identity from storage. If it doesn't already exist, a new RID is generated from the provided name, and that RID and profile are written to storage. Changes to the name or profile will update the stored identity.
|
|
26
|
-
|
|
27
|
-
WARNING: If the name is changed, the RID will be overwritten which will have consequences for the rest of the network.
|
|
28
|
-
"""
|
|
15
|
+
def __init__(self, config: NodeConfig):
|
|
29
16
|
self.config = config
|
|
30
|
-
self.cache = cache
|
|
31
17
|
|
|
32
18
|
@property
|
|
33
19
|
def rid(self) -> KoiNetNode:
|
|
@@ -35,8 +21,4 @@ class NodeIdentity:
|
|
|
35
21
|
|
|
36
22
|
@property
|
|
37
23
|
def profile(self) -> NodeProfile:
|
|
38
|
-
return self.config.koi_net.node_profile
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def bundle(self) -> Bundle:
|
|
42
|
-
return self.cache.read(self.rid)
|
|
24
|
+
return self.config.koi_net.node_profile
|
koi_net/lifecycle.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from .network.behavior import Actor
|
|
4
|
+
from .effector import Effector
|
|
5
|
+
from .config import NodeConfig
|
|
6
|
+
from .processor.interface import ProcessorInterface
|
|
7
|
+
from .network.graph import NetworkGraph
|
|
8
|
+
from .identity import NodeIdentity
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NodeLifecycle:
|
|
14
|
+
config: NodeConfig
|
|
15
|
+
graph: NetworkGraph
|
|
16
|
+
processor: ProcessorInterface
|
|
17
|
+
effector: Effector
|
|
18
|
+
actor: Actor
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
config: NodeConfig,
|
|
23
|
+
identity: NodeIdentity,
|
|
24
|
+
graph: NetworkGraph,
|
|
25
|
+
processor: ProcessorInterface,
|
|
26
|
+
effector: Effector,
|
|
27
|
+
actor: Actor,
|
|
28
|
+
use_kobj_processor_thread: bool
|
|
29
|
+
):
|
|
30
|
+
self.config = config
|
|
31
|
+
self.identity = identity
|
|
32
|
+
self.graph = graph
|
|
33
|
+
self.processor = processor
|
|
34
|
+
self.effector = effector
|
|
35
|
+
self.actor = actor
|
|
36
|
+
self.use_kobj_processor_thread = use_kobj_processor_thread
|
|
37
|
+
|
|
38
|
+
def start(self) -> None:
|
|
39
|
+
"""Starts a node, call this method first.
|
|
40
|
+
|
|
41
|
+
Starts the processor thread (if enabled). Loads event queues into memory. Generates network graph from nodes and edges in cache. Processes any state changes of node bundle. Initiates handshake with first contact (if provided) if node doesn't have any neighbors.
|
|
42
|
+
"""
|
|
43
|
+
if self.use_kobj_processor_thread:
|
|
44
|
+
logger.info("Starting processor worker thread")
|
|
45
|
+
self.processor.worker_thread.start()
|
|
46
|
+
|
|
47
|
+
self.graph.generate()
|
|
48
|
+
|
|
49
|
+
# refresh to reflect changes (if any) in config.yaml
|
|
50
|
+
self.effector.deref(self.identity.rid, refresh_cache=True)
|
|
51
|
+
|
|
52
|
+
logger.debug("Waiting for kobj queue to empty")
|
|
53
|
+
if self.use_kobj_processor_thread:
|
|
54
|
+
self.processor.kobj_queue.join()
|
|
55
|
+
else:
|
|
56
|
+
self.processor.flush_kobj_queue()
|
|
57
|
+
logger.debug("Done")
|
|
58
|
+
|
|
59
|
+
if not self.graph.get_neighbors() and self.config.koi_net.first_contact.rid:
|
|
60
|
+
logger.debug(f"I don't have any neighbors, reaching out to first contact {self.config.koi_net.first_contact.rid!r}")
|
|
61
|
+
|
|
62
|
+
self.actor.handshake_with(self.config.koi_net.first_contact.rid)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def stop(self):
|
|
66
|
+
"""Stops a node, call this method last.
|
|
67
|
+
|
|
68
|
+
Finishes processing knowledge object queue. Saves event queues to storage.
|
|
69
|
+
"""
|
|
70
|
+
logger.info("Stopping node...")
|
|
71
|
+
|
|
72
|
+
if self.use_kobj_processor_thread:
|
|
73
|
+
logger.info(f"Waiting for kobj queue to empty ({self.processor.kobj_queue.unfinished_tasks} tasks remaining)")
|
|
74
|
+
self.processor.kobj_queue.join()
|
|
75
|
+
else:
|
|
76
|
+
self.processor.flush_kobj_queue()
|