Skip to content

Commit f97cfff

Browse files
authored
Merge pull request #248 from ton-blockchain/mytonctrl2_dev
Mytonctrl2 dev
2 parents f28a021 + 46b95fd commit f97cfff

File tree

17 files changed

+586
-203
lines changed

17 files changed

+586
-203
lines changed

modules/__init__.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import typing
2+
from dataclasses import dataclass
3+
4+
from modules.module import MtcModule
5+
from modules.pool import PoolModule
6+
from modules.nominator_pool import NominatorPoolModule
7+
from modules.single_pool import SingleNominatorModule
8+
from modules.validator import ValidatorModule
9+
from modules.controller import ControllerModule
10+
11+
12+
MODES = {
13+
'validator': ValidatorModule,
14+
'nominator-pool': NominatorPoolModule,
15+
'single-nominator': SingleNominatorModule,
16+
'liquid-staking': ControllerModule,
17+
}
18+
19+
20+
def get_mode(mode_name: str) -> typing.Optional[MtcModule]:
21+
return MODES.get(mode_name)
22+
23+
24+
@dataclass
25+
class Setting:
26+
mode: typing.Optional[str]
27+
default_value: typing.Any
28+
description: str
29+
30+
31+
SETTINGS = {
32+
'stake': Setting('validator', None, 'Stake amount'),
33+
'stakePercent': Setting('validator', 99, 'Stake percent if `stake` is null'),
34+
'isSlashing': Setting('validator', None, 'Create complaints to validators'),
35+
'maxFactor': Setting('validator', None, 'Param send to Elector. if null will be taken from 17 config param'),
36+
'participateBeforeEnd': Setting('validator', None, 'Amount of seconds before start of round to participate'),
37+
'liquid_pool_addr': Setting('liquid-staking', None, 'Liquid staking pool address'),
38+
'min_loan': Setting('liquid-staking', 41000, 'Min loan amount'),
39+
'max_loan': Setting('liquid-staking', 43000, 'Max loan amount'),
40+
'max_interest_percent': Setting('liquid-staking', 10, 'Max interest percent'),
41+
'duplicateSendfile': Setting(None, True, 'Duplicate external to public Liteservers'),
42+
'sendTelemetry': Setting(None, True, 'Send node telemetry'),
43+
'telemetryLiteUrl': Setting(None, 'https://telemetry.toncenter.com/report_status', 'Telemetry url'),
44+
'overlayTelemetryUrl': Setting(None, 'https://telemetry.toncenter.com/report_overlays', 'Overlay telemetry url'),
45+
'duplicateApi': Setting(None, 'sendTelemetry', 'Duplicate external to Toncenter'),
46+
'duplicateApiUrl': Setting(None, 'https://[testnet.]toncenter.com/api/v2/sendBoc', 'Toncenter api url for duplicate'),
47+
'liteclient_timeout': Setting(None, 3, 'Liteclient default timeout'),
48+
'console_timeout': Setting(None, 3, 'Validator console default timeout'),
49+
'fift_timeout': Setting(None, 3, 'Fift default timeout'),
50+
'useDefaultCustomOverlays': Setting(None, True, 'Participate in default custom overlays node eligible to'),
51+
'defaultCustomOverlaysUrl': Setting(None, 'https://ton-blockchain.github.io/fallback_custom_overlays.json', 'Default custom overlays config url'),
52+
}
53+
54+
55+
def get_setting(name: str) -> typing.Optional[Setting]:
56+
return SETTINGS.get(name)
57+
58+
59+
def get_mode_settings(name: str):
60+
return {k: v for k, v in SETTINGS.items() if v.mode == name}
61+

modules/controller.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
class ControllerModule(MtcModule):
1212

13+
description = 'Liquid staking controllers.'
14+
default_value = False
15+
1316
def do_create_controllers(self):
1417
new_controllers = self.ton.GetControllers()
1518
old_controllers = self.ton.local.db.get("using_controllers", list())

