Skip to content

Commit 291acde

Browse files
authored
Merge pull request #261 from skalenetwork/optimize-config
Migrate configuration to Pydantic settings
2 parents 0d2a133 + 61985af commit 291acde

11 files changed

Lines changed: 130 additions & 89 deletions

File tree

bounty_agent.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@
2525
import logging
2626
import socket
2727
import time
28-
from datetime import datetime, timedelta
28+
from datetime import datetime, timedelta, timezone
2929

3030
import tenacity
3131
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_EXECUTED
3232
from apscheduler.schedulers.background import BackgroundScheduler
33+
from skale.core.settings import SkaleSettings, get_settings
3334
from skale.transactions.exceptions import TransactionError
3435
from web3.logs import DISCARD
3536

@@ -56,10 +57,16 @@
5657

5758

5859
class BountyAgent:
59-
def __init__(self, skale, node_id=None):
60+
def __init__(self, skale, settings: SkaleSettings, node_id=None):
6061
self.agent_name = get_agent_name(self.__class__.__name__)
6162
self.logger = logging.getLogger(self.agent_name)
62-
add_file_handler(self.logger, self.agent_name, node_id)
63+
add_file_handler(
64+
self.logger,
65+
self.agent_name,
66+
node_id,
67+
str(settings.sgx_url),
68+
str(settings.endpoint),
69+
)
6370
self.logger.info(f'Initialization of {self.agent_name} ...')
6471
if node_id is None:
6572
self.id = get_id_from_config(NODE_CONFIG_FILEPATH)
@@ -90,7 +97,7 @@ def get_reward_date(self):
9097
except Exception as err:
9198
self.notifier.send(f'Cannot get reward date from SKALE Manager: {err}', MsgIcon.ERROR)
9299
raise
93-
return datetime.utcfromtimestamp(reward_date)
100+
return datetime.fromtimestamp(reward_date, timezone.utc)
94101

95102
def get_bounty(self):
96103
try:
@@ -134,7 +141,7 @@ def job(self) -> None:
134141
reward_date = self.get_reward_date()
135142
last_block_number = self.skale.web3.eth.block_number
136143
block_data = call_retry(self.skale.web3.eth.get_block, last_block_number)
137-
block_timestamp = datetime.utcfromtimestamp(block_data['timestamp'])
144+
block_timestamp = datetime.fromtimestamp(block_data['timestamp'], timezone.utc)
138145
self.logger.info(f'Reward date: {reward_date}')
139146
self.logger.info(f'Block timestamp: {block_timestamp}')
140147
if reward_date > block_timestamp:
@@ -145,7 +152,7 @@ def job(self) -> None:
145152
def job_listener(self, event):
146153
if event.exception:
147154
self.logger.info('"Get Bounty" job failed')
148-
utc_now = datetime.utcnow()
155+
utc_now = datetime.now(timezone.utc)
149156
self.scheduler.add_job(
150157
self.job, 'date', run_date=utc_now + timedelta(seconds=DELAY_AFTER_ERR)
151158
)
@@ -156,7 +163,7 @@ def job_listener(self, event):
156163
reward_date = self.get_reward_date()
157164
self.notifier.send(f'Next reward date: {reward_date}', MsgIcon.BOUNTY)
158165
except Exception:
159-
reward_date = datetime.utcnow() + timedelta(seconds=DELAY_AFTER_ERR)
166+
reward_date = datetime.now(timezone.utc) + timedelta(seconds=DELAY_AFTER_ERR)
160167
self.logger.info(f'Next try to get reward date: {reward_date}')
161168
self.scheduler.add_job(self.job, 'date', run_date=reward_date)
162169
self.scheduler.print_jobs()
@@ -165,7 +172,7 @@ def run(self) -> None:
165172
"""Starts agent."""
166173
reward_date = self.get_reward_date()
167174
self.logger.info(f"Next reward date on agent's start: {reward_date}")
168-
utc_now = datetime.utcnow()
175+
utc_now = datetime.now(timezone.utc)
169176
if utc_now > reward_date:
170177
reward_date = utc_now
171178
self.scheduler.add_job(self.job, 'date', run_date=reward_date)
@@ -179,11 +186,12 @@ def stop(self):
179186

180187

