@le-space/rootfs 0.3.18 → 0.3.20
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.
- package/package.json +1 -1
- package/reference/uc-go-peer/rootfs/build-rootfs-image.sh +3 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-bootstrap-deregister.py +327 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-bootstrap-deregister.service +19 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-bootstrap-refresh.py +38 -12
- package/reference/uc-go-peer/rootfs/uc-go-peer-configure.sh +32 -0
package/package.json
CHANGED
|
@@ -86,11 +86,13 @@ virt-customize \
|
|
|
86
86
|
--copy-in "${APP_BINARY}:${GUEST_APP_DIR}" \
|
|
87
87
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap.sh:/usr/local/sbin" \
|
|
88
88
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-configure.sh:/usr/local/sbin" \
|
|
89
|
+
--copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap-deregister.py:/usr/local/sbin" \
|
|
89
90
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap-refresh.py:/usr/local/sbin" \
|
|
90
91
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-autotls-refresh.py:/usr/local/sbin" \
|
|
91
92
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-describe.py:/usr/local/sbin" \
|
|
92
93
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-setup-server.py:/usr/local/sbin" \
|
|
93
94
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap.service:/etc/systemd/system" \
|
|
95
|
+
--copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap-deregister.service:/etc/systemd/system" \
|
|
94
96
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap-refresh.service:/etc/systemd/system" \
|
|
95
97
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-bootstrap-refresh.timer:/etc/systemd/system" \
|
|
96
98
|
--copy-in "${SCRIPT_DIR}/uc-go-peer-autotls-refresh.service:/etc/systemd/system" \
|
|
@@ -98,6 +100,7 @@ virt-customize \
|
|
|
98
100
|
--run-command "chmod 0755 ${ROOTFS_CONTRACT_BINARY_PATH}" \
|
|
99
101
|
--run-command "chmod 0755 /usr/local/sbin/uc-go-peer-bootstrap.sh" \
|
|
100
102
|
--run-command "chmod 0755 /usr/local/sbin/uc-go-peer-configure.sh" \
|
|
103
|
+
--run-command "chmod 0755 /usr/local/sbin/uc-go-peer-bootstrap-deregister.py" \
|
|
101
104
|
--run-command "chmod 0755 /usr/local/sbin/uc-go-peer-bootstrap-refresh.py" \
|
|
102
105
|
--run-command "chmod 0755 /usr/local/sbin/uc-go-peer-autotls-refresh.py" \
|
|
103
106
|
--run-command "chmod 0755 /usr/local/sbin/uc-go-peer-describe.py" \
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
import urllib.error
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import urllib.request
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from eth_account import Account
|
|
14
|
+
from eth_account.messages import encode_defunct
|
|
15
|
+
except ImportError as error: # pragma: no cover - runtime dependency
|
|
16
|
+
raise SystemExit(
|
|
17
|
+
"eth-account is required for guest-side bootstrap deregistration"
|
|
18
|
+
) from error
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
ENV_FILE = os.environ.get("ENV_FILE", "/etc/default/uc-go-peer")
|
|
22
|
+
DESCRIBE_SCRIPT = os.environ.get("DESCRIBE_SCRIPT", "/usr/local/sbin/uc-go-peer-describe.py")
|
|
23
|
+
DEFAULT_API_HOST = os.environ.get("ALEPH_BOOTSTRAP_API_HOST", "https://api2.aleph.im")
|
|
24
|
+
DEFAULT_CHANNEL = os.environ.get("ALEPH_BOOTSTRAP_CHANNEL", "simple-todo")
|
|
25
|
+
DEFAULT_REF = os.environ.get("ALEPH_BOOTSTRAP_REF", "simple-todo-bootstrap")
|
|
26
|
+
DEFAULT_POST_TYPE = os.environ.get("ALEPH_BOOTSTRAP_POST_TYPE", "relay-bootstrap")
|
|
27
|
+
MAX_PREVIOUS_PAGES = int(os.environ.get("ALEPH_BOOTSTRAP_MAX_PREVIOUS_PAGES", "5"))
|
|
28
|
+
PAGINATION = int(os.environ.get("ALEPH_BOOTSTRAP_PAGINATION", "50"))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def parse_env_file(path: str) -> dict[str, str]:
|
|
32
|
+
values: dict[str, str] = {}
|
|
33
|
+
if not os.path.exists(path):
|
|
34
|
+
return values
|
|
35
|
+
|
|
36
|
+
with open(path, encoding="utf-8") as handle:
|
|
37
|
+
for line in handle:
|
|
38
|
+
stripped = line.strip()
|
|
39
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
40
|
+
continue
|
|
41
|
+
key, value = stripped.split("=", 1)
|
|
42
|
+
values[key.strip()] = value.strip()
|
|
43
|
+
return values
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def json_dumps(payload: object) -> str:
|
|
47
|
+
return json.dumps(payload, separators=(",", ":"))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def dedupe(values: list[str]) -> list[str]:
|
|
51
|
+
seen: set[str] = set()
|
|
52
|
+
result: list[str] = []
|
|
53
|
+
for value in values:
|
|
54
|
+
if value and value not in seen:
|
|
55
|
+
seen.add(value)
|
|
56
|
+
result.append(value)
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def sign_personal_message(private_key: str, payload: str) -> str:
|
|
61
|
+
message = encode_defunct(text=payload)
|
|
62
|
+
signed = Account.sign_message(message, private_key=private_key)
|
|
63
|
+
signature = signed.signature.hex()
|
|
64
|
+
return signature if signature.startswith("0x") else f"0x{signature}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def address_from_private_key(private_key: str) -> str:
|
|
68
|
+
return Account.from_key(private_key).address
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def signature_payload(chain: str, sender: str, message_type: str, item_hash: str) -> str:
|
|
72
|
+
return "\n".join([chain, sender, message_type, item_hash])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def post_json(url: str, body: dict[str, object]) -> tuple[int, dict]:
|
|
76
|
+
data = json_dumps(body).encode("utf-8")
|
|
77
|
+
request = urllib.request.Request(
|
|
78
|
+
url,
|
|
79
|
+
data=data,
|
|
80
|
+
headers={"content-type": "application/json"},
|
|
81
|
+
method="POST",
|
|
82
|
+
)
|
|
83
|
+
try:
|
|
84
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
85
|
+
payload = response.read().decode("utf-8")
|
|
86
|
+
return response.status, json.loads(payload or "{}")
|
|
87
|
+
except urllib.error.HTTPError as error:
|
|
88
|
+
payload = error.read().decode("utf-8")
|
|
89
|
+
try:
|
|
90
|
+
return error.code, json.loads(payload or "{}")
|
|
91
|
+
except json.JSONDecodeError:
|
|
92
|
+
return error.code, {"details": payload}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_json(url: str) -> dict:
|
|
96
|
+
with urllib.request.urlopen(url, timeout=30) as response:
|
|
97
|
+
return json.loads(response.read().decode("utf-8") or "{}")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def sign_aleph_message(unsigned_message: dict[str, object], private_key: str) -> dict[str, object]:
|
|
101
|
+
signed = dict(unsigned_message)
|
|
102
|
+
signed["signature"] = sign_personal_message(
|
|
103
|
+
private_key,
|
|
104
|
+
signature_payload(
|
|
105
|
+
str(unsigned_message["chain"]),
|
|
106
|
+
str(unsigned_message["sender"]),
|
|
107
|
+
str(unsigned_message["type"]),
|
|
108
|
+
str(unsigned_message["item_hash"]),
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
return signed
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def is_invalid_message_format(http_status: int, payload: dict) -> bool:
|
|
115
|
+
if http_status != 422:
|
|
116
|
+
return False
|
|
117
|
+
details = payload.get("details")
|
|
118
|
+
if isinstance(details, str) and "InvalidMessageFormat" in details:
|
|
119
|
+
return True
|
|
120
|
+
if isinstance(details, dict):
|
|
121
|
+
message = details.get("message")
|
|
122
|
+
if isinstance(message, str) and "InvalidMessageFormat" in message:
|
|
123
|
+
return True
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def is_retryable_broadcast_failure(http_status: int, payload: dict) -> bool:
|
|
128
|
+
if http_status >= 500:
|
|
129
|
+
return True
|
|
130
|
+
publication_status = payload.get("publication_status")
|
|
131
|
+
if isinstance(publication_status, dict):
|
|
132
|
+
status = publication_status.get("status")
|
|
133
|
+
if isinstance(status, str) and status.strip().lower() == "error":
|
|
134
|
+
return True
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def broadcast_aleph_message(api_host: str, message: dict[str, object]) -> tuple[int, dict]:
|
|
139
|
+
url = urllib.parse.urljoin(api_host.rstrip("/") + "/", "api/v0/messages")
|
|
140
|
+
attempts = [
|
|
141
|
+
{"sync": True, "message": message},
|
|
142
|
+
{**message, "sync": True},
|
|
143
|
+
dict(message),
|
|
144
|
+
]
|
|
145
|
+
for index, attempt in enumerate(attempts):
|
|
146
|
+
http_status, payload = post_json(url, attempt)
|
|
147
|
+
if 200 <= http_status < 300:
|
|
148
|
+
return http_status, payload
|
|
149
|
+
can_retry = index < len(attempts) - 1 and (
|
|
150
|
+
is_invalid_message_format(http_status, payload)
|
|
151
|
+
or is_retryable_broadcast_failure(http_status, payload)
|
|
152
|
+
)
|
|
153
|
+
if not can_retry:
|
|
154
|
+
raise RuntimeError(f"Aleph broadcast failed: {http_status} {json_dumps(payload)}")
|
|
155
|
+
raise RuntimeError("Aleph broadcast failed: no compatible request format was accepted")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def parse_post_record(entry: object) -> dict[str, object] | None:
|
|
159
|
+
if not isinstance(entry, dict):
|
|
160
|
+
return None
|
|
161
|
+
item_hash = entry.get("item_hash") or entry.get("hash")
|
|
162
|
+
if not isinstance(item_hash, str) or not item_hash:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
sender = entry.get("address") or entry.get("sender")
|
|
166
|
+
item_content = entry.get("item_content")
|
|
167
|
+
if isinstance(item_content, str):
|
|
168
|
+
try:
|
|
169
|
+
item_content = json.loads(item_content)
|
|
170
|
+
except json.JSONDecodeError:
|
|
171
|
+
item_content = None
|
|
172
|
+
|
|
173
|
+
if not isinstance(item_content, dict):
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
content = item_content.get("content")
|
|
177
|
+
if not isinstance(content, dict):
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
"item_hash": item_hash,
|
|
182
|
+
"sender": sender,
|
|
183
|
+
"registration_id": content.get("registrationId"),
|
|
184
|
+
"peer_id": content.get("peerId"),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def load_peer_id() -> str | None:
|
|
189
|
+
describe = subprocess.run([DESCRIBE_SCRIPT], check=False, capture_output=True, text=True)
|
|
190
|
+
if describe.returncode != 0:
|
|
191
|
+
return None
|
|
192
|
+
try:
|
|
193
|
+
metadata = json.loads(describe.stdout.strip() or "{}")
|
|
194
|
+
except json.JSONDecodeError:
|
|
195
|
+
return None
|
|
196
|
+
peer_id = metadata.get("peer_id")
|
|
197
|
+
if isinstance(peer_id, str) and peer_id.strip():
|
|
198
|
+
return peer_id.strip()
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def fetch_current_hashes(
|
|
203
|
+
api_host: str,
|
|
204
|
+
channel: str,
|
|
205
|
+
ref: str,
|
|
206
|
+
post_type: str,
|
|
207
|
+
sender: str,
|
|
208
|
+
registration_id: str | None,
|
|
209
|
+
peer_id: str | None,
|
|
210
|
+
) -> list[str]:
|
|
211
|
+
found: list[str] = []
|
|
212
|
+
for page in range(1, MAX_PREVIOUS_PAGES + 1):
|
|
213
|
+
url = (
|
|
214
|
+
f"{api_host.rstrip('/')}/api/v0/posts.json?"
|
|
215
|
+
f"channels={urllib.parse.quote(channel)}&"
|
|
216
|
+
f"refs={urllib.parse.quote(ref)}&"
|
|
217
|
+
f"types={urllib.parse.quote(post_type)}&"
|
|
218
|
+
f"pagination={PAGINATION}&page={page}"
|
|
219
|
+
)
|
|
220
|
+
payload = get_json(url)
|
|
221
|
+
posts = payload.get("posts")
|
|
222
|
+
if not isinstance(posts, list):
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
for entry in posts:
|
|
226
|
+
parsed = parse_post_record(entry)
|
|
227
|
+
if parsed is None:
|
|
228
|
+
continue
|
|
229
|
+
if str(parsed["sender"]).lower() != sender.lower():
|
|
230
|
+
continue
|
|
231
|
+
if registration_id and parsed["registration_id"] == registration_id:
|
|
232
|
+
found.append(str(parsed["item_hash"]))
|
|
233
|
+
continue
|
|
234
|
+
if peer_id and parsed["peer_id"] == peer_id:
|
|
235
|
+
found.append(str(parsed["item_hash"]))
|
|
236
|
+
|
|
237
|
+
if len(posts) < PAGINATION:
|
|
238
|
+
break
|
|
239
|
+
|
|
240
|
+
return dedupe(found)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def broadcast_forget(
|
|
244
|
+
api_host: str,
|
|
245
|
+
sender: str,
|
|
246
|
+
private_key: str,
|
|
247
|
+
hashes: list[str],
|
|
248
|
+
channel: str,
|
|
249
|
+
) -> tuple[int, dict] | None:
|
|
250
|
+
if not hashes:
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
now_seconds = time.time()
|
|
254
|
+
item_content = json_dumps(
|
|
255
|
+
{
|
|
256
|
+
"address": sender,
|
|
257
|
+
"time": now_seconds,
|
|
258
|
+
"hashes": hashes,
|
|
259
|
+
"aggregates": [],
|
|
260
|
+
"reason": f"Deregister relay bootstrap records for {sender}",
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
unsigned = {
|
|
264
|
+
"sender": sender,
|
|
265
|
+
"chain": "ETH",
|
|
266
|
+
"type": "FORGET",
|
|
267
|
+
"item_hash": hashlib.sha256(item_content.encode("utf-8")).hexdigest(),
|
|
268
|
+
"item_type": "inline",
|
|
269
|
+
"item_content": item_content,
|
|
270
|
+
"time": now_seconds,
|
|
271
|
+
"channel": channel,
|
|
272
|
+
}
|
|
273
|
+
return broadcast_aleph_message(api_host, sign_aleph_message(unsigned, private_key))
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def main() -> None:
|
|
277
|
+
env_values = parse_env_file(ENV_FILE)
|
|
278
|
+
publisher_private_key = env_values.get("ALEPH_BOOTSTRAP_PUBLISHER_PRIVATE_KEY", "").strip()
|
|
279
|
+
registration_id = env_values.get("ALEPH_BOOTSTRAP_REGISTRATION_ID", "").strip() or None
|
|
280
|
+
if not publisher_private_key:
|
|
281
|
+
print(json_dumps({"status": "skipped", "reason": "missing publisher key"}))
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
publisher_address = address_from_private_key(publisher_private_key)
|
|
285
|
+
peer_id = load_peer_id()
|
|
286
|
+
channel = env_values.get("ALEPH_BOOTSTRAP_CHANNEL", DEFAULT_CHANNEL).strip() or DEFAULT_CHANNEL
|
|
287
|
+
ref = env_values.get("ALEPH_BOOTSTRAP_REF", DEFAULT_REF).strip() or DEFAULT_REF
|
|
288
|
+
post_type = env_values.get("ALEPH_BOOTSTRAP_POST_TYPE", DEFAULT_POST_TYPE).strip() or DEFAULT_POST_TYPE
|
|
289
|
+
api_host = env_values.get("ALEPH_BOOTSTRAP_API_HOST", DEFAULT_API_HOST).strip() or DEFAULT_API_HOST
|
|
290
|
+
|
|
291
|
+
hashes = fetch_current_hashes(
|
|
292
|
+
api_host,
|
|
293
|
+
channel,
|
|
294
|
+
ref,
|
|
295
|
+
post_type,
|
|
296
|
+
publisher_address,
|
|
297
|
+
registration_id,
|
|
298
|
+
peer_id,
|
|
299
|
+
)
|
|
300
|
+
if not hashes:
|
|
301
|
+
print(json_dumps({"status": "skipped", "reason": "no matching bootstrap records"}))
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
http_status, response = broadcast_forget(
|
|
305
|
+
api_host, publisher_address, publisher_private_key, hashes, channel
|
|
306
|
+
) or (0, {})
|
|
307
|
+
print(
|
|
308
|
+
json_dumps(
|
|
309
|
+
{
|
|
310
|
+
"status": "forgotten",
|
|
311
|
+
"httpStatus": http_status,
|
|
312
|
+
"sender": publisher_address,
|
|
313
|
+
"registrationId": registration_id,
|
|
314
|
+
"peerId": peer_id,
|
|
315
|
+
"forgottenHashes": hashes,
|
|
316
|
+
"response": response,
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if __name__ == "__main__":
|
|
323
|
+
try:
|
|
324
|
+
main()
|
|
325
|
+
except Exception as error: # pragma: no cover - runtime error path
|
|
326
|
+
print(json_dumps({"status": "error", "error": str(error)}), file=sys.stderr)
|
|
327
|
+
raise
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Best-effort Aleph bootstrap deregistration on shutdown for uc-go-peer
|
|
3
|
+
After=network-online.target
|
|
4
|
+
Wants=network-online.target
|
|
5
|
+
ConditionPathExists=/etc/default/uc-go-peer.ready
|
|
6
|
+
|
|
7
|
+
[Service]
|
|
8
|
+
Type=oneshot
|
|
9
|
+
RemainAfterExit=yes
|
|
10
|
+
Environment=ENV_FILE=/etc/default/uc-go-peer
|
|
11
|
+
Environment=DESCRIBE_SCRIPT=/usr/local/sbin/uc-go-peer-describe.py
|
|
12
|
+
ExecStart=/bin/true
|
|
13
|
+
ExecStop=/usr/bin/python3 /usr/local/sbin/uc-go-peer-bootstrap-deregister.py
|
|
14
|
+
StandardOutput=journal
|
|
15
|
+
StandardError=journal
|
|
16
|
+
TimeoutStopSec=25
|
|
17
|
+
|
|
18
|
+
[Install]
|
|
19
|
+
WantedBy=multi-user.target
|
|
@@ -117,7 +117,8 @@ def json_dumps(payload: object) -> str:
|
|
|
117
117
|
def sign_personal_message(private_key: str, payload: str) -> str:
|
|
118
118
|
message = encode_defunct(text=payload)
|
|
119
119
|
signed = Account.sign_message(message, private_key=private_key)
|
|
120
|
-
|
|
120
|
+
signature = signed.signature.hex()
|
|
121
|
+
return signature if signature.startswith("0x") else f"0x{signature}"
|
|
121
122
|
|
|
122
123
|
|
|
123
124
|
def address_from_private_key(private_key: str) -> str:
|
|
@@ -302,6 +303,30 @@ def sign_aleph_message(unsigned_message: dict[str, object], private_key: str) ->
|
|
|
302
303
|
return signed
|
|
303
304
|
|
|
304
305
|
|
|
306
|
+
def is_invalid_message_format(http_status: int, payload: dict) -> bool:
|
|
307
|
+
if http_status != 422:
|
|
308
|
+
return False
|
|
309
|
+
details = payload.get("details")
|
|
310
|
+
if isinstance(details, str) and "InvalidMessageFormat" in details:
|
|
311
|
+
return True
|
|
312
|
+
if isinstance(details, dict):
|
|
313
|
+
message = details.get("message")
|
|
314
|
+
if isinstance(message, str) and "InvalidMessageFormat" in message:
|
|
315
|
+
return True
|
|
316
|
+
return False
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def is_retryable_broadcast_failure(http_status: int, payload: dict) -> bool:
|
|
320
|
+
if http_status >= 500:
|
|
321
|
+
return True
|
|
322
|
+
publication_status = payload.get("publication_status")
|
|
323
|
+
if isinstance(publication_status, dict):
|
|
324
|
+
status = publication_status.get("status")
|
|
325
|
+
if isinstance(status, str) and status.strip().lower() == "error":
|
|
326
|
+
return True
|
|
327
|
+
return False
|
|
328
|
+
|
|
329
|
+
|
|
305
330
|
def broadcast_aleph_message(api_host: str, message: dict[str, object]) -> tuple[int, dict]:
|
|
306
331
|
url = urllib.parse.urljoin(api_host.rstrip("/") + "/", "api/v0/messages")
|
|
307
332
|
attempts = [
|
|
@@ -309,14 +334,17 @@ def broadcast_aleph_message(api_host: str, message: dict[str, object]) -> tuple[
|
|
|
309
334
|
{**message, "sync": True},
|
|
310
335
|
dict(message),
|
|
311
336
|
]
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
337
|
+
for index, attempt in enumerate(attempts):
|
|
338
|
+
http_status, payload = post_json(url, attempt)
|
|
339
|
+
if 200 <= http_status < 300:
|
|
340
|
+
return http_status, payload
|
|
341
|
+
can_retry = index < len(attempts) - 1 and (
|
|
342
|
+
is_invalid_message_format(http_status, payload)
|
|
343
|
+
or is_retryable_broadcast_failure(http_status, payload)
|
|
344
|
+
)
|
|
345
|
+
if not can_retry:
|
|
346
|
+
raise RuntimeError(f"Aleph broadcast failed: {http_status} {json_dumps(payload)}")
|
|
347
|
+
raise RuntimeError("Aleph broadcast failed: no compatible request format was accepted")
|
|
320
348
|
|
|
321
349
|
|
|
322
350
|
def parse_post_record(entry: object) -> dict[str, object] | None:
|
|
@@ -466,9 +494,6 @@ def main() -> None:
|
|
|
466
494
|
version,
|
|
467
495
|
now_ms,
|
|
468
496
|
)
|
|
469
|
-
if owner_authorization is None:
|
|
470
|
-
print(json_dumps({"status": "skipped", "reason": "missing owner authorization"}))
|
|
471
|
-
return
|
|
472
497
|
|
|
473
498
|
proof_payload = relay_proof_payload(
|
|
474
499
|
peer_id,
|
|
@@ -529,6 +554,7 @@ def main() -> None:
|
|
|
529
554
|
"publishedMultiaddrs": multiaddrs,
|
|
530
555
|
"publishedBrowserMultiaddrs": browser_multiaddrs,
|
|
531
556
|
"forgottenHashes": previous_hashes,
|
|
557
|
+
"ownerAuthorizationPresent": owner_authorization is not None,
|
|
532
558
|
"forgetResponse": forget_response[1] if forget_response else None,
|
|
533
559
|
"response": response,
|
|
534
560
|
}
|
|
@@ -11,6 +11,7 @@ SERVICE_NAME="${SERVICE_NAME:-uc-go-peer.service}"
|
|
|
11
11
|
AUTOTLS_REFRESH_SERVICE="${AUTOTLS_REFRESH_SERVICE:-uc-go-peer-autotls-refresh.service}"
|
|
12
12
|
BOOTSTRAP_REFRESH_TIMER="${BOOTSTRAP_REFRESH_TIMER:-uc-go-peer-bootstrap-refresh.timer}"
|
|
13
13
|
BOOTSTRAP_REFRESH_SERVICE="${BOOTSTRAP_REFRESH_SERVICE:-uc-go-peer-bootstrap-refresh.service}"
|
|
14
|
+
BOOTSTRAP_DEREGISTER_SERVICE="${BOOTSTRAP_DEREGISTER_SERVICE:-uc-go-peer-bootstrap-deregister.service}"
|
|
14
15
|
CADDY_SERVICE="${CADDY_SERVICE:-caddy.service}"
|
|
15
16
|
CADDYFILE="${CADDYFILE:-/etc/caddy/Caddyfile}"
|
|
16
17
|
BOOTSTRAP_SERVICE="${BOOTSTRAP_SERVICE:-uc-go-peer-bootstrap.service}"
|
|
@@ -97,6 +98,27 @@ current_identity_path() {
|
|
|
97
98
|
printf '/var/lib/uc-go-peer/identity.key\n'
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
current_bootstrap_publisher_private_key() {
|
|
102
|
+
local existing
|
|
103
|
+
existing="$(grep -E '^[#[:space:]]*ALEPH_BOOTSTRAP_PUBLISHER_PRIVATE_KEY=' "${ENV_FILE}" 2>/dev/null | tail -n1 | cut -d= -f2- || true)"
|
|
104
|
+
if [ -n "${existing}" ]; then
|
|
105
|
+
printf '%s\n' "${existing}"
|
|
106
|
+
return
|
|
107
|
+
fi
|
|
108
|
+
printf '\n'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
generate_bootstrap_publisher_private_key() {
|
|
112
|
+
while true; do
|
|
113
|
+
local candidate
|
|
114
|
+
candidate="$(openssl rand -hex 32)"
|
|
115
|
+
if [ -n "${candidate}" ] && [ "${candidate}" != "0000000000000000000000000000000000000000000000000000000000000000" ]; then
|
|
116
|
+
printf '0x%s\n' "${candidate}"
|
|
117
|
+
return
|
|
118
|
+
fi
|
|
119
|
+
done
|
|
120
|
+
}
|
|
121
|
+
|
|
100
122
|
while [ "$#" -gt 0 ]; do
|
|
101
123
|
case "$1" in
|
|
102
124
|
--public-ipv4)
|
|
@@ -179,6 +201,13 @@ fi
|
|
|
179
201
|
touch "${ENV_FILE}"
|
|
180
202
|
rm -f "${AUTOTLS_READY_FILE}" "${AUTOTLS_ZONE_FILE}" "${AUTOTLS_HOSTS_FILE}" "${AUTOTLS_CADDY_READY_FILE}"
|
|
181
203
|
|
|
204
|
+
if [ -z "${BOOTSTRAP_PUBLISHER_PRIVATE_KEY}" ]; then
|
|
205
|
+
BOOTSTRAP_PUBLISHER_PRIVATE_KEY="$(current_bootstrap_publisher_private_key)"
|
|
206
|
+
fi
|
|
207
|
+
if [ -z "${BOOTSTRAP_PUBLISHER_PRIVATE_KEY}" ]; then
|
|
208
|
+
BOOTSTRAP_PUBLISHER_PRIVATE_KEY="$(generate_bootstrap_publisher_private_key)"
|
|
209
|
+
fi
|
|
210
|
+
|
|
182
211
|
announce=(
|
|
183
212
|
"/ip4/${PUBLIC_IPV4}/tcp/${TCP_PORT}"
|
|
184
213
|
)
|
|
@@ -234,6 +263,7 @@ if [ -n "${UDP_PORT}" ]; then
|
|
|
234
263
|
fi
|
|
235
264
|
if [ -n "${BOOTSTRAP_PUBLISHER_PRIVATE_KEY}" ]; then
|
|
236
265
|
write_env_var "ALEPH_BOOTSTRAP_PUBLISHER_PRIVATE_KEY" "${BOOTSTRAP_PUBLISHER_PRIVATE_KEY}"
|
|
266
|
+
write_env_var "GO_PEER_IDENTITY_EVM_PRIVATE_KEY" "${BOOTSTRAP_PUBLISHER_PRIVATE_KEY}"
|
|
237
267
|
fi
|
|
238
268
|
if [ -n "${BOOTSTRAP_PUBLISHER_LIBP2P_IDENTITY_B64}" ]; then
|
|
239
269
|
identity_path="$(current_identity_path)"
|
|
@@ -261,6 +291,8 @@ if [ "${START_SERVICE}" -eq 1 ]; then
|
|
|
261
291
|
systemctl restart "${SERVICE_NAME}"
|
|
262
292
|
systemctl enable "${AUTOTLS_REFRESH_SERVICE}"
|
|
263
293
|
systemctl enable "${BOOTSTRAP_REFRESH_TIMER}"
|
|
294
|
+
systemctl enable "${BOOTSTRAP_DEREGISTER_SERVICE}"
|
|
295
|
+
systemctl restart "${BOOTSTRAP_DEREGISTER_SERVICE}"
|
|
264
296
|
systemctl start "${BOOTSTRAP_REFRESH_TIMER}"
|
|
265
297
|
systemctl restart --no-block "${BOOTSTRAP_REFRESH_SERVICE}" || true
|
|
266
298
|
if [ -n "${PROXY_HOSTNAME}" ]; then
|