modules/custom_overlays.py

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import json
2+
import requests
3+
4+
from mypylib.mypylib import color_print
5+
from modules.module import MtcModule
6+
from mytoncore.utils import hex2base64
7+
8+
9+
class CustomOverlayModule(MtcModule):
10+
11+
@staticmethod
12+
def parse_config(name: str, config: dict, vset: list = None):
13+
"""
14+
Converts config to validator-console friendly format
15+
:param name: custom overlay name
16+
:param config: config
17+
:param vset: list of validators adnl addresses, can be None if `@validators` not in config
18+
:return:
19+
"""
20+
result = {
21+
"name": name,
22+
"nodes": []
23+
}
24+
for k, v in config.items():
25+
if k == '@validators' and v:
26+
if vset is None:
27+
raise Exception("Validators set is not defined but @validators is in config")
28+
for v_adnl in vset:
29+
result["nodes"].append({
30+
"adnl_id": hex2base64(v_adnl),
31+
"msg_sender": False,
32+
})
33+
else:
34+
result["nodes"].append({
35+
"adnl_id": hex2base64(k),
36+
"msg_sender": v["msg_sender"],
37+
})
38+
if v["msg_sender"]:
39+
result["nodes"][-1]["msg_sender_priority"] = v["msg_sender_priority"]
40+
return result
41+
42+
def add_custom_overlay(self, args):
43+
if len(args) != 2:
44+
color_print("{red}Bad args. Usage:{endc} add_custom_overlay <name> <path_to_config>")
45+
return
46+
path = args[1]
47+
with open(path, 'r') as f:
48+
config = json.load(f)
49+
self.ton.set_custom_overlay(args[0], config)
50+
if '@validators' in config:
51+
print('Dynamic overlay will be added within 1 minute')
52+
else:
53+
result = self.add_custom_overlay_to_vc(self.parse_config(args[0], config))
54+
if not result:
55+
print('Failed to add overlay to validator console')
56+
color_print("add_custom_overlay - {red}ERROR{endc}")
57+
return
58+
color_print("add_custom_overlay - {green}OK{endc}")
59+
60+
def list_custom_overlays(self, args):
61+
if not self.ton.get_custom_overlays():
62+
color_print("{red}No custom overlays{endc}")
63+
return
64+
for k, v in self.ton.get_custom_overlays().items():
65+
color_print(f"Custom overlay {{bold}}{k}{{endc}}:")
66+
print(json.dumps(v, indent=4))
67+
68+
def delete_custom_overlay(self, args):
69+
if len(args) != 1:
70+
color_print("{red}Bad args. Usage:{endc} delete_custom_overlay <name>")
71+
return
72+
if '@validators' in self.ton.get_custom_overlays().get(args[0], {}):
73+
self.ton.delete_custom_overlay(args[0])
74+
print('Dynamic overlay will be deleted within 1 minute')
75+
else:
76+
self.ton.delete_custom_overlay(args[0])
77+
result = self.delete_custom_overlay_from_vc(args[0])
78+
if not result:
79+
print('Failed to delete overlay from validator console')
80+
color_print("delete_custom_overlay - {red}ERROR{endc}")
81+
return
82+
color_print("delete_custom_overlay - {green}OK{endc}")
83+
84+
def check_node_eligible_for_custom_overlay(self, config: dict):
85+
vconfig = self.ton.GetValidatorConfig()
86+
my_adnls = vconfig.adnl
87+
node_adnls = [i["adnl_id"] for i in config["nodes"]]
88+
for adnl in my_adnls:
89+
if adnl.id in node_adnls:
90+
return True
91+
return False
92+
93+
def delete_custom_overlay_from_vc(self, name: str):
94+
result = self.ton.validatorConsole.Run(f"delcustomoverlay {name}")
95+
return 'success' in result
96+
97+
def add_custom_overlay_to_vc(self, config: dict):
98+
if not self.check_node_eligible_for_custom_overlay(config):
99+
self.ton.local.add_log(f"Node has no adnl address required for custom overlay {config.get('name')}", "debug")
100+
return False
101+
self.ton.local.add_log(f"Adding custom overlay {config.get('name')}", "debug")
102+
path = self.ton.tempDir + f'/custom_overlay_{config["name"]}.json'
103+
with open(path, 'w') as f:
104+
json.dump(config, f)
105+
result = self.ton.validatorConsole.Run(f"addcustomoverlay {path}")
106+
return 'success' in result
107+
108+
def custom_overlays(self):
109+
config = self.get_default_custom_overlay()
110+
if config is not None:
111+
self.ton.set_custom_overlay('default', config)
112+
self.deploy_custom_overlays()
113+
114+
def deploy_custom_overlays(self):
115+
result = self.ton.validatorConsole.Run("showcustomoverlays")
116+
if 'unknown command' in result:
117+
return # node old version
118+
names = []
119+
for line in result.split('\n'):
120+
if line.startswith('Overlay'):
121+
names.append(line.split(' ')[1].replace('"', '').replace(':', ''))
122+
123+
config34 = self.ton.GetConfig34()
124+
current_el_id = config34['startWorkTime']
125+
current_vset = [i["adnlAddr"] for i in config34['validators']]
126+
127+
config36 = self.ton.GetConfig36()
128+
next_el_id = config36['startWorkTime'] if config36['validators'] else 0
129+
next_vset = [i["adnlAddr"] for i in config36['validators']]
130+
131+
for name in names:
132+
# check that overlay still exists in mtc db
133+
pure_name = name
134+
suffix = name.split('_')[-1]
135+
if suffix.startswith('elid') and suffix.split('elid')[-1].isdigit(): # probably election id
136+
pure_name = '_'.join(name.split('_')[:-1])
137+
el_id = int(suffix.split('elid')[-1])
138+
if el_id not in (current_el_id, next_el_id):
139+
self.ton.local.add_log(f"Overlay {name} is not in current or next election, deleting", "debug")
140+
self.delete_custom_overlay_from_vc(name) # delete overlay if election id is not in current or next election
141+
continue
142+
143+
if pure_name not in self.ton.get_custom_overlays():
144+
self.ton.local.add_log(f"Overlay {name} ({pure_name}) is not in mtc db, deleting", "debug")
145+
self.delete_custom_overlay_from_vc(name) # delete overlay if it's not in mtc db
146+
147+
for name, config in self.ton.get_custom_overlays().items():
148+
if name in names:
149+
continue
150+
if '@validators' in config:
151+
new_name = name + '_elid' + str(current_el_id)
152+
if new_name not in names:
153+
node_config = self.parse_config(new_name, config, current_vset)
154+
self.add_custom_overlay_to_vc(node_config)
155+
156+
if next_el_id != 0:
157+
new_name = name + '_elid' + str(next_el_id)
158+
if new_name not in names:
159+
node_config = self.parse_config(new_name, config, next_vset)
160+
self.add_custom_overlay_to_vc(node_config)
161+
else:
162+
node_config = self.parse_config(name, config)
163+
self.add_custom_overlay_to_vc(node_config)
164+
165+
def get_default_custom_overlay(self):
166+
if not self.ton.local.db.get('useDefaultCustomOverlays', True):
167+
return None
168+
network = self.ton.GetNetworkName()
169+
default_url = 'https://ton-blockchain.github.io/fallback_custom_overlays.json'
170+
url = self.ton.local.db.get('defaultCustomOverlaysUrl', default_url)
171+
resp = requests.get(url)
172+
if resp.status_code != 200:
173+
self.ton.local.add_log(f"Failed to get default custom overlays from {url}", "error")
174+
return None
175+
config = resp.json()
176+
return config.get(network)
177+
178+
def add_console_commands(self, console):
179+
console.AddItem("add_custom_overlay", self.add_custom_overlay, self.local.translate("add_custom_overlay_cmd"))
180+
console.AddItem("list_custom_overlays", self.list_custom_overlays, self.local.translate("list_custom_overlays_cmd"))
181+
console.AddItem("delete_custom_overlay", self.delete_custom_overlay, self.local.translate("delete_custom_overlay_cmd"))

