distributed-state-network 0.0.1__tar.gz → 0.0.2__tar.gz

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.
Files changed (27) hide show
  1. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/.gitignore +2 -1
  2. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/PKG-INFO +3 -23
  3. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/README.md +2 -22
  4. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/pyproject.toml +1 -1
  5. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/dsnode.py +4 -4
  6. distributed_state_network-0.0.2/tests/connections.py +75 -0
  7. distributed_state_network-0.0.1/security.md +0 -14
  8. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/.github/workflows/publish.yml +0 -0
  9. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/LICENSE +0 -0
  10. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/coverage.sh +0 -0
  11. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/__init__.py +0 -0
  12. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/create_key.py +0 -0
  13. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/handler.py +0 -0
  14. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/config.py +0 -0
  15. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/endpoint.py +0 -0
  16. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/hello_packet.py +0 -0
  17. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/peers_packet.py +0 -0
  18. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/signed_packet.py +0 -0
  19. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/objects/state_packet.py +0 -0
  20. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/__init__.py +0 -0
  21. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/aes.py +0 -0
  22. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/byte_helper.py +0 -0
  23. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/ecdsa.py +0 -0
  24. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/https.py +0 -0
  25. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/src/distributed_state_network/util/key_manager.py +0 -0
  26. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/technical.md +0 -0
  27. {distributed_state_network-0.0.1 → distributed_state_network-0.0.2}/test.py +0 -0
@@ -7,4 +7,5 @@ test.key
7
7
  .coverage
8
8
  count.py
9
9
  appended.py
10
- py_line_count_history.jsonl
10
+ py_line_count_history.jsonl
11
+ network.key
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: distributed-state-network
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A tool to distribute the state of a network device to other devices on the network
5
5
  Project-URL: Homepage, https://github.com/erinclemmer/distributed_state_network
6
6
  Project-URL: Issues, https://github.com/erinclemmer/distributed_state_network/issues
@@ -99,9 +99,7 @@ temperature = node.read_data("sensor_node", "temperature")
99
99
  connected_nodes = node.peers()
100
100
  ```
101
101
 
102
- ## Real-World Examples
103
-
104
- ### Distributed Temperature Monitoring
102
+ ## Example: Distributed Temperature Monitoring
105
103
 
106
104
  Create a network of temperature sensors that share readings:
107
105
 
@@ -128,22 +126,4 @@ for node_id in monitor.node.peers():
128
126
  if node_id.startswith("sensor_"):
129
127
  temp = monitor.node.read_data(node_id, "temperature")
130
128
  print(f"{node_id}: {temp}°F")
131
- ```
132
-
133
- ## Troubleshooting
134
-
135
- ### Node Can't Connect
136
- - Verify the AES key file is identical on all nodes
137
- - Check firewall rules allow traffic on the configured port
138
- - Ensure bootstrap node address is reachable
139
-
140
- ### State Not Updating
141
- - Confirm nodes show as connected with `node.peers()`
142
- - Check network latency between nodes
143
- - Verify no duplicate node IDs (each must be unique)
144
-
145
- ## Performance Characteristics
146
- - State updates typically propagate in <100ms on local networks
147
- - Each node stores the complete state of all other nodes
148
- - Suitable for networks up to ~100 nodes
149
- - State values should be kept reasonably small (< 1MB per key)
129
+ ```
@@ -74,9 +74,7 @@ temperature = node.read_data("sensor_node", "temperature")
74
74
  connected_nodes = node.peers()
75
75
  ```
76
76
 
77
- ## Real-World Examples
78
-
79
- ### Distributed Temperature Monitoring
77
+ ## Example: Distributed Temperature Monitoring
80
78
 
81
79
  Create a network of temperature sensors that share readings:
82
80
 
@@ -103,22 +101,4 @@ for node_id in monitor.node.peers():
103
101
  if node_id.startswith("sensor_"):
104
102
  temp = monitor.node.read_data(node_id, "temperature")
105
103
  print(f"{node_id}: {temp}°F")