181188
if __name__ == '__main__':
182-
init_logger()
189+
st = get_settings(SkaleSettings)
190+
init_logger(str(st.sgx_url), str(st.endpoint))
183191
while True:
184192
try:
185-
skale = init_skale()
186-
bounty_agent = BountyAgent(skale)
193+
skale = init_skale(st)
194+
bounty_agent = BountyAgent(skale, settings=st)
187195
bounty_agent.run()
188196
while not bounty_agent.is_stopped:
189197
time.sleep(1)

configs/__init__.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import os
2+
from pathlib import Path
3+
4+
from skale.core.settings import SkaleSettings
25

36
ENV = os.environ.get('ENV')
47

58
LONG_LINE = '-' * 100
69

710
NOTIFIER_URL = 'http://localhost:3007/send-tg-notification'
8-
SKALE_VOLUME_PATH = os.getenv('SKALE_VOLUME_PATH', '/skale_vol')
9-
NODE_DATA_PATH = os.getenv('NODE_DATA_PATH', '/skale_node_data')
11+
NODE_DATA_FOLDER_NAME: str = 'node_data'
1012

11-
NODE_CONFIG_FILENAME = 'node_config.json'
12-
NODE_CONFIG_FILEPATH = os.path.join(NODE_DATA_PATH, NODE_CONFIG_FILENAME)
13+
SKALE_VOLUME_PATH: Path = Path(os.getenv('SKALE_VOLUME_PATH', '/skale_vol'))
14+
NODE_DATA_PATH: Path = SKALE_VOLUME_PATH / NODE_DATA_FOLDER_NAME
1315

14-
STATE_FILENAME = os.getenv('STATE_FILENAME')
15-
STATE_BASE_PATH = os.path.join(NODE_DATA_PATH, 'eth-state')
16-
STATE_FILEPATH = None if not STATE_FILENAME else os.path.join(STATE_BASE_PATH, STATE_FILENAME)
16+
NODE_CONFIG_FILENAME = 'node_config.json'
17+
NODE_CONFIG_FILEPATH: Path = NODE_DATA_PATH / NODE_CONFIG_FILENAME
1718

1819
MIN_ETH_AMOUNT_IN_SKL = 0.01
1920
MIN_ETH_AMOUNT = int(MIN_ETH_AMOUNT_IN_SKL * (10**18))
@@ -22,14 +23,11 @@
2223
MISFIRE_GRACE_TIME = 365 * 24 * 60 * 60 # in seconds
2324
DELAY_AFTER_ERR = 60 # in seconds
2425

25-
SGX_SERVER_URL = os.getenv('SGX_SERVER_URL')
26-
SGX_CERTIFICATES_FOLDER_NAME = os.getenv('SGX_CERTIFICATES_DIR_NAME')
27-
28-
SGX_CERTIFICATES_FOLDER = None
29-
if SGX_CERTIFICATES_FOLDER_NAME:
30-
SGX_CERTIFICATES_FOLDER = os.path.join(NODE_DATA_PATH, SGX_CERTIFICATES_FOLDER_NAME)
31-
else:
32-
SGX_CERTIFICATES_FOLDER = os.getenv('SGX_CERTIFICATES_FOLDER')
26+
SGX_CERTIFICATES_FOLDER = NODE_DATA_PATH / 'sgx_certs'
3327

3428
DEFAULT_POOL = 'transactions'
3529
REDIS_URI = os.getenv('REDIS_URI', 'redis://@127.0.0.1:6379')
30+
31+
SETTINGS_FOLDER_PATH: Path = NODE_DATA_PATH / 'settings'
32+
NODE_SETTINGS_PATH: Path = SETTINGS_FOLDER_PATH / 'node.toml'
33+
SkaleSettings.model_config['toml_file'] = NODE_SETTINGS_PATH

configs/web3.py

Lines changed: 0 additions & 4 deletions
This file was deleted.

helper-scripts

