Skip to content

Commit 6116d41

Browse files
authored
Merge pull request #472 from ton-blockchain/dev
Dev
2 parents 1b88179 + 6e2dab7 commit 6116d41

File tree

19 files changed

+503
-53
lines changed

19 files changed

+503
-53
lines changed

modules/alert_bot.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ def init_alerts():
7676
),
7777
"zero_block_created": Alert(
7878
"critical",
79-
f"Validator has not created any blocks in the {int(VALIDATION_PERIOD // 6 // 3600)} hours",
79+
"Validator has not created any blocks in the last few hours",
8080
"Validator has not created any blocks in the last <b>{hours} hours</b>.",
81-
VALIDATION_PERIOD // 6
81+
int(VALIDATION_PERIOD / 2.3)
8282
),
8383
"validator_slashed": Alert(
8484
"high",
@@ -355,7 +355,7 @@ def check_zero_blocks_created(self):
355355
if not self.ton.using_validator():
356356
return
357357
ts = get_timestamp()
358-
period = VALIDATION_PERIOD // 6 # 3h for mainnet, 40m for testnet
358+
period = int(VALIDATION_PERIOD / 2.3) # ~ 8h for mainnet, 100m for testnet
359359
start, end = ts - period, ts - 60
360360
config34 = self.ton.GetConfig34()
361361
if start < config34.startWorkTime: # round started recently
@@ -364,7 +364,7 @@ def check_zero_blocks_created(self):
364364
validator = self.validator_module.find_myself(validators)
365365
if validator is None or validator.blocks_created > 0:
366366
return
367-
self.send_alert("zero_block_created", hours=round(period // 3600, 1))
367+
self.send_alert("zero_block_created", hours=round(period / 3600))
368368

369369
def check_slashed(self):
370370
if not self.ton.using_validator():

modules/backups.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pkg_resources
77

88
from modules.module import MtcModule
9-
from mypylib.mypylib import color_print, ip2int, run_as_root, parse
9+
from mypylib.mypylib import color_print, ip2int, run_as_root, parse, MyPyClass
1010
from mytoninstaller.config import get_own_ip
1111

1212

@@ -80,6 +80,10 @@ def restore_backup(self, args):
8080
command_args = ["-m", self.ton.local.buffer.my_work_dir, "-n", args[0], "-i", ip]
8181

8282
if self.run_restore_backup(command_args) == 0:
83+
self.ton.local.load_db()
84+
if self.ton.using_validator():
85+
from modules.btc_teleport import BtcTeleportModule
86+
BtcTeleportModule(self.ton, self.local).init(reinstall=True)
8387
color_print("restore_backup - {green}OK{endc}")
8488
self.local.exit()
8589
else:

modules/btc_teleport.py

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import json
2+
import os
3+
import subprocess
4+
5+
import pkg_resources
6+
7+
from modules.module import MtcModule
8+
from mypylib.mypylib import run_as_root, color_print, bcolors, print_table
9+
10+
11+
class BtcTeleportModule(MtcModule):
12+
13+
COORDINATOR_ADDRESS = 'Ef_q19o4m94xfF-yhYB85Qe6rTHDX-VTSzxBh4XpAfZMaOvk'
14+
CONFIGURATOR_ADDRESS = 'EQAR_I_lQm5wnEePggUKfioQUFs1vN1YYkK1Kl3WVAPiCzDZ'
15+
16+
def __init__(self, ton, local, *args, **kwargs):
17+
super().__init__(ton, local, *args, **kwargs)
18+
self.keystore_path = self.ton.local.buffer.my_work_dir + '/btc_oracle_keystore'
19+
self.repo_name = 'ton-teleport-btc-periphery'
20+
self.src_dir = f"/usr/src/{self.repo_name}"
21+
self.bin_dir = self.src_dir + '/out'
22+
23+
def create_local_file(self):
24+
from mytoninstaller.mytoninstaller import CreateLocalConfigFile
25+
CreateLocalConfigFile(self.local, [])
26+
27+
def create_env_file(self, reinit=False):
28+
env_path = self.bin_dir + '/.env'
29+
if os.path.exists(env_path) and not reinit:
30+
return
31+
self.create_local_file()
32+
config_path = "/usr/bin/ton/local.config.json"
33+
if not os.path.exists(config_path):
34+
config_path = 'https://ton.org/global-config.json'
35+
warning_text = f"""
36+
WARNING: Could not create local config file. Using global config file ({config_path}).
37+
Please try to create local config file (`mytonctrl <<< "installer clcf"`) and update its path in {env_path} and restart
38+
btc teleport service (`systemctl restart btc_teleport`) or contact validators support.
39+
"""
40+
self.local.add_log(warning_text, 'warning')
41+
text = f"""
42+
COMMON_TON_CONFIG={config_path}
43+
COMMON_TON_CONTRACT_COORDINATOR={self.COORDINATOR_ADDRESS}
44+
ORACLE_STANDALONE_MODE=false
45+
ORACLE_KEYSTORE_PATH={self.keystore_path}
46+
ORACLE_VALIDATOR_ENGINE_CONSOLE_PATH={self.ton.validatorConsole.appPath}
47+
ORACLE_SERVER_PUBLIC_KEY_PATH={self.ton.validatorConsole.pubKeyPath}
48+
ORACLE_CLIENT_PRIVATE_KEY_PATH={self.ton.validatorConsole.privKeyPath}
49+
ORACLE_VALIDATOR_SERVER_ADDR={self.ton.validatorConsole.addr}
50+
ORACLE_DKG_FETCH_PERIOD=15
51+
ORACLE_EXECUTE_SIGN_PERIOD=15
52+
ORACLE_SEND_START_DKG_PERIOD=30
53+
API_CALL_TIMEOUT=15
54+
LOG_FILE=/var/log/btc_teleport/btc_teleport.log
55+
"""
56+
with open(env_path, 'w') as f:
57+
f.write(text)
58+
59+
def add_daemon(self):
60+
start = f'{self.bin_dir}/oracle'
61+
script_path = pkg_resources.resource_filename('mytoninstaller', 'scripts/add2systemd.sh')
62+
user = os.environ.get("USER", "root")
63+
run_as_root(['bash', script_path, '-n', 'btc_teleport', '-u', user, '-g', user, '-s', start, '-w', self.bin_dir])
64+
65+
def install(self, branch):
66+
script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/btc_teleport1.sh')
67+
exit_code = run_as_root(["bash", script_path, "-s", '/usr/src', "-r", self.repo_name, "-b", branch])
68+
if exit_code != 0:
69+
raise Exception('Failed to install btc_teleport')
70+
script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/btc_teleport2.sh')
71+
subprocess.run(["bash", script_path, "-s", self.src_dir])
72+
73+
def init(self, reinstall=False, branch: str = 'master'):
74+
if os.path.exists(self.src_dir) and not reinstall:
75+
return
76+
self.local.add_log('Installing btc_teleport', 'info')
77+
os.makedirs(self.keystore_path, mode=0o700, exist_ok=True)
78+
self.install(branch)
79+
self.create_env_file()
80+
self.add_daemon()
81+
self.local.add_log('Installed btc_teleport', 'info')
82+
83+
@staticmethod
84+
def run_remove_btc_teleport(args):
85+
script_path = pkg_resources.resource_filename('mytonctrl', 'scripts/remove_btc_teleport.sh')
86+
return run_as_root(["bash", script_path] + args)
87+
88+
def get_save_offers(self):
89+
bname = "saveOffersBtcTeleport"
90+
save_offers = self.ton.local.db.get(bname)
91+
if save_offers is None:
92+
save_offers = dict()
93+
self.ton.local.db[bname] = save_offers
94+
return save_offers
95+
96+
def auto_vote_offers(self):
97+
save_offers = self.get_save_offers()
98+
if not save_offers:
99+
return
100+
current_offers = self.get_offers()
101+
config34 = self.ton.GetConfig34()
102+
for save_offer in list(save_offers.values()):
103+
offer_hash = save_offer['hash']
104+
if offer_hash not in current_offers:
105+
if save_offer['ttl'] > config34['endWorkTime']: # offer is not expired but not found in current offers, so we assume it has been accepted
106+
save_offers.pop(offer_hash)
107+
self.local.add_log(f'Removing offer {offer_hash} from saved offers', 'debug')
108+
continue
109+
offer = current_offers[save_offer['hash']]
110+
if offer['created_at'] != save_offer['created_at'] and save_offer['ttl'] > config34['endWorkTime']: # offer is not expired but has been changed, so it has been accepted and launched again
111+
save_offers.pop(offer_hash)
112+
self.local.add_log(f'Removing offer {offer_hash} from saved offers', 'debug')
113+
continue
114+
self.vote_offer_btc_teleport([offer_hash])
115+
116+
def get_offers(self):
117+
self.local.add_log("start get_offers_btc_teleport function", "debug")
118+
cmd = f"runmethodfull {self.CONFIGURATOR_ADDRESS} list_proposals"
119+
result = self.ton.liteClient.Run(cmd)
120+
raw_offers = self.ton.Result2List(result)
121+
raw_offers = raw_offers[0]
122+
validators = self.ton.GetValidatorsList(fast=True)
123+
total_weight = 0
124+
for v in validators:
125+
if v['is_masterchain'] is False:
126+
continue
127+
total_weight += v['weight']
128+
129+
offers = {}
130+
for offer in raw_offers:
131+
if len(offer) == 0:
132+
continue
133+
item = {}
134+
o_hash = str(offer[0])
135+
item["hash"] = o_hash
136+
item["remaining_losses"] = offer[1]
137+
item["price"] = offer[2]
138+
item["proposal"] = offer[3]
139+
item["votedValidators"] = offer[4]
140+
weight_remaining = offer[5]
141+
item["weightRemaining"] = weight_remaining
142+
item["vset_id"] = offer[6]
143+
item["creator"] = offer[7]
144+
item["created_at"] = offer[-1] # todo: bug in parsing slice in get method output
145+
required_weight = total_weight * 2 / 3
146+
if len(item["votedValidators"]) == 0:
147+
weight_remaining = required_weight
148+
available_weight = required_weight - weight_remaining
149+
item["approvedPercent"] = round(available_weight / total_weight * 100, 3)
150+
item["isPassed"] = (weight_remaining < 0)
151+
offers[o_hash] = item
152+
return offers
153+
154+
def add_save_offer(self, offer: dict):
155+
config15 = self.ton.GetConfig15()
156+
offer['ttl'] = offer['created_at'] + config15['validatorsElectedFor'] * 3 # proposal lives 3 rounds
157+
self.get_save_offers()[offer['hash']] = offer
158+
self.ton.local.save()
159+
160+
def vote_offer_btc_teleport(self, args):
161+
if len(args) == 0:
162+
color_print("{red}Bad args. Usage:{endc} vote_offer_btc_teleport <offer-hash> [offer-hash-2 offer-hash-3 ...]")
163+
return
164+
wallet = self.ton.GetValidatorWallet(mode="vote")
165+
validator_key = self.ton.GetValidatorKey()
166+
validator_pubkey_b64 = self.ton.GetPubKeyBase64(validator_key)
167+
validator_index = self.ton.GetValidatorIndex()
168+
config34 = self.ton.GetConfig34()
169+
if validator_index == -1 or validator_index >= config34['mainValidators']:
170+
self.local.add_log("Can not vote for BTC Teleport proposal from non-masterchain validator", "error")
171+
return
172+
for offer_hash in args:
173+
current_offers = self.get_offers()
174+
if offer_hash not in current_offers:
175+
self.local.add_log("Offer not found, skip", "warning")
176+
continue
177+
offer = current_offers[offer_hash]
178+
if validator_index in offer.get("votedValidators"):
179+
self.local.add_log("Proposal already has been voted", "debug")
180+
continue
181+
self.add_save_offer(offer)
182+
request_hash = self.ton.CreateConfigProposalRequest(offer_hash, validator_index)
183+
validator_signature = self.ton.GetValidatorSignature(validator_key, request_hash)
184+
path = self.ton.SignProposalVoteRequestWithValidator(offer_hash, validator_index, validator_pubkey_b64,
185+
validator_signature)
186+
path = self.ton.SignBocWithWallet(wallet, path, self.CONFIGURATOR_ADDRESS, 1.5)
187+
self.ton.SendFile(path, wallet)
188+
189+
def print_offers_btc_teleport_list(self, args):
190+
data = self.get_offers()
191+
if not data:
192+
print("No data")
193+
return
194+
if "--json" in args:
195+
text = json.dumps(data, indent=2)
196+
print(text)
197+
return
198+
table = [["Hash", "Votes", "Approved", "Is passed"]]
199+
for item in data.values():
200+
o_hash = item.get("hash")
201+
voted_validators = len(item.get("votedValidators"))
202+
approved_percent_text = f"{item.get('approvedPercent')}%"
203+
is_passed = item.get("isPassed")
204+
if "hash" not in args:
205+
from modules.utilities import UtilitiesModule
206+
o_hash = UtilitiesModule.reduct(o_hash)
207+
if is_passed is True:
208+
is_passed = bcolors.green_text("true")
209+
if is_passed is False:
210+
is_passed = bcolors.red_text("false")
211+
table += [[o_hash, voted_validators, approved_percent_text, is_passed]]
212+
print_table(table)
213+
214+
def remove_btc_teleport(self, args: list):
215+
if len(args) > 1:
216+
color_print("{red}Bad args. Usage:{endc} remove_btc_teleport [--force]")
217+
return
218+
if '--force' not in args:
219+
if -1 < self.ton.GetValidatorIndex() < self.ton.GetConfig34()['mainValidators']:
220+
self.local.add_log('You can not remove btc_teleport on working masterchain validator', 'error')
221+
return
222+
exit_code = self.run_remove_btc_teleport(["-s", self.src_dir, "-k", self.keystore_path])
223+
if exit_code != 0:
224+
raise Exception('Failed to remove btc_teleport')
225+
self.local.add_log('Removed btc_teleport', 'info')
226+
227+
def add_console_commands(self, console):
228+
console.AddItem("remove_btc_teleport", self.remove_btc_teleport, self.local.translate("remove_btc_teleport_cmd"))
229+
console.AddItem("vote_offer_btc_teleport", self.vote_offer_btc_teleport, self.local.translate("vote_offer_btc_teleport_cmd"))
230+
console.AddItem("print_offers_btc_teleport_list", self.print_offers_btc_teleport_list, self.local.translate("print_offers_btc_teleport_list_cmd"))

modules/module.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from abc import ABC, abstractmethod
22

3+
from mypylib.mypylib import MyPyClass
4+
35

46
class MtcModule(ABC):
57

@@ -9,7 +11,7 @@ class MtcModule(ABC):
911
def __init__(self, ton, local, *args, **kwargs):
1012
from mytoncore.mytoncore import MyTonCore
1113
self.ton: MyTonCore = ton
12-
self.local = local
14+
self.local: MyPyClass = local
1315

1416
@abstractmethod
1517
def add_console_commands(self, console): ...

mypylib

mytoncore/functions.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ def Event(local, event_name):
5555
ValidatorDownEvent(local)
5656
elif event_name.startswith("enable_mode"):
5757
enable_mode(local, event_name)
58+
elif event_name == "enable_btc_teleport":
59+
enable_btc_teleport(local)
5860
local.exit()
5961
# end define
6062

@@ -90,6 +92,10 @@ def enable_mode(local, event_name):
9092
ton.enable_mode(mode)
9193
#end define
9294

95+
def enable_btc_teleport(local):
96+
ton = MyTonCore(local)
97+
from modules.btc_teleport import BtcTeleportModule
98+
BtcTeleportModule(ton, local).init(reinstall=True)
9399

94100
def Elections(local, ton):
95101
use_pool = ton.using_pool()
@@ -312,25 +318,35 @@ def get_ok_error(value: str):
312318
data['ls_queries']['error'] = int(k.split(':')[1])
313319
statistics = local.db.get("statistics", dict())
314320

315-
if time.time() - int(status.start_time) <= 60: # was node restart <60 sec ago, resetting node statistics
321+
# if time.time() - int(status.start_time) <= 60: # was node restart <60 sec ago, resetting node statistics
322+
# statistics['node'] = []
323+
324+
if 'node' not in statistics:
316325
statistics['node'] = []
317326

318-
# statistics['node'] = [stats_from_election_id, stats_from_prev_min, stats_now]
327+
if statistics['node']:
328+
if int(status.start_time) > statistics['node'][-1]['timestamp']:
329+
# node was restarted, reset node statistics
330+
statistics['node'] = []
331+
332+
# statistics['node']: [stats_from_election_id, stats_from_prev_min, stats_now]
319333

320-
election_id = ton.GetConfig34()['startWorkTime']
321-
if 'node' not in statistics or len(statistics['node']) == 0:
334+
election_id = ton.GetConfig34(no_cache=True)['startWorkTime']
335+
if len(statistics['node']) == 0:
322336
statistics['node'] = [None, data]
323337
elif len(statistics['node']) < 3:
324338
statistics['node'].append(data)
325-
if len(statistics['node']) == 3:
339+
elif len(statistics['node']) == 3:
326340
if statistics['node'][0] is None:
327341
if 0 < data['timestamp'] - election_id < 90:
328342
statistics['node'][0] = data
329343
elif statistics['node'][0]['timestamp'] < election_id:
330344
statistics['node'][0] = data
331-
statistics['node'] = statistics.get('node', []) + [data]
332-
statistics['node'].pop(1)
345+
temp = statistics.get('node', []) + [data]
346+
temp.pop(1)
347+
statistics['node'] = temp
333348
local.db["statistics"] = statistics
349+
local.save()
334350

335351

336352
def ReadTransData(local, scanner):
@@ -669,6 +685,9 @@ def General(local):
669685
from modules.prometheus import PrometheusModule
670686
local.start_cycle(PrometheusModule(ton, local).push_metrics, sec=30, args=())
671687

688+
from modules.btc_teleport import BtcTeleportModule
689+
local.start_cycle(BtcTeleportModule(ton, local).auto_vote_offers, sec=180, args=())
690+
672691
if ton.in_initial_sync():
673692
local.start_cycle(check_initial_sync, sec=120, args=(local, ton))
674693

0 commit comments

Comments
 (0)