ovtools 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +121 -2
- package/assets/common/bond-inspect.sh +368 -0
- package/assets/common/config.sh +263 -0
- package/assets/common/extract-data.sh +197 -0
- package/assets/common/harden-host.sh +37 -0
- package/assets/common/install-deps.sh +93 -0
- package/assets/common/node-enroll.sh +65 -0
- package/assets/common/node-setup.sh +150 -0
- package/assets/common/node-start.sh +90 -0
- package/assets/common/node-stop.sh +18 -0
- package/assets/common/node-upgrade.sh +45 -0
- package/assets/common/pre-run.sh +497 -0
- package/assets/common/promote-validator.sh +104 -0
- package/assets/deploy/10-prepare-host.sh +98 -0
- package/assets/deploy/20-install-toolchain.sh +93 -0
- package/assets/deploy/30-setup-node.sh +82 -0
- package/assets/deploy/40-start-node.sh +75 -0
- package/assets/deploy/50-verify.sh +259 -0
- package/assets/deploy/60-enroll.sh +304 -0
- package/assets/deploy/70-promote.sh +152 -0
- package/assets/deploy/lib.sh +398 -0
- package/assets/manage/10-bond-increase.sh +149 -0
- package/assets/manage/10-diagnose-node.sh +169 -0
- package/assets/manage/20-exit-validator.sh +259 -0
- package/assets/manage/25-recover-node.sh +291 -0
- package/assets/manage/30-status.sh +64 -0
- package/assets/manage/40-exit-withdraw.sh +133 -0
- package/assets/manage/bond_inc.sh +318 -0
- package/assets/manage/dashboard_monitor/app.js +1544 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/base.css +113 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/content.css +133 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/controls.css +229 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/data.css +374 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/icons.css +35 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/motion.css +57 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/shell.css +51 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/sidebar.css +242 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/components/topbar.css +138 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/fonts.css +30 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/synteq-ui-v2.css +25 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-dark.css +38 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-light.css +36 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/css/tokens.css +61 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
- package/assets/manage/dashboard_monitor/assets/v2.2/js/synteq-ui-v2.js +869 -0
- package/assets/manage/dashboard_monitor/deploy-dashboard.sh +230 -0
- package/assets/manage/dashboard_monitor/favicon.svg +13 -0
- package/assets/manage/dashboard_monitor/index.html +1197 -0
- package/assets/manage/dashboard_monitor/nginx-octra.conf +76 -0
- package/assets/manage/dashboard_monitor/octra-dashboard.service +29 -0
- package/assets/manage/dashboard_monitor/octra-node-prune.cron +12 -0
- package/assets/manage/dashboard_monitor/octra-node-prune.service +16 -0
- package/assets/manage/dashboard_monitor/octra-node-prune.timer +12 -0
- package/assets/manage/dashboard_monitor/prune-node-junk.sh +329 -0
- package/assets/manage/dashboard_monitor/server.py +1887 -0
- package/assets/manage/dashboard_monitor/ssl-certbot.sh +90 -0
- package/assets/manage/dashboard_monitor/styles.css +327 -0
- package/assets/manage/lib.sh +347 -0
- package/assets/manifest.json +143 -0
- package/assets/migrate/00-backup-identity.sh +67 -0
- package/assets/migrate/01-prepare-target.sh +145 -0
- package/assets/migrate/02-authorize-pull.sh +43 -0
- package/assets/migrate/03-pull-state.sh +102 -0
- package/assets/migrate/04-stop-source.sh +85 -0
- package/assets/migrate/05-finalize-target.sh +140 -0
- package/assets/migrate/06-start-target.sh +57 -0
- package/assets/migrate/07-verify.sh +237 -0
- package/assets/migrate/08-rollback.sh +54 -0
- package/assets/migrate/lib.sh +149 -0
- package/assets/upgrade/10-preflight.sh +120 -0
- package/assets/upgrade/20-upgrade.sh +142 -0
- package/assets/upgrade/30-manual-upgrade.sh +129 -0
- package/assets/upgrade/40-storage-prune.sh +65 -0
- package/assets/upgrade/50-status.sh +58 -0
- package/assets/upgrade/lib.sh +232 -0
- package/bin/ovtools.js +32 -0
- package/dist/api/checks.d.ts +6 -0
- package/dist/api/checks.d.ts.map +1 -0
- package/dist/api/checks.js +173 -0
- package/dist/api/checks.js.map +1 -0
- package/dist/api/hostkeys.d.ts +10 -0
- package/dist/api/hostkeys.d.ts.map +1 -0
- package/dist/api/hostkeys.js +58 -0
- package/dist/api/hostkeys.js.map +1 -0
- package/dist/api/jobs.d.ts +33 -0
- package/dist/api/jobs.d.ts.map +1 -0
- package/dist/api/jobs.js +486 -0
- package/dist/api/jobs.js.map +1 -0
- package/dist/api/nodes.d.ts +16 -0
- package/dist/api/nodes.d.ts.map +1 -0
- package/dist/api/nodes.js +126 -0
- package/dist/api/nodes.js.map +1 -0
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +443 -0
- package/dist/api/server.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +301 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants.d.ts +76 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +150 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/base.d.ts +52 -0
- package/dist/orchestrator/base.d.ts.map +1 -0
- package/dist/orchestrator/base.js +219 -0
- package/dist/orchestrator/base.js.map +1 -0
- package/dist/orchestrator/deploy.d.ts +33 -0
- package/dist/orchestrator/deploy.d.ts.map +1 -0
- package/dist/orchestrator/deploy.js +313 -0
- package/dist/orchestrator/deploy.js.map +1 -0
- package/dist/orchestrator/manage.d.ts +28 -0
- package/dist/orchestrator/manage.d.ts.map +1 -0
- package/dist/orchestrator/manage.js +267 -0
- package/dist/orchestrator/manage.js.map +1 -0
- package/dist/orchestrator/migrate.d.ts +31 -0
- package/dist/orchestrator/migrate.d.ts.map +1 -0
- package/dist/orchestrator/migrate.js +442 -0
- package/dist/orchestrator/migrate.js.map +1 -0
- package/dist/ssh/client.d.ts +39 -0
- package/dist/ssh/client.d.ts.map +1 -0
- package/dist/ssh/client.js +418 -0
- package/dist/ssh/client.js.map +1 -0
- package/dist/test/core.test.d.ts +2 -0
- package/dist/test/core.test.d.ts.map +1 -0
- package/dist/test/core.test.js +188 -0
- package/dist/test/core.test.js.map +1 -0
- package/dist/types.d.ts +267 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/assets.d.ts +28 -0
- package/dist/utils/assets.d.ts.map +1 -0
- package/dist/utils/assets.js +583 -0
- package/dist/utils/assets.js.map +1 -0
- package/dist/utils/browser.d.ts +2 -0
- package/dist/utils/browser.d.ts.map +1 -0
- package/dist/utils/browser.js +23 -0
- package/dist/utils/browser.js.map +1 -0
- package/dist/utils/dashboard.d.ts +16 -0
- package/dist/utils/dashboard.d.ts.map +1 -0
- package/dist/utils/dashboard.js +177 -0
- package/dist/utils/dashboard.js.map +1 -0
- package/dist/utils/helpers.d.ts +29 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +204 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/scripts.d.ts +60 -0
- package/dist/utils/scripts.d.ts.map +1 -0
- package/dist/utils/scripts.js +174 -0
- package/dist/utils/scripts.js.map +1 -0
- package/dist/utils/state.d.ts +12 -0
- package/dist/utils/state.d.ts.map +1 -0
- package/dist/utils/state.js +44 -0
- package/dist/utils/state.js.map +1 -0
- package/dist/utils/tar.d.ts +11 -0
- package/dist/utils/tar.d.ts.map +1 -0
- package/dist/utils/tar.js +93 -0
- package/dist/utils/tar.js.map +1 -0
- package/dist/utils/update.d.ts +9 -0
- package/dist/utils/update.d.ts.map +1 -0
- package/dist/utils/update.js +134 -0
- package/dist/utils/update.js.map +1 -0
- package/dist/utils/websocket.d.ts +10 -0
- package/dist/utils/websocket.d.ts.map +1 -0
- package/dist/utils/websocket.js +203 -0
- package/dist/utils/websocket.js.map +1 -0
- package/package.json +55 -4
- package/public/app.js +2736 -0
- package/public/assets/v2.2/css/base.css +113 -0
- package/public/assets/v2.2/css/components/content.css +133 -0
- package/public/assets/v2.2/css/components/controls.css +229 -0
- package/public/assets/v2.2/css/components/data.css +373 -0
- package/public/assets/v2.2/css/components/icons.css +35 -0
- package/public/assets/v2.2/css/components/motion.css +57 -0
- package/public/assets/v2.2/css/components/shell.css +51 -0
- package/public/assets/v2.2/css/components/sidebar.css +242 -0
- package/public/assets/v2.2/css/components/topbar.css +138 -0
- package/public/assets/v2.2/css/fonts.css +30 -0
- package/public/assets/v2.2/css/synteq-ui-v2.css +25 -0
- package/public/assets/v2.2/css/theme-dark.css +38 -0
- package/public/assets/v2.2/css/theme-light.css +36 -0
- package/public/assets/v2.2/css/tokens.css +61 -0
- package/public/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
- package/public/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
- package/public/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
- package/public/assets/v2.2/js/synteq-ui-v2.js +869 -0
- package/public/favicon.svg +13 -0
- package/public/index.html +1083 -0
- package/public/styles.css +580 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
ROOT=$(pwd -P)
|
|
5
|
+
exec python3 -B - "$ROOT" "$@" <<'PY'
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
ROOT = Path(sys.argv.pop(1)).resolve()
|
|
10
|
+
modules = ROOT / "controls/lib"
|
|
11
|
+
for name in ("validator_common", "validator_rpc", "validator_enroll", "validator_exit"):
|
|
12
|
+
if not (modules / (name + ".py")).is_file():
|
|
13
|
+
print("event = bond_inc status = refused reason = run_from_node_directory", file = sys.stderr)
|
|
14
|
+
raise SystemExit(1)
|
|
15
|
+
sys.path.insert(0, str(modules))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# sp import if (not sure btw, will check later)
|
|
19
|
+
import base64
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
import math
|
|
23
|
+
import re
|
|
24
|
+
|
|
25
|
+
from nacl.signing import VerifyKey
|
|
26
|
+
|
|
27
|
+
from validator_common import ValidatorError
|
|
28
|
+
from validator_exit import BOND_ESCROW, valid_hash
|
|
29
|
+
|
|
30
|
+
FEE = 1000
|
|
31
|
+
FIELDS = ("from", "to_", "amount", "nonce", "ou", "timestamp", "op_type")
|
|
32
|
+
|
|
33
|
+
def amount_raw(text):
|
|
34
|
+
if not isinstance(text, str) or len(text) > 40 or not re.fullmatch(
|
|
35
|
+
r"[0-9]+(?:\.[0-9]{1,6})?", text
|
|
36
|
+
):
|
|
37
|
+
raise ValidatorError("amount must be OCT with at most six decimal places")
|
|
38
|
+
whole, _, fraction = text.partition(".")
|
|
39
|
+
value = int(whole) * 1000000 + int(fraction.ljust(6, "0"))
|
|
40
|
+
if value < 1000000:
|
|
41
|
+
raise ValidatorError("bond increase must be at least 1 OCT")
|
|
42
|
+
return value
|
|
43
|
+
|
|
44
|
+
def wire(fields, stamp):
|
|
45
|
+
encoded = json.dumps(fields, separators = (",", ":"), ensure_ascii = True)
|
|
46
|
+
return re.sub(r'"timestamp":[0-9.eE+\-]+', '"timestamp":' + stamp,
|
|
47
|
+
encoded, count = 1).encode()
|
|
48
|
+
|
|
49
|
+
def payment(record, wallet, chain, amount):
|
|
50
|
+
tx = record.get("tx")
|
|
51
|
+
stamp = record.get("stamp")
|
|
52
|
+
if not isinstance(tx, dict) or not isinstance(stamp, str) or not re.fullmatch(
|
|
53
|
+
r"[0-9]+(?:\.[0-9]+)?(?:[eE][+\-]?[0-9]+)?", stamp
|
|
54
|
+
):
|
|
55
|
+
raise ValidatorError("saved bond payment is incomplete")
|
|
56
|
+
expected = {"from": wallet["address"], "to_": BOND_ESCROW,
|
|
57
|
+
"public_key": wallet["pub"], "op_type": "validator_bond",
|
|
58
|
+
"amount": str(amount), "ou": str(FEE)}
|
|
59
|
+
|
|
60
|
+
if any(tx.get(key) != value for key, value in expected.items()) or (
|
|
61
|
+
tx.get("encrypted_data") is not None
|
|
62
|
+
or set(tx) - set(FIELDS) - {"public_key", "signature", "message", "encrypted_data"}
|
|
63
|
+
or type(tx.get("nonce")) is not int or tx["nonce"] < 1
|
|
64
|
+
or type(tx.get("timestamp")) not in (int, float)
|
|
65
|
+
or not math.isfinite(tx["timestamp"]) or tx["timestamp"] <= 0
|
|
66
|
+
or float(stamp) != tx["timestamp"] or not valid_hash(record.get("tx_hash"))
|
|
67
|
+
):
|
|
68
|
+
raise ValidatorError("saved bond payment differs")
|
|
69
|
+
try:
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
public = base64.b64decode(wallet["pub"], validate = True)
|
|
75
|
+
key = VerifyKey(public)
|
|
76
|
+
signed = {name: tx[name] for name in FIELDS}
|
|
77
|
+
signed["message"] = tx["message"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
key.verify(wire(signed, stamp), base64.b64decode(tx["signature"], validate = True))
|
|
81
|
+
proof = json.loads(tx["message"])
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# as a temporary solution - ok (will remember to include it in git after the release but it's better to do ring approval , so pls check 12 id before!!!!
|
|
86
|
+
if set(proof) != {"consensus_pubkey", "proof"} or proof["consensus_pubkey"] != wallet["pub"]:
|
|
87
|
+
raise ValueError("bond key differs")
|
|
88
|
+
parts = [b"octra:validator_bond:v1", chain.encode(), wallet["address"].encode(),
|
|
89
|
+
str(amount).encode(), str(tx["nonce"]).encode(), public]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# !!!!!!! here also
|
|
93
|
+
message = b"".join(str(len(part)).encode() + b":" + part for part in parts)
|
|
94
|
+
key.verify(message, base64.b64decode(proof["proof"], validate = True))
|
|
95
|
+
except Exception as error:
|
|
96
|
+
raise ValidatorError("bond payment signature or network proof is invalid") from error
|
|
97
|
+
fields = {name: tx[name] for name in FIELDS[:-1]}
|
|
98
|
+
fields.update({"signature": tx["signature"], "op_type": tx["op_type"],
|
|
99
|
+
"public_key": tx["public_key"], "message": tx["message"], "encrypted_data": None})
|
|
100
|
+
if hashlib.sha256(wire(fields, stamp)).hexdigest() != record["tx_hash"]:
|
|
101
|
+
raise ValidatorError("bond transaction hash differs")
|
|
102
|
+
return tx
|
|
103
|
+
|
|
104
|
+
def receipt(value, record, head):
|
|
105
|
+
tx = record["tx"]
|
|
106
|
+
fields = {"tx_hash": record["tx_hash"], "from": tx["from"], "to": tx["to_"],
|
|
107
|
+
"amount_raw": tx["amount"], "nonce": tx["nonce"], "op_type": tx["op_type"]}
|
|
108
|
+
if not isinstance(value, dict) or any(
|
|
109
|
+
type(value.get(key)) is not type(expected) or value[key] != expected
|
|
110
|
+
for key, expected in fields.items()
|
|
111
|
+
):
|
|
112
|
+
raise ValidatorError("bond transaction receipt differs")
|
|
113
|
+
status = value.get("status")
|
|
114
|
+
if status == "confirmed" and (
|
|
115
|
+
type(value.get("epoch")) is not int or not record["head"] <= value["epoch"] <= head
|
|
116
|
+
):
|
|
117
|
+
raise ValidatorError("bond confirmation epoch differs")
|
|
118
|
+
if status not in {"confirmed", "pending", "staging", "rejected", "dropped"}:
|
|
119
|
+
raise ValidatorError("bond transaction status is invalid")
|
|
120
|
+
return status
|
|
121
|
+
|
|
122
|
+
import argparse
|
|
123
|
+
import json
|
|
124
|
+
import os
|
|
125
|
+
from pathlib import Path
|
|
126
|
+
import re
|
|
127
|
+
import stat
|
|
128
|
+
import subprocess
|
|
129
|
+
import sys
|
|
130
|
+
import time
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# !!!!!! checked
|
|
134
|
+
from validator_common import ValidatorError, load_wallet, parse_env, private_mode, sha256_file, write_private_json
|
|
135
|
+
from validator_enroll import EnrollmentState, committed_enrollment, local_rpc
|
|
136
|
+
from validator_exit import command_lock, control_view, directory, read_outbox, require_prepare, sync_directory
|
|
137
|
+
from validator_rpc import call, transaction
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ONLY FOR DEVNET!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
142
|
+
CHAIN = "octra-devnet-9871-cluster"
|
|
143
|
+
# !!!!!!!!!!!!!!!!!
|
|
144
|
+
|
|
145
|
+
def number(value, name):
|
|
146
|
+
if type(value) is int and value >= 0:
|
|
147
|
+
return value
|
|
148
|
+
if isinstance(value, str) and value.isascii() and value.isdecimal():
|
|
149
|
+
return int(value)
|
|
150
|
+
raise ValidatorError("invalid " + name)
|
|
151
|
+
|
|
152
|
+
def view(values, wallet):
|
|
153
|
+
if values.get("OCTRA_CHAIN_ID") != CHAIN:
|
|
154
|
+
raise ValidatorError("this bond tool requires devnet")
|
|
155
|
+
url = local_rpc(values)
|
|
156
|
+
runtime = call(url, "octra_runtimeVersion", [])
|
|
157
|
+
if not isinstance(runtime, dict) or runtime.get("chain_id") != CHAIN or (
|
|
158
|
+
runtime.get("validator") != wallet["address"]
|
|
159
|
+
):
|
|
160
|
+
raise ValidatorError("local node network or validator differs")
|
|
161
|
+
raw = call(url, "octra_validatorEnrollment", [])
|
|
162
|
+
entry = committed_enrollment(values, wallet, raw)
|
|
163
|
+
if entry.state is not EnrollmentState.READY or entry.membership is None or (
|
|
164
|
+
not entry.membership.active or entry.withdraw_epoch is not None
|
|
165
|
+
):
|
|
166
|
+
raise ValidatorError("active ready validator required; no join or exit will be sent")
|
|
167
|
+
control = control_view(raw, values, wallet, entry.bonded_epoch)
|
|
168
|
+
if control["exit_requested"]:
|
|
169
|
+
raise ValidatorError("validator exit is requested; nothing signed")
|
|
170
|
+
balance = call(url, "octra_balance", [wallet["address"]])
|
|
171
|
+
if not isinstance(balance, dict) or balance.get("address") != wallet["address"]:
|
|
172
|
+
raise ValidatorError("local balance address differs")
|
|
173
|
+
return {"head": entry.head_epoch, "bond": entry.bond, "bonded": entry.bonded_epoch,
|
|
174
|
+
"ready": entry.ready_epoch, "nonce": number(balance.get("nonce"), "account nonce"),
|
|
175
|
+
"pending": number(balance.get("pending_nonce"), "pending nonce"),
|
|
176
|
+
"balance": number(balance.get("balance_raw"), "account balance")}
|
|
177
|
+
|
|
178
|
+
def available(current, amount):
|
|
179
|
+
if current["pending"] != current["nonce"]:
|
|
180
|
+
raise ValidatorError("account has pending transactions; nothing signed or submitted")
|
|
181
|
+
if current["balance"] < amount + FEE:
|
|
182
|
+
raise ValidatorError("insufficient public balance for increase plus 0.001 OCT fee")
|
|
183
|
+
|
|
184
|
+
def save(path, value):
|
|
185
|
+
write_private_json(path, value)
|
|
186
|
+
sync_directory(path.parent)
|
|
187
|
+
|
|
188
|
+
def prepare(values, wallet, wallet_path, amount, current):
|
|
189
|
+
binary = Path(values["OCTRA_OPERATOR_CONTROL_BINARY"])
|
|
190
|
+
if sha256_file(binary) != values["OCTRA_OPERATOR_CONTROL_BINARY_HASH"]:
|
|
191
|
+
raise ValidatorError("validator control binary hash mismatch")
|
|
192
|
+
require_prepare(binary)
|
|
193
|
+
result = subprocess.run([str(binary), "--wallet", str(wallet_path),
|
|
194
|
+
"--rpc", local_rpc(values), "--chain-id", CHAIN, "--op", "validator_bond",
|
|
195
|
+
"--amount", str(amount), "--nonce", str(current["nonce"] + 1),
|
|
196
|
+
"--ou", str(FEE), "--prepare"], capture_output = True, text = True, timeout = 30)
|
|
197
|
+
if result.returncode != 0:
|
|
198
|
+
raise ValidatorError("bond preparation failed; nothing submitted")
|
|
199
|
+
try:
|
|
200
|
+
prepared = json.loads(result.stdout)
|
|
201
|
+
stamps = re.findall(r'"timestamp"\s*:\s*([0-9.eE+\-]+)', result.stdout)
|
|
202
|
+
if len(stamps) != 1:
|
|
203
|
+
raise ValueError("timestamp missing")
|
|
204
|
+
record = {"chain": CHAIN, "address": wallet["address"], "pubkey": wallet["pub"],
|
|
205
|
+
"amount": amount, "before": current["bond"], "head": current["head"],
|
|
206
|
+
"bonded": current["bonded"], "ready": current["ready"], "stage": "prepared",
|
|
207
|
+
"tx": prepared["tx"], "tx_hash": prepared["tx_hash"], "stamp": stamps[0]}
|
|
208
|
+
except (ValueError, KeyError, TypeError) as error:
|
|
209
|
+
raise ValidatorError("invalid prepared bond transaction; nothing submitted") from error
|
|
210
|
+
tx = payment(record, wallet, CHAIN, amount)
|
|
211
|
+
if tx["nonce"] != current["nonce"] + 1 or abs(time.time() - tx["timestamp"]) > 60:
|
|
212
|
+
raise ValidatorError("prepared bond nonce or timestamp differs; nothing submitted")
|
|
213
|
+
return record
|
|
214
|
+
|
|
215
|
+
def identity(record, wallet, current, amount):
|
|
216
|
+
expected = {"chain": CHAIN, "address": wallet["address"], "pubkey": wallet["pub"],
|
|
217
|
+
"amount": amount, "bonded": current["bonded"], "ready": current["ready"]}
|
|
218
|
+
if any(type(record.get(key)) is not type(value) or record[key] != value
|
|
219
|
+
for key, value in expected.items()) or (
|
|
220
|
+
type(record.get("before")) is not int or record["before"] < 1
|
|
221
|
+
or type(record.get("head")) is not int or not 0 <= record["head"] <= current["head"]
|
|
222
|
+
or record.get("stage") not in {"prepared", "sent", "accepted"}
|
|
223
|
+
):
|
|
224
|
+
raise ValidatorError("saved increase differs; inspect before requesting another increase")
|
|
225
|
+
return payment(record, wallet, CHAIN, amount)
|
|
226
|
+
|
|
227
|
+
def increase(values, wallet, wallet_path, amount, *, new = False, retry = False):
|
|
228
|
+
current = view(values, wallet)
|
|
229
|
+
root = directory(values)
|
|
230
|
+
path = root / "bond-increase.json"
|
|
231
|
+
record = read_outbox(path)
|
|
232
|
+
if record is None and any(root.glob("bond-increase-*.json")):
|
|
233
|
+
raise ValidatorError("saved increase missing; restore control records")
|
|
234
|
+
if record is not None:
|
|
235
|
+
identity(record, wallet, current, record.get("amount") if new else amount)
|
|
236
|
+
observed = transaction(local_rpc(values), record["tx_hash"])
|
|
237
|
+
state = receipt(observed, record, current["head"]) if observed is not None else None
|
|
238
|
+
if state == "confirmed":
|
|
239
|
+
if current["bond"] < record["before"] + record["amount"]:
|
|
240
|
+
raise ValidatorError("confirmed increase is absent from current registry")
|
|
241
|
+
if not new:
|
|
242
|
+
return record["tx_hash"], "confirmed"
|
|
243
|
+
elif new:
|
|
244
|
+
raise ValidatorError("previous increase is not confirmed; nothing new signed")
|
|
245
|
+
elif state in {"pending", "staging"}:
|
|
246
|
+
return record["tx_hash"], state
|
|
247
|
+
elif state in {"rejected", "dropped"}:
|
|
248
|
+
raise ValidatorError("saved increase refused; inspect tx = " + record["tx_hash"])
|
|
249
|
+
elif record["stage"] != "prepared" and not retry:
|
|
250
|
+
raise ValidatorError("submission unresolved; inspect saved tx = " + record["tx_hash"]
|
|
251
|
+
+ "; --retry submits only this same transaction")
|
|
252
|
+
elif retry or new:
|
|
253
|
+
raise ValidatorError("no saved increase for --retry or --new")
|
|
254
|
+
available(current, amount)
|
|
255
|
+
if record is None or new:
|
|
256
|
+
prepared = prepare(values, wallet, wallet_path, amount, current)
|
|
257
|
+
if record is not None:
|
|
258
|
+
archive = root / ("bond-increase-" + record["tx_hash"] + ".json")
|
|
259
|
+
prior = read_outbox(archive)
|
|
260
|
+
if prior is not None and prior != record:
|
|
261
|
+
raise ValidatorError("saved increase archive differs")
|
|
262
|
+
save(archive, record)
|
|
263
|
+
save(path, prepared)
|
|
264
|
+
record = prepared
|
|
265
|
+
tx = identity(record, wallet, current, amount)
|
|
266
|
+
fresh = view(values, wallet)
|
|
267
|
+
available(fresh, amount)
|
|
268
|
+
if any(fresh[key] != current[key] for key in ("bond", "bonded", "ready", "nonce")) or (
|
|
269
|
+
tx["nonce"] != fresh["nonce"] + 1 or fresh["bond"] != record["before"]
|
|
270
|
+
or not 0 <= time.time() - tx["timestamp"] <= 300
|
|
271
|
+
):
|
|
272
|
+
raise ValidatorError("account, registry or transaction time changed; inspect tx = " + record["tx_hash"])
|
|
273
|
+
record = {**record, "stage": "sent"}
|
|
274
|
+
save(path, record)
|
|
275
|
+
try:
|
|
276
|
+
reply = call(local_rpc(values), "octra_submit", [tx], timeout = 30)
|
|
277
|
+
except ValidatorError as error:
|
|
278
|
+
raise ValidatorError("submission unresolved; signed payment retained; tx = " + record["tx_hash"]) from error
|
|
279
|
+
if not isinstance(reply, dict) or reply.get("tx_hash") != record["tx_hash"] or (
|
|
280
|
+
reply.get("status") not in {"accepted", "pending"}
|
|
281
|
+
):
|
|
282
|
+
raise ValidatorError("submission not acknowledged; inspect tx = " + record["tx_hash"])
|
|
283
|
+
save(path, {**record, "stage": "accepted"})
|
|
284
|
+
return record["tx_hash"], "accepted"
|
|
285
|
+
|
|
286
|
+
def main():
|
|
287
|
+
parser = argparse.ArgumentParser(prog = "bond_inc.sh",
|
|
288
|
+
description = "Add OCT to an active devnet validator bond")
|
|
289
|
+
parser.add_argument("-a", "--amount", required = True, type = amount_raw,
|
|
290
|
+
help = "Additional OCT, at least 1, at most six decimal places")
|
|
291
|
+
parser.add_argument("-w", "--wallet", required = True, type = Path)
|
|
292
|
+
parser.add_argument("--config", type = Path, default = Path(os.getenv(
|
|
293
|
+
"OCTRA_OPERATOR_CONFIG", str(ROOT / ".keys/validator/node.env"))))
|
|
294
|
+
group = parser.add_mutually_exclusive_group()
|
|
295
|
+
group.add_argument("--new", action = "store_true", help = "Start another increase after confirmed prior payment")
|
|
296
|
+
group.add_argument("--retry", action = "store_true", help = "Resubmit the exact saved payment after an unknown outcome")
|
|
297
|
+
args = parser.parse_args()
|
|
298
|
+
private_mode(args.config)
|
|
299
|
+
values = parse_env(args.config)
|
|
300
|
+
info = args.wallet.lstat()
|
|
301
|
+
if not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid():
|
|
302
|
+
raise ValidatorError("wallet must be a regular file owned by the node user")
|
|
303
|
+
wallet = load_wallet(args.wallet)
|
|
304
|
+
local = load_wallet(Path(values["OCTRA_DATA_DIR"]) / "wallet.json")
|
|
305
|
+
if any(wallet[key] != local[key] for key in ("address", "pub")):
|
|
306
|
+
raise ValidatorError("wallet does not belong to the configured validator")
|
|
307
|
+
with command_lock(values):
|
|
308
|
+
tx_hash, status = increase(values, wallet, args.wallet, args.amount, new = args.new, retry = args.retry)
|
|
309
|
+
print(f"event = bond_inc status = {status} amount_raw = {args.amount}", file = sys.stderr)
|
|
310
|
+
print("tx = " + tx_hash)
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
try:
|
|
314
|
+
main()
|
|
315
|
+
except (ValidatorError, OSError, KeyError, ValueError, subprocess.SubprocessError) as error:
|
|
316
|
+
print("event = bond_inc status = refused reason = " + json.dumps(str(error)), file = sys.stderr)
|
|
317
|
+
raise SystemExit(1)
|
|
318
|
+
PY
|