pyproject.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ name = "bounty-agent"
33
version = "3.1.0"
44
requires-python = ">=3.13"
55
dependencies = [
6-
"apscheduler==3.11.0",
6+
"apscheduler==3.11.2",
77
"peewee==3.18.2",
88
"schedule==1.2.2",
9-
"skale.py==7.6dev3",
10-
"tenacity==9.1.2",
9+
"skale.py==7.13dev1",
10+
"tenacity==9.1.4",
1111
]
1212
[project.optional-dependencies]
13-
dev = ["ruff==0.14.1", "mypy==1.18.2"]
13+
dev = ["ruff==0.15.1", "mypy==1.19.1"]
1414
test = [
15-
"pytest==8.4.2",
15+
"pytest==9.0.2",
1616
"pytest-cov==7.0.0",
1717
"freezegun==1.5.5",
1818
"codecov==2.1.13",
@@ -25,6 +25,9 @@ log_cli_format = "[%(asctime)s] [%(levelname)8s] [%(threadName)s] %(message)s (%
2525
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
2626
filterwarnings = ["ignore::DeprecationWarning"]
2727

28+
[tool.uv]
29+
prerelease = "allow"
30+
2831
[tool.ruff]
2932
line-length = 100
3033

scripts/export_env.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export SKALE_VOLUME_PATH=$DIR/../skale_vol
77
export NODE_DATA_PATH=$DIR/../skale_node_data
88
export PYTHONPATH=$PYTHONPATH:.
99
export ALLOWED_TS_DIFF=-1
10+
export DEFAULT_GAS_PRICE_WEI=1000000000

tests/conftest.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@
44

55
import pytest
66
from skale import SkaleManager
7+
from skale.core.settings import SkaleSettings
78
from skale.utils.helper import get_skale_manager_address
89
from skale.utils.web3_utils import init_web3
910
from skale.wallets import Web3Wallet
1011

1112
from tests.constants import ENDPOINT, ETH_PRIVATE_KEY, N_TEST_NODES, TEST_ABI_FILEPATH
12-
from tests.prepare_validator import create_dirs, create_set_of_nodes, get_active_ids
13+
from tests.prepare_validator import (
14+
create_dirs,
15+
create_set_of_nodes,
16+
get_active_ids,
17+
setup_validator,
18+
)
1319

1420

1521
@pytest.fixture(scope='session', autouse=True)
@@ -18,16 +24,38 @@ def setup_test_paths():
1824
os.environ['SKALE_VOLUME_PATH'] = './skale_vol'
1925
os.environ['NODE_DATA_PATH'] = './skale_node_data'
2026

27+
# Force low gas price for tests to avoid Insufficient Funds in local anvil
28+
import skale.config as skale_config
29+
skale_config.DEFAULT_GAS_PRICE = 1000000000 # 1 Gwei
30+
31+
32+
@pytest.fixture(scope='session')
33+
def settings():
34+
manager_address = get_skale_manager_address(TEST_ABI_FILEPATH)
35+
return SkaleSettings(
36+
env_type='devnet',
37+
endpoint=ENDPOINT,
38+
node_version='0.0.0',
39+
block_device='/dev/sda',
40+
manager_contracts=manager_address,
41+
ima_contracts='0x' + '0' * 40,
42+
docker_lvmpy_version='0.0.0',
43+
sgx_url='http://localhost:1026',
44+
default_gas_price_wei=1000000000,
45+
)
46+
2147

2248
@pytest.fixture(scope='session')
23-
def skale():
49+
def skale(settings):
2450
"""Returns a SKALE instance with provider from config"""
25-
web3 = init_web3(ENDPOINT)
51+
web3 = init_web3(str(settings.endpoint))
2652
wallet = Web3Wallet(ETH_PRIVATE_KEY, web3)
27-
manager_address = get_skale_manager_address(TEST_ABI_FILEPATH)
28-
skale = SkaleManager(ENDPOINT, manager_address, wallet)
53+
skale = SkaleManager(str(settings.endpoint), settings.manager_contracts, wallet)
2954

3055
create_dirs()
56+
57+
setup_validator(skale)
58+
3159
ids = get_active_ids(skale)
3260
print(f'Existing Node IDs = {ids}')
3361
cur_node_id = max(ids) + 1 if len(ids) else 0

tests/test_bounty_agent.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# along with this program. If not, see <https://www.gnu.org/licenses/>.
1919

2020
import time
21-
from datetime import datetime
21+
from datetime import datetime, timezone
2222

2323
import pytest
2424
from freezegun import freeze_time
@@ -40,8 +40,8 @@ def node_id(skale):
4040

4141

4242
@pytest.fixture(scope='module')
43-
def bounty_collector(skale, node_id):
44-
return bounty_agent.BountyAgent(skale, node_id)
43+
def bounty_collector(skale, settings, node_id):
44+
return bounty_agent.BountyAgent(skale, settings, node_id)
4545

4646

4747
def test_check_if_node_is_registered(skale, node_id):
@@ -54,7 +54,7 @@ def test_check_if_node_is_registered(skale, node_id):
5454
def test_get_bounty_neg(skale, bounty_collector):
5555
last_block_number = skale.web3.eth.block_number
5656
block_data = skale.web3.eth.get_block(last_block_number)
57-
block_timestamp = datetime.utcfromtimestamp(block_data['timestamp'])
57+
block_timestamp = datetime.fromtimestamp(block_data['timestamp'], timezone.utc)
5858
reward_date = bounty_collector.get_reward_date()
5959
print(f'Reward date: {reward_date}')
6060
print(f'Timestamp: {block_timestamp}')
@@ -74,7 +74,7 @@ def get_bounty_events(skale, node_id):
7474
args = log['args']
7575
tx_block_number = log['blockNumber']
7676
block_data = skale.web3.eth.get_block(tx_block_number)
77-
block_timestamp = datetime.utcfromtimestamp(block_data['timestamp'])
77+
block_timestamp = datetime.fromtimestamp(block_data['timestamp'], timezone.utc)
7878
bounty_events.append(
7979
(
8080
args['nodeIndex'],
@@ -101,14 +101,14 @@ def test_bounty_job_saves_data(skale, bounty_collector):
101101
assert len(bounties) == 1
102102

103103

104-
def test_run_agent(skale, node_id):
105-
bounty_collector = bounty_agent.BountyAgent(skale, node_id)
104+
def test_run_agent(skale, settings, node_id):
105+
bounty_collector = bounty_agent.BountyAgent(skale, settings, node_id)
106106
reward_date = skale.nodes.contract.functions.getNodeNextRewardDate(bounty_collector.id).call()
107107
print(f'Reward date: {reward_date}')
108108
go_to_date(skale.web3, reward_date + REWARD_DATE_OFFSET)
109109
print('Latest block timestamp', skale.web3.eth.get_block('latest')['timestamp'])
110110

111-
with freeze_time(datetime.utcfromtimestamp(reward_date + REWARD_DATE_OFFSET)):
111+
with freeze_time(datetime.fromtimestamp(reward_date + REWARD_DATE_OFFSET, timezone.utc)):
112112
bounty_collector.run()
113113
bounty_collector.stop()
114114

tests/test_init_agent.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,34 @@
2626
from tools.exceptions import NodeNotFoundException
2727

2828

29-
def test_init_agent_pos(skale):
29+
def test_init_agent_pos(skale, settings):
3030
print("Test agent init with a given node id")
31-
agent0 = BountyAgent(skale, 0)
31+
agent0 = BountyAgent(skale, settings, 0)
3232
assert agent0.id == 0
3333

3434
print("Test agent init without given node id - read id from file")
3535
with open(NODE_CONFIG_FILEPATH, 'w') as json_file:
3636
json.dump({'node_id': 1}, json_file)
3737

38-
agent1 = BountyAgent(skale)
38+
agent1 = BountyAgent(skale, settings)
3939
assert agent1.id == 1
4040

4141

42-
def test_init_agent_neg(skale):
42+
def test_init_agent_neg(skale, settings):
4343
print("Test agent init with a non-existing node id")
4444
with pytest.raises(NodeNotFoundException):
45-
BountyAgent(skale, 100)
45+
BountyAgent(skale, settings, 100)
4646

4747
print("Test agent init with a negative node id")
4848
with open(NODE_CONFIG_FILEPATH, 'w') as json_file:
4949
json.dump({'node_id': -1}, json_file)
5050

5151
with pytest.raises(Exception):
52-
BountyAgent(skale)
52+
BountyAgent(skale, settings)
5353

5454
print("Test agent init with a non-integer node id")
5555
with open(NODE_CONFIG_FILEPATH, 'w') as json_file:
5656
json.dump({'node_id': 'one'}, json_file)
5757

5858
with pytest.raises(Exception):
59-
BountyAgent(skale)
59+
BountyAgent(skale, settings)

0 commit comments

Comments
 (0)