modules/module.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from abc import ABC, abstractmethod
2-
from mytoncore.mytoncore import MyTonCore
32

43

54
class MtcModule(ABC):
65

6+
description = '' # module text description
7+
default_value = True # is module enabled by default
8+
79
def __init__(self, ton, local, *args, **kwargs):
10+
from mytoncore.mytoncore import MyTonCore
811
self.ton: MyTonCore = ton
912
self.local = local
1013

modules/nominator_pool.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
class NominatorPoolModule(PoolModule):
99

10+
description = 'Standard nominator pools.'
11+
default_value = False
12+
1013
def do_create_pool(self, pool_name, validator_reward_share_percent, max_nominators_count, min_validator_stake,
1114
min_nominator_stake):
1215
self.ton.local.add_log("start CreatePool function", "debug")

modules/pool.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
class PoolModule(MtcModule):
88

9+
description = 'Basic pools functions.'
10+
default_value = False
11+
912
def print_pools_list(self, args):
1013
table = list()
1114
table += [["Name", "Status", "Balance", "Version", "Address"]]

modules/single_pool.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
class SingleNominatorModule(PoolModule):
1010

11+
description = 'Orbs\'s single nominator pools.'
12+
default_value = False
13+
1114
def do_create_single_pool(self, pool_name, owner_address):
1215
self.ton.local.add_log("start create_single_pool function", "debug")
1316

modules/validator.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from mypylib.mypylib import color_print
22
from modules.module import MtcModule
33

4-
from mytoncore.functions import Elections
5-
64

75
class ValidatorModule(MtcModule):
86

7+
description = ('Validator functions. Activates participating in elections and staking. '
8+
'If pools and l/s modes are disabled stakes from validator wallet.')
9+
10+
default_value = True
11+
912
def vote_offer(self, args):
1013
if len(args) == 0:
1114
color_print("{red}Bad args. Usage:{endc} vo <offer-hash>")
@@ -15,6 +18,7 @@ def vote_offer(self, args):
1518
color_print("VoteOffer - {green}OK{endc}")
1619

1720
def vote_election_entry(self, args):
21+
from mytoncore.functions import Elections
1822
Elections(self.ton.local, self.ton)
1923
color_print("VoteElectionEntry - {green}OK{endc}")
2024

0 commit comments

Comments
 (0)