106
- ```
107
-
108
- ## Troubleshooting
109
-
110
- ### Node Can't Connect
111
- - Verify the AES key file is identical on all nodes
112
- - Check firewall rules allow traffic on the configured port
113
- - Ensure bootstrap node address is reachable
114
-
115
- ### State Not Updating
116
- - Confirm nodes show as connected with `node.peers()`
117
- - Check network latency between nodes
118
- - Verify no duplicate node IDs (each must be unique)
119
-
120
- ## Performance Characteristics
121
- - State updates typically propagate in <100ms on local networks
122
- - Each node stores the complete state of all other nodes
123
- - Suitable for networks up to ~100 nodes
124
- - State values should be kept reasonably small (< 1MB per key)
104
+ ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "distributed-state-network"
7
- version = "0.0.1"
7
+ version = "0.0.2"
8
8
  authors = [{name="Erin Clemmer", email="erin.c.clemmer@gmail.com"}]
9
9
  description = "A tool to distribute the state of a network device to other devices on the network"
10
10
  keywords = ["distributed", "networking"]
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  import time
3
- import json
4
3
  import logging
5
4
 
6
5
  import requests
@@ -16,7 +15,7 @@ from distributed_state_network.objects.config import DSNodeConfig
16
15
 
17
16
  from distributed_state_network.util import get_dict_hash
18
17
  from distributed_state_network.util.key_manager import CertManager, CredentialManager
19
- from distributed_state_network.util.aes import aes_encrypt, aes_decrypt, generate_aes_key
18
+ from distributed_state_network.util.aes import aes_encrypt, aes_decrypt
20
19
 
21
20
  TICK_INTERVAL = 3
22
21
 
@@ -60,7 +59,7 @@ class DSNode:
60
59
  def network_tick(self):
61
60
  time.sleep(TICK_INTERVAL)
62
61
  if self.shutting_down:
63
- self.logger.info(f"Shutting down node")
62
+ self.logger.info("Shutting down node")
64
63
  return
65
64
  self.test_connections()
66
65
  threading.Thread(target=self.network_tick).start()
@@ -69,9 +68,10 @@ class DSNode:
69
68
  def remove(node_id: str):
70
69
  if node_id in self.node_states:
71
70
  del self.node_states[node_id]
71
+ del self.address_book[node_id]
72
72
  self.logger.info(f"PING failed for {node_id}, disconnecting...")
73
73
  for node_id in self.node_states.copy().keys():
74
- if not node_id in self.node_states or node_id == self.config.node_id:
74
+ if node_id not in self.node_states or node_id == self.config.node_id:
75
75
  continue
76
76
  try:
77
77
  if self.shutting_down:
@@ -0,0 +1,75 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ import unittest
5
+
6
+ sys.path.append(os.path.join(os.path.dirname(__file__), '../src'))
7
+
8
+ from distributed_state_network import DSNodeServer, DSNodeConfig, Endpoint
9
+
10
+ KEY_FILE = "network.key"
11
+
12
+ if not os.path.exists(KEY_FILE):
13
+ DSNodeServer.generate_key("network.key")
14
+
15
+ current_port = 5000
16
+
17
+ def start_node(node_id: str, bootstrap_port: int = None):
18
+ global current_port
19
+ args = {
20
+ "node_id": node_id,
21
+ "port": current_port,
22
+ "aes_key_file": KEY_FILE
23
+ }
24
+ current_port += 1
25
+ if bootstrap_port is not None:
26
+ args["bootstrap_nodes"] = [Endpoint(
27
+ address="127.0.0.1",
28
+ port=bootstrap_port
29
+ )]
30
+ else:
31
+ args["bootstrap_nodes"] = []
32
+
33
+ return DSNodeServer.start(DSNodeConfig(**args))
34
+
35
+ class ConnectionsTest(unittest.TestCase):
36
+ def test_one(self):
37
+ node = start_node("node-1")
38
+ self.assertEqual(["node-1"], node.node.peers())
39
+
40
+ def test_two(self):
41
+ node1 = start_node("node-1")
42
+ node2 = start_node("node-2", node1.config.port)
43
+ self.assertEqual(["node-1", "node-2"], sorted(node1.node.peers()))
44
+ self.assertEqual(["node-1", "node-2"], sorted(node2.node.peers()))
45
+
46
+ def test_many(self):
47
+ bootstrap_node = start_node("bootstrap")
48
+ nodes = []
49
+ for i in range(10):
50
+ nodes.append(start_node(f"node-{i}", bootstrap_node.config.port))
51
+
52
+ node_list = ["bootstrap"]
53
+ node_list.extend([f"node-{i}" for i in range(10)])
54
+
55
+ time.sleep(5)
56
+ self.assertEqual(node_list, sorted(bootstrap_node.node.peers()))
57
+ for i in range(10):
58
+ self.assertEqual(node_list, sorted(nodes[i].node.peers()))
59
+
60
+ def test_disconnect(self):
61
+ node1 = start_node("node-1")
62
+ node2 = start_node("node-2", node1.config.port)
63
+ node3 = start_node("node-3", node1.config.port)
64
+
65
+ time.sleep(1)
66
+ node2.stop()
67
+ time.sleep(10)
68
+ node4 = start_node("node-4", node1.config.port)
69
+ time.sleep(10)
70
+ self.assertEqual(["node-1", "node-3", "node-4"], sorted(node1.node.peers()))
71
+ self.assertEqual(["node-1", "node-3", "node-4"], sorted(node3.node.peers()))
72
+ self.assertEqual(["node-1", "node-3", "node-4"], sorted(node4.node.peers()))
73
+
74
+ if __name__ == '__main__':
75
+ unittest.main()
@@ -1,14 +0,0 @@
1
- ## Security Architecture
2
- We use a three-layer security model where each layer addresses a specific vulnerability. Understanding this design helps explain why certain setup steps are necessary and how your data stays protected.
3
- The Three-Layer Defense
4
- Think of our security like protecting a secret club where members share sensitive information. You need multiple types of protection because different attacks require different defenses.
5
- ### Layer 1: AES
6
- AES Encryption acts as the network's membership card. Every message between nodes is encrypted with a shared AES key, making the network invisible to outsiders. If someone intercepts your network traffic without this key, they see only meaningless encrypted bytes. This is why you must carefully share the key file with trusted nodes - it's the fundamental gatekeeper of your network.
7
- ### Layer 2: ECDSA
8
- ECDSA Signatures solve a critical problem that AES alone cannot address. Once someone has the network key, how do you prevent them from impersonating other nodes? Each node has its own unforgeable cryptographic identity. When node "sensor_1" shares its temperature reading, it signs that data with its private key. Other nodes verify this signature to ensure the data truly came from sensor_1, not from an attacker pretending to be sensor_1. This is why node IDs must be unique - each ID is permanently bound to a specific cryptographic identity.
9
- ### Layer 3: HTTPS
10
- HTTPS/TLS prevents sophisticated network-level attacks. Even with encryption and signatures, an attacker positioned between two nodes could intercept messages, block updates, or redirect traffic to malicious servers. HTTPS certificates ensure you're communicating with the exact node you intend to reach. During the initial handshake, nodes exchange certificates that get verified on every subsequent connection, creating authenticated channels that can't be hijacked.
11
-
12
- ## Why This Matters
13
- Each layer compensates for what the others cannot protect. AES keeps outsiders out but can't distinguish between insiders. ECDSA proves identity but doesn't hide data. HTTPS secures the transport but doesn't control network access. Together, they create a system where only authorized nodes can join, every piece of state data is cryptographically guaranteed to come from its claimed source, and all communications flow through verified channels. This isn't over-engineering - it's the minimum required for nodes to trust each other in a truly distributed system without any central authority.
14
- When you see the various key files and certificates being generated during setup, you're watching these three defensive layers being constructed. Each one is essential for maintaining the integrity and privacy of your distributed application.