naeural-client 2.6.24__py3-none-any.whl → 2.6.26__py3-none-any.whl
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.
- naeural_client/_ver.py +1 -1
- naeural_client/base/generic_session.py +19 -2
- naeural_client/bc/base.py +64 -0
- naeural_client/cli/cli_commands.py +16 -2
- naeural_client/cli/nodes.py +8 -2
- naeural_client/cli/oracles.py +165 -0
- naeural_client/utils/oracle_sync/multiple_requests.py +585 -0
- {naeural_client-2.6.24.dist-info → naeural_client-2.6.26.dist-info}/METADATA +7 -1
- {naeural_client-2.6.24.dist-info → naeural_client-2.6.26.dist-info}/RECORD +12 -10
- {naeural_client-2.6.24.dist-info → naeural_client-2.6.26.dist-info}/WHEEL +0 -0
- {naeural_client-2.6.24.dist-info → naeural_client-2.6.26.dist-info}/entry_points.txt +0 -0
- {naeural_client-2.6.24.dist-info → naeural_client-2.6.26.dist-info}/licenses/LICENSE +0 -0
naeural_client/_ver.py
CHANGED
@@ -2664,6 +2664,7 @@ class GenericSession(BaseDecentrAIObject):
|
|
2664
2664
|
df_only=False,
|
2665
2665
|
debug=False,
|
2666
2666
|
eth=False,
|
2667
|
+
all_info=False,
|
2667
2668
|
):
|
2668
2669
|
"""
|
2669
2670
|
This function will return a Pandas dataframe known nodes in the network based on
|
@@ -2698,6 +2699,9 @@ class GenericSession(BaseDecentrAIObject):
|
|
2698
2699
|
eth: bool, optional
|
2699
2700
|
If True, will use the nodes eth addresses instead of internal. Defaults to False.
|
2700
2701
|
|
2702
|
+
all_info: bool, optional
|
2703
|
+
If True, will return all the information. Defaults to False.
|
2704
|
+
|
2701
2705
|
Returns
|
2702
2706
|
-------
|
2703
2707
|
|
@@ -2723,11 +2727,19 @@ class GenericSession(BaseDecentrAIObject):
|
|
2723
2727
|
'Oracle' : PAYLOAD_DATA.NETMON_IS_SUPERVISOR,
|
2724
2728
|
'Peered' : PAYLOAD_DATA.NETMON_WHITELIST,
|
2725
2729
|
})
|
2726
|
-
|
2730
|
+
if all_info:
|
2731
|
+
mapping = OrderedDict({
|
2732
|
+
# we assign dummy integer values to the computed columns
|
2733
|
+
# and we will fillter them
|
2734
|
+
'ETH Address': 1,
|
2735
|
+
**mapping
|
2736
|
+
})
|
2727
2737
|
res = OrderedDict()
|
2728
2738
|
for k in mapping:
|
2729
2739
|
res[k] = []
|
2730
2740
|
|
2741
|
+
reverse_mapping = {v: k for k, v in mapping.items()}
|
2742
|
+
|
2731
2743
|
result, elapsed = self.__wait_for_supervisors_net_mon_data(
|
2732
2744
|
supervisor=supervisor,
|
2733
2745
|
timeout=timeout,
|
@@ -2759,6 +2771,8 @@ class GenericSession(BaseDecentrAIObject):
|
|
2759
2771
|
if supervisors_only and not is_supervisor:
|
2760
2772
|
continue
|
2761
2773
|
for key, column in reverse_mapping.items():
|
2774
|
+
if isinstance(key, int):
|
2775
|
+
continue
|
2762
2776
|
val = node_info.get(key, None)
|
2763
2777
|
if key == PAYLOAD_DATA.NETMON_LAST_REMOTE_TIME:
|
2764
2778
|
# val hols a string '2024-12-23 23:50:16.462155' and must be converted to a datetime
|
@@ -2772,7 +2786,10 @@ class GenericSession(BaseDecentrAIObject):
|
|
2772
2786
|
# again self.get_node_name(best_super) might not work if using the hb data
|
2773
2787
|
best_super_alias = node_info.get(PAYLOAD_DATA.NETMON_EEID, None)
|
2774
2788
|
val = self.bc_engine._add_prefix(val)
|
2775
|
-
if
|
2789
|
+
if all_info:
|
2790
|
+
val_eth = self.bc_engine.node_address_to_eth_address(val)
|
2791
|
+
res['ETH Address'].append(val_eth)
|
2792
|
+
elif eth:
|
2776
2793
|
val = self.bc_engine.node_address_to_eth_address(val)
|
2777
2794
|
elif key == PAYLOAD_DATA.NETMON_WHITELIST:
|
2778
2795
|
val = client_is_allowed
|
naeural_client/bc/base.py
CHANGED
@@ -621,6 +621,7 @@ class BaseBlockEngine:
|
|
621
621
|
address = self._remove_prefix(address)
|
622
622
|
address = BCct.ADDR_PREFIX + address
|
623
623
|
return address
|
624
|
+
|
624
625
|
|
625
626
|
|
626
627
|
def _pk_to_address(self, public_key):
|
@@ -915,6 +916,23 @@ class BaseBlockEngine:
|
|
915
916
|
the address without the prefix.
|
916
917
|
"""
|
917
918
|
return self._remove_prefix(address)
|
919
|
+
|
920
|
+
|
921
|
+
def maybe_add_prefix(self, address):
|
922
|
+
"""
|
923
|
+
Adds the prefix to the address
|
924
|
+
|
925
|
+
Parameters
|
926
|
+
----------
|
927
|
+
address : str
|
928
|
+
the text address.
|
929
|
+
|
930
|
+
Returns
|
931
|
+
-------
|
932
|
+
address : str
|
933
|
+
the address with the prefix.
|
934
|
+
"""
|
935
|
+
return self._add_prefix(address)
|
918
936
|
|
919
937
|
|
920
938
|
def dict_digest(self, dct_data, return_str=True):
|
@@ -1235,6 +1253,52 @@ class BaseBlockEngine:
|
|
1235
1253
|
def eth_account(self):
|
1236
1254
|
return self.__eth_account
|
1237
1255
|
|
1256
|
+
@staticmethod
|
1257
|
+
def is_valid_evm_address(address: str) -> bool:
|
1258
|
+
"""
|
1259
|
+
Check if the input string is a valid Ethereum (EVM) address using basic heuristics.
|
1260
|
+
|
1261
|
+
Parameters
|
1262
|
+
----------
|
1263
|
+
address : str
|
1264
|
+
The address string to verify.
|
1265
|
+
|
1266
|
+
Returns
|
1267
|
+
-------
|
1268
|
+
bool
|
1269
|
+
True if `address` meets the basic criteria for an EVM address, False otherwise.
|
1270
|
+
"""
|
1271
|
+
# Basic checks:
|
1272
|
+
# A) Must start with '0x'
|
1273
|
+
# B) Must be exactly 42 characters in total
|
1274
|
+
# C) All remaining characters must be valid hexadecimal digits
|
1275
|
+
if not address.startswith("0x"):
|
1276
|
+
return False
|
1277
|
+
if len(address) != 42:
|
1278
|
+
return False
|
1279
|
+
|
1280
|
+
hex_part = address[2:]
|
1281
|
+
# Ensure all characters in the hex part are valid hex digits
|
1282
|
+
return all(c in "0123456789abcdefABCDEF" for c in hex_part)
|
1283
|
+
|
1284
|
+
@staticmethod
|
1285
|
+
def is_valid_eth_address(address: str) -> bool:
|
1286
|
+
"""
|
1287
|
+
Check if the input string is a valid Ethereum (EVM) address using basic heuristics.
|
1288
|
+
|
1289
|
+
Parameters
|
1290
|
+
----------
|
1291
|
+
address : str
|
1292
|
+
The address string to verify.
|
1293
|
+
|
1294
|
+
Returns
|
1295
|
+
-------
|
1296
|
+
bool
|
1297
|
+
True if `address` meets the basic criteria for an EVM address, False otherwise.
|
1298
|
+
"""
|
1299
|
+
return BaseBlockEngine.is_valid_evm_address(address)
|
1300
|
+
|
1301
|
+
|
1238
1302
|
### end Ethereum
|
1239
1303
|
|
1240
1304
|
|
@@ -1,7 +1,8 @@
|
|
1
1
|
from naeural_client.cli.nodes import (
|
2
2
|
get_nodes, get_supervisors,
|
3
|
-
restart_node, shutdown_node
|
3
|
+
restart_node, shutdown_node,
|
4
4
|
)
|
5
|
+
from naeural_client.cli.oracles import get_availability
|
5
6
|
from naeural_client.utils.config import show_config, reset_config, show_address
|
6
7
|
|
7
8
|
|
@@ -17,12 +18,25 @@ CLI_COMMANDS = {
|
|
17
18
|
"--online" : "Get only online nodes as seen by a active supervisor (flag)", # DONE
|
18
19
|
"--peered": "Get only peered nodes - ie nodes that can be used by current client address (flag)", # DONE
|
19
20
|
"--supervisor" : "Use a specific supervisor node",
|
20
|
-
"--eth": "Use a specific node (flag)",
|
21
|
+
"--eth" : "Use a specific node (flag)",
|
22
|
+
"--wide" : "Display all available information (flag)",
|
21
23
|
}
|
22
24
|
},
|
23
25
|
"supervisors": {
|
24
26
|
"func": get_supervisors, # DONE
|
25
27
|
},
|
28
|
+
"avail": {
|
29
|
+
"func": get_availability,
|
30
|
+
"params": {
|
31
|
+
### use "(flag)" at the end of the description to indicate a boolean flag
|
32
|
+
### otherwise it will be treated as a str parameter
|
33
|
+
"node": "The ETH address of the node to be checked via the oracle network.",
|
34
|
+
"--start": "The start epoch number to check the availability from",
|
35
|
+
"--end": "The end epoch number to check the availability to",
|
36
|
+
"--json": "Enable full JSON oracle network output (flag)",
|
37
|
+
"--rounds": "The number of rounds to check the availability for testing purposes (default=1)",
|
38
|
+
}
|
39
|
+
}
|
26
40
|
},
|
27
41
|
"config": {
|
28
42
|
"show": {
|
naeural_client/cli/nodes.py
CHANGED
@@ -11,6 +11,7 @@ def _get_netstats(
|
|
11
11
|
supervisors_only=False,
|
12
12
|
return_session=False,
|
13
13
|
eth=False,
|
14
|
+
all_info=False
|
14
15
|
):
|
15
16
|
t1 = time()
|
16
17
|
from naeural_client import Session
|
@@ -19,6 +20,7 @@ def _get_netstats(
|
|
19
20
|
online_only=online_only, allowed_only=allowed_only, supervisor=supervisor,
|
20
21
|
supervisors_only=supervisors_only,
|
21
22
|
eth=eth,
|
23
|
+
all_info=all_info,
|
22
24
|
)
|
23
25
|
df = dct_info[SESSION_CT.NETSTATS_REPORT]
|
24
26
|
supervisor = dct_info[SESSION_CT.NETSTATS_REPORTER]
|
@@ -31,6 +33,7 @@ def _get_netstats(
|
|
31
33
|
return df, supervisor, super_alias, nr_supers, elapsed
|
32
34
|
|
33
35
|
|
36
|
+
|
34
37
|
def get_nodes(args):
|
35
38
|
"""
|
36
39
|
This function is used to get the information about the nodes and it will perform the following:
|
@@ -40,7 +43,8 @@ def get_nodes(args):
|
|
40
43
|
3. Wait for the second net mon message via Session and show progress.
|
41
44
|
4. Get the active nodes union via Session and display the nodes marking those peered vs non-peered.
|
42
45
|
"""
|
43
|
-
supervisor_addr = args.supervisor
|
46
|
+
supervisor_addr = args.supervisor
|
47
|
+
wide = args.wide
|
44
48
|
if args.verbose:
|
45
49
|
log_with_color(f"Getting nodes from supervisor <{supervisor_addr}>...", color='b')
|
46
50
|
|
@@ -50,6 +54,7 @@ def get_nodes(args):
|
|
50
54
|
allowed_only=args.peered,
|
51
55
|
supervisor=supervisor_addr,
|
52
56
|
eth=args.eth,
|
57
|
+
all_info=wide,
|
53
58
|
)
|
54
59
|
df, supervisor, super_alias, nr_supers, elapsed = res
|
55
60
|
if args.online:
|
@@ -150,4 +155,5 @@ def shutdown_node(args):
|
|
150
155
|
node = args.node
|
151
156
|
log_with_color(f"Attempting to shutdown node <{node}>", color='b')
|
152
157
|
_send_command_to_node(args, COMMANDS.SHUTDOWN)
|
153
|
-
return
|
158
|
+
return
|
159
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
"""
|
2
|
+
|
3
|
+
|
4
|
+
NOTE: if any of oracles return data["result"]["oracle"]["manager"]["valid"] != True then
|
5
|
+
- ommit that oracle from the list of oracles shown
|
6
|
+
- display red warning containing the issue and the "certainty"
|
7
|
+
|
8
|
+
>nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6 --rounds 8
|
9
|
+
|
10
|
+
Availability of node <0x693369781001bAC65F653856d0C00fA62129F407> from epoch 4 to epoch 6 on 8 rounds:
|
11
|
+
Oracle #1:
|
12
|
+
Address: 0xai_AleLPKqUHV-iPc-76-rUvDkRWW4dFMIGKW1xFVcy65nH
|
13
|
+
ETH Addr: 0xE486F0d594e9F26931fC10c29E6409AEBb7b5144
|
14
|
+
Alias: nen-aid01
|
15
|
+
Responses: 3
|
16
|
+
Epochs: [ 4, 5, 6]
|
17
|
+
Avails: [ 3, 254, 127]
|
18
|
+
Cartainty: [0.99, 0.99, 0.99]
|
19
|
+
Oracle #2:
|
20
|
+
Address: 0xai_Amfnbt3N-qg2-qGtywZIPQBTVlAnoADVRmSAsdDhlQ-6
|
21
|
+
ETH Addr: 0x129a21A78EBBA79aE78B8f11d5B57102950c1Fc0
|
22
|
+
Alias: nen-2
|
23
|
+
Responses: 2
|
24
|
+
Epochs: [ 4, 5, 6]
|
25
|
+
Avails: [ 3, 254, 127]
|
26
|
+
Cartainty: [0.99, 0.99, 0.99]
|
27
|
+
Oracle #3: [RED due to not data["result"]["oracle"]["manager"]["valid"]]
|
28
|
+
WARNING: Oracle returned invalid data due to uncertainity
|
29
|
+
Address: 0xai_A-Bn9grkqH1GUMTZUqHNzpX5DA6PqducH9_JKAlBx6YL
|
30
|
+
ETH Addr: 0x93B04EF1152D81A0847C2272860a8a5C70280E14
|
31
|
+
Alias: nen-aid02
|
32
|
+
Responses: 3
|
33
|
+
Epochs: [ 4, 5, 6]
|
34
|
+
Avails: [ 3, 0, 127]
|
35
|
+
Cartainty: [0.99, 0.41, 0.99]
|
36
|
+
|
37
|
+
|
38
|
+
>nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6 --rounds 8
|
39
|
+
|
40
|
+
Availability of node <0x693369781001bAC65F653856d0C00fA62129F407> from epoch 4 to epoch 6 on 8 rounds:
|
41
|
+
Oracle #1:
|
42
|
+
Address: 0xai_AleLPKqUHV-iPc-76-rUvDkRWW4dFMIGKW1xFVcy65nH
|
43
|
+
ETH Addr: 0xE486F0d594e9F26931fC10c29E6409AEBb7b5144
|
44
|
+
Alias: nen-aid01
|
45
|
+
Responses: 3
|
46
|
+
Avails: [3, 254, 127]
|
47
|
+
Oracle #2:
|
48
|
+
Address: 0xai_Amfnbt3N-qg2-qGtywZIPQBTVlAnoADVRmSAsdDhlQ-6
|
49
|
+
ETH Addr: 0x129a21A78EBBA79aE78B8f11d5B57102950c1Fc0
|
50
|
+
Alias: nen-2
|
51
|
+
Responses: 3
|
52
|
+
Avails: [3, 254, 127]
|
53
|
+
Oracle #3:
|
54
|
+
Address: 0xai_A-Bn9grkqH1GUMTZUqHNzpX5DA6PqducH9_JKAlBx6YL
|
55
|
+
ETH Addr: 0x93B04EF1152D81A0847C2272860a8a5C70280E14
|
56
|
+
Alias: nen-aid02
|
57
|
+
Responses: 3
|
58
|
+
Avails: [3, 254, 127]
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
>nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6 --rounds 8
|
63
|
+
[if same oracle returns different avail dump the two confligting json with RED and stop command]
|
64
|
+
|
65
|
+
>nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6 --full
|
66
|
+
[just dump json]
|
67
|
+
|
68
|
+
|
69
|
+
>nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 --start 4 --end 6
|
70
|
+
Availability of node <0x693369781001bAC65F653856d0C00fA62129F407> from epoch 4 to epoch 6:
|
71
|
+
Oracle address: 0xE486F0d594e9F26931fC10c29E6409AEBb7b5144
|
72
|
+
Oracle alias: nen-aid01
|
73
|
+
Oracle report:
|
74
|
+
- Epoch #4: 127 ( 50%)
|
75
|
+
- Epoch #5: 254 (100%)
|
76
|
+
- Epoch #6: 3 ( 1%)
|
77
|
+
|
78
|
+
>nepctl get avail 0x693369781001bAC65F653856d0C00fA62129F407 # assuming current epoch is 10
|
79
|
+
Availability of node <0x693369781001bAC65F653856d0C00fA62129F407> from epoch 1 to epoch 9:
|
80
|
+
Oracle address: 0xE486F0d594e9F26931fC10c29E6409AEBb7b5144
|
81
|
+
Oracle alias: nen-aid01
|
82
|
+
Oracle report:
|
83
|
+
- Epoch #1: 127 ( 50%)
|
84
|
+
- Epoch #2: 127 ( 50%)
|
85
|
+
- Epoch #3: 127 ( 50%)
|
86
|
+
- Epoch #4: 127 ( 50%)
|
87
|
+
- Epoch #5: 254 (100%)
|
88
|
+
- Epoch #6: 3 ( 1%)
|
89
|
+
- Epoch #7: 64 ( 25%)
|
90
|
+
- Epoch #8: 33 ( 13%)
|
91
|
+
- Epoch #9: 254 (100%)
|
92
|
+
|
93
|
+
|
94
|
+
TODO: (future)
|
95
|
+
- check ETH signature of the oracle data
|
96
|
+
|
97
|
+
"""
|
98
|
+
import requests
|
99
|
+
|
100
|
+
from naeural_client.utils.config import log_with_color
|
101
|
+
from naeural_client import Logger
|
102
|
+
from naeural_client.bc import DefaultBlockEngine
|
103
|
+
from naeural_client.utils.oracle_sync.multiple_requests import oracle_tester_init, handle_command_results
|
104
|
+
|
105
|
+
"""
|
106
|
+
TODOs:
|
107
|
+
- test NEPCTL command in CLI too
|
108
|
+
- check ETH signature of the oracle data
|
109
|
+
"""
|
110
|
+
|
111
|
+
|
112
|
+
def _check_response(data):
|
113
|
+
res = True
|
114
|
+
log = Logger("NEPCTL", base_folder=".", app_folder="_local_cache", silent=True)
|
115
|
+
bc = DefaultBlockEngine(name='test', log=log)
|
116
|
+
print(bc.address)
|
117
|
+
return res
|
118
|
+
|
119
|
+
def get_availability(args):
|
120
|
+
"""
|
121
|
+
This function is used to get the availability of the node.
|
122
|
+
|
123
|
+
Parameters
|
124
|
+
----------
|
125
|
+
args : argparse.Namespace
|
126
|
+
Arguments passed to the function.
|
127
|
+
"""
|
128
|
+
|
129
|
+
node = args.node
|
130
|
+
start = args.start or 1
|
131
|
+
end = args.end
|
132
|
+
full = args.json
|
133
|
+
rounds = args.rounds or 1
|
134
|
+
if str(rounds).isnumeric() and int(rounds) > 0:
|
135
|
+
rounds = int(rounds)
|
136
|
+
else:
|
137
|
+
log_with_color("`rounds` must be a positive integer. Setting rounds to 1.", color='r')
|
138
|
+
rounds = 1
|
139
|
+
# endif rounds
|
140
|
+
|
141
|
+
if isinstance(rounds, int) and rounds > 10:
|
142
|
+
log_with_color("Rounds exceed the maximum limit of 10. Setting rounds to 10.", color='r')
|
143
|
+
rounds = min(rounds, 10)
|
144
|
+
|
145
|
+
if full:
|
146
|
+
if rounds > 1:
|
147
|
+
log_with_color("Cannot generate full JSON oracle network output in 'rounds' mode.", color='r')
|
148
|
+
full = False
|
149
|
+
# endif full
|
150
|
+
|
151
|
+
tester = oracle_tester_init()
|
152
|
+
log_with_color("Checking {}availability of node <{}> from {} to {}.".format(
|
153
|
+
"(DEBUG MODE) " if rounds > 1 else "", node, start,
|
154
|
+
end if end else "last epoch"
|
155
|
+
), color='b'
|
156
|
+
)
|
157
|
+
res = tester.execute_command(
|
158
|
+
node_eth_addr=node,
|
159
|
+
start=start,
|
160
|
+
end=end,
|
161
|
+
rounds=rounds,
|
162
|
+
debug=full
|
163
|
+
)
|
164
|
+
handle_command_results(res)
|
165
|
+
return
|
@@ -0,0 +1,585 @@
|
|
1
|
+
"""
|
2
|
+
TODO: add signature check for the oracle data
|
3
|
+
|
4
|
+
"""
|
5
|
+
|
6
|
+
import requests
|
7
|
+
import time
|
8
|
+
import json
|
9
|
+
from collections import defaultdict
|
10
|
+
|
11
|
+
from naeural_client import Logger
|
12
|
+
from naeural_client.bc import DefaultBlockEngine
|
13
|
+
from naeural_client.utils.config import log_with_color
|
14
|
+
|
15
|
+
|
16
|
+
class OracleTesterConstants:
|
17
|
+
BASE_URL = "https://naeural-oracle.ngrok.app"
|
18
|
+
TEST_ENDPOINT = "/node_epochs_range"
|
19
|
+
CURRENT_EPOCH_ENDPOINT = "/current_epoch"
|
20
|
+
ACTIVE_NODES_ENDPOINT = "/active_nodes_list"
|
21
|
+
DEFAULT_INTERVAL_SECONDS = 5
|
22
|
+
DEFAULT_COMMAND_INTERVAL_SECONDS = 1
|
23
|
+
MAX_REQUEST_ROUNDS = 10
|
24
|
+
FREQUENCY = "frequency"
|
25
|
+
ORACLE_DATA = "oracle_data"
|
26
|
+
|
27
|
+
|
28
|
+
ct = OracleTesterConstants
|
29
|
+
|
30
|
+
|
31
|
+
class OracleTester:
|
32
|
+
def __init__(
|
33
|
+
self, bce, log,
|
34
|
+
max_requests_rounds=ct.MAX_REQUEST_ROUNDS,
|
35
|
+
interval_seconds=ct.DEFAULT_INTERVAL_SECONDS
|
36
|
+
):
|
37
|
+
self.bc = bce
|
38
|
+
self.log = log
|
39
|
+
self.BASE_URL = ct.BASE_URL
|
40
|
+
self.TEST_ENDPOINT = ct.TEST_ENDPOINT
|
41
|
+
self.CURRENT_EPOCH_ENDPOINT = ct.CURRENT_EPOCH_ENDPOINT
|
42
|
+
self.ACTIVE_NODES_ENDPOINT = ct.ACTIVE_NODES_ENDPOINT
|
43
|
+
self.request_rounds = 0
|
44
|
+
self.max_request_rounds = max_requests_rounds
|
45
|
+
self.interval_seconds = interval_seconds
|
46
|
+
|
47
|
+
self.node_addr_to_alias = {}
|
48
|
+
self.alias_to_node_addr = {}
|
49
|
+
self.node_eth_addr_to_alias = {}
|
50
|
+
self.alias_to_node_eth_addr = {}
|
51
|
+
self.node_addr_to_node_eth_addr = {}
|
52
|
+
self.node_eth_addr_to_node_addr = {}
|
53
|
+
return
|
54
|
+
|
55
|
+
"""UTILS"""
|
56
|
+
if True:
|
57
|
+
def maybe_register_node(self, node_addr: str, eth_address: str, alias: str = None):
|
58
|
+
if node_addr is None:
|
59
|
+
return
|
60
|
+
self.node_addr_to_alias[node_addr] = alias
|
61
|
+
self.node_eth_addr_to_alias[eth_address] = alias
|
62
|
+
|
63
|
+
self.node_addr_to_node_eth_addr[node_addr] = eth_address
|
64
|
+
self.node_eth_addr_to_node_addr[eth_address] = node_addr
|
65
|
+
|
66
|
+
if alias is None:
|
67
|
+
return
|
68
|
+
|
69
|
+
self.alias_to_node_addr[alias] = node_addr
|
70
|
+
self.alias_to_node_eth_addr[alias] = eth_address
|
71
|
+
return
|
72
|
+
|
73
|
+
def make_request(self, request_url, request_kwargs=None, debug=False):
|
74
|
+
request_kwargs = request_kwargs or {}
|
75
|
+
try:
|
76
|
+
if debug:
|
77
|
+
self.P(f"Making request to {request_url} with kwargs: {request_kwargs}")
|
78
|
+
response = requests.get(request_url, params=request_kwargs)
|
79
|
+
response.raise_for_status() # Raise an HTTPError if the status is not 2xx
|
80
|
+
return response.json() # Assuming the response is JSON
|
81
|
+
except requests.RequestException as e:
|
82
|
+
self.P(f"Request failed: {e}")
|
83
|
+
return None
|
84
|
+
|
85
|
+
def done(self, rounds=None):
|
86
|
+
if rounds is not None:
|
87
|
+
return self.request_rounds >= rounds
|
88
|
+
return self.request_rounds >= self.max_request_rounds
|
89
|
+
|
90
|
+
def P(self, msg, **kwargs):
|
91
|
+
self.log.P(msg, **kwargs)
|
92
|
+
return
|
93
|
+
|
94
|
+
def frequency_dict_to_str(self, freq_dict):
|
95
|
+
return '\n'.join([
|
96
|
+
f"\t{self.node_addr_to_alias.get(node_addr)} ({node_addr}): {freq}"
|
97
|
+
for node_addr, freq in freq_dict.items()
|
98
|
+
])
|
99
|
+
"""END UTILS"""
|
100
|
+
|
101
|
+
"""RESPONSE HANDLING"""
|
102
|
+
if True:
|
103
|
+
def compute_epochs_availability_and_certainty(self, result: dict):
|
104
|
+
"""
|
105
|
+
Compute the availability and certainty for a given result extracted from the response.
|
106
|
+
Parameters
|
107
|
+
----------
|
108
|
+
result : dict
|
109
|
+
The result extracted from the response.
|
110
|
+
|
111
|
+
Returns
|
112
|
+
-------
|
113
|
+
tuple
|
114
|
+
A tuple containing the availability and certainty values.
|
115
|
+
"""
|
116
|
+
epoch_ids = result.get("epochs")
|
117
|
+
epoch_vals = result.get("epochs_vals")
|
118
|
+
dict_certainty = result.get("oracle", {}).get("manager", {}).get("certainty", {})
|
119
|
+
is_valid = result.get("oracle", {}).get("manager", {}).get("valid", False)
|
120
|
+
|
121
|
+
current_epochs, current_avails, current_cert = [], [], []
|
122
|
+
for epoch_id, epoch_val in zip(epoch_ids, epoch_vals):
|
123
|
+
current_epochs.append(epoch_id)
|
124
|
+
current_avails.append(epoch_val)
|
125
|
+
current_cert.append(dict_certainty.get(str(epoch_id), 0))
|
126
|
+
# endfor epochs
|
127
|
+
return current_epochs, current_avails, current_cert, is_valid
|
128
|
+
|
129
|
+
def handle_server_data(
|
130
|
+
self, oracle_stats_dict: dict,
|
131
|
+
sender: str, sender_eth_addr: str, sender_node_alias: str,
|
132
|
+
result: dict
|
133
|
+
):
|
134
|
+
"""
|
135
|
+
Handle the data received from the oracle server.
|
136
|
+
|
137
|
+
Parameters
|
138
|
+
----------
|
139
|
+
oracle_stats_dict : dict
|
140
|
+
The dictionary containing the oracle data received so far.
|
141
|
+
sender : str
|
142
|
+
The address of the sender.
|
143
|
+
sender_eth_addr : str
|
144
|
+
The ETH address of the sender.
|
145
|
+
sender_node_alias : str
|
146
|
+
The alias of the sender.
|
147
|
+
result : dict
|
148
|
+
The result extracted from the response.
|
149
|
+
"""
|
150
|
+
if sender not in oracle_stats_dict:
|
151
|
+
oracle_stats_dict[sender] = {
|
152
|
+
'addr': sender,
|
153
|
+
'eth_addr': sender_eth_addr,
|
154
|
+
'alias': sender_node_alias,
|
155
|
+
'errors': []
|
156
|
+
}
|
157
|
+
# endif first time for this sender
|
158
|
+
current_stats = oracle_stats_dict[sender]
|
159
|
+
current_epochs, current_avails, current_certs, is_valid = self.compute_epochs_availability_and_certainty(result)
|
160
|
+
stats_epochs = current_stats.get("epochs", None)
|
161
|
+
stats_avails = current_stats.get("avails", None)
|
162
|
+
stats_certs = current_stats.get("certs", None)
|
163
|
+
stats_is_valid = current_stats.get("is_valid", None)
|
164
|
+
mismatches = []
|
165
|
+
if stats_epochs is not None and current_epochs != stats_epochs:
|
166
|
+
mismatches.append(f"epochs: {current_epochs} != {stats_epochs}")
|
167
|
+
# endif check for mismatch
|
168
|
+
if stats_avails is not None and current_avails != stats_avails:
|
169
|
+
mismatches.append(f"avails: {current_avails} != {stats_avails}")
|
170
|
+
# endif check for mismatch
|
171
|
+
if stats_certs is not None and current_certs != stats_certs:
|
172
|
+
mismatches.append(f"certainty: {current_certs} != {stats_certs}")
|
173
|
+
# endif check for mismatch
|
174
|
+
if stats_is_valid is not None and is_valid != stats_is_valid:
|
175
|
+
mismatches.append(f"validity: {is_valid} != {stats_is_valid}")
|
176
|
+
# endif check for mismatch
|
177
|
+
|
178
|
+
if len(mismatches) > 0:
|
179
|
+
current_stats["errors"].append(f"Round {self.request_rounds}: {', '.join(mismatches)}")
|
180
|
+
else:
|
181
|
+
# Valid data received
|
182
|
+
if stats_epochs is None:
|
183
|
+
current_stats["epochs"] = current_epochs
|
184
|
+
if stats_avails is None:
|
185
|
+
current_stats["avails"] = current_avails
|
186
|
+
if stats_certs is None:
|
187
|
+
current_stats["certs"] = current_certs
|
188
|
+
if stats_is_valid is None:
|
189
|
+
current_stats["is_valid"] = is_valid
|
190
|
+
# endif valid data received
|
191
|
+
return
|
192
|
+
|
193
|
+
def add_to_stats(
|
194
|
+
self,
|
195
|
+
stats_dict: dict,
|
196
|
+
response: dict,
|
197
|
+
node_data: dict,
|
198
|
+
debug=False
|
199
|
+
):
|
200
|
+
node_eth_addr = node_data["eth_address"]
|
201
|
+
node_addr = node_data.get("address")
|
202
|
+
node_alias = node_data.get("alias")
|
203
|
+
self.maybe_register_node(node_addr=node_addr, eth_address=node_eth_addr, alias=node_alias)
|
204
|
+
result = response.get("result")
|
205
|
+
if not result:
|
206
|
+
return
|
207
|
+
sender = result.get("EE_SENDER")
|
208
|
+
sender_eth_addr = result.get("EE_ETH_SENDER")
|
209
|
+
sender_node_alias = result.get("server_alias")
|
210
|
+
self.maybe_register_node(node_addr=sender, eth_address=sender_eth_addr, alias=sender_node_alias)
|
211
|
+
if node_eth_addr not in stats_dict:
|
212
|
+
stats_dict[node_eth_addr] = {
|
213
|
+
**node_data,
|
214
|
+
ct.FREQUENCY: {},
|
215
|
+
ct.ORACLE_DATA: {}
|
216
|
+
}
|
217
|
+
# endif first time for this node
|
218
|
+
stats_dict[node_eth_addr][ct.FREQUENCY][sender] = stats_dict[node_eth_addr][ct.FREQUENCY].get(sender, 0) + 1
|
219
|
+
|
220
|
+
self.handle_server_data(
|
221
|
+
oracle_stats_dict=stats_dict[node_eth_addr][ct.ORACLE_DATA],
|
222
|
+
sender=sender,
|
223
|
+
sender_eth_addr=sender_eth_addr,
|
224
|
+
sender_node_alias=sender_node_alias,
|
225
|
+
result=result
|
226
|
+
)
|
227
|
+
|
228
|
+
return stats_dict
|
229
|
+
"""END RESPONSE HANDLING"""
|
230
|
+
|
231
|
+
def gather(self, nodes, request_kwargs=None, debug=False, rounds=None):
|
232
|
+
"""
|
233
|
+
Gather data from the oracle server for the given nodes.
|
234
|
+
|
235
|
+
Parameters
|
236
|
+
----------
|
237
|
+
nodes : list[dict] or list[str]
|
238
|
+
The list of nodes for which to gather data. Each node can be a dictionary containing the
|
239
|
+
address, eth_address, and alias of the node or a string containing the eth_address of the node.
|
240
|
+
Either way, the eth_address is required.
|
241
|
+
request_kwargs : dict
|
242
|
+
The request kwargs to be used for the request. Default None.
|
243
|
+
debug : bool
|
244
|
+
Whether to enable debug mode or not. If enabled the function will exit after one request round.
|
245
|
+
|
246
|
+
Returns
|
247
|
+
-------
|
248
|
+
tuple
|
249
|
+
A tuple containing the responses and the stats dictionary.
|
250
|
+
"""
|
251
|
+
responses = []
|
252
|
+
request_kwargs = request_kwargs or {}
|
253
|
+
stats_dict = {}
|
254
|
+
self.request_rounds = 0
|
255
|
+
while not self.done(rounds):
|
256
|
+
try:
|
257
|
+
self.P(f'Starting request round {self.request_rounds + 1} for {len(nodes)} nodes...')
|
258
|
+
current_url = self.BASE_URL + self.TEST_ENDPOINT
|
259
|
+
# TODO: maybe shuffle the nodes list in order to avoid
|
260
|
+
# the same order of requests in each round
|
261
|
+
# relevant if the number of nodes is divisible by the number of oracles.
|
262
|
+
for node_data in nodes:
|
263
|
+
if isinstance(node_data, str):
|
264
|
+
node_data = {"eth_address": node_data}
|
265
|
+
# endif only eth address provided
|
266
|
+
eth_addr = node_data.get("eth_address", "N/A")
|
267
|
+
node_alias = node_data.get("alias", eth_addr)
|
268
|
+
node_addr = node_data.get("address", eth_addr)
|
269
|
+
self.P(f'\tRequesting data for {node_alias}...')
|
270
|
+
current_kwargs = {
|
271
|
+
"eth_node_addr": eth_addr,
|
272
|
+
**request_kwargs
|
273
|
+
}
|
274
|
+
response = self.make_request(current_url, request_kwargs=current_kwargs, debug=debug)
|
275
|
+
if response:
|
276
|
+
responses.append(response)
|
277
|
+
str_sender = response.get("node_addr")
|
278
|
+
self.P(f"Received response from {str_sender} with keys: {response.get('result').keys()}")
|
279
|
+
stats_dict = self.add_to_stats(
|
280
|
+
stats_dict=stats_dict,
|
281
|
+
response=response,
|
282
|
+
node_data=node_data,
|
283
|
+
debug=debug
|
284
|
+
)
|
285
|
+
if debug:
|
286
|
+
self.P(f'Full response: {response}')
|
287
|
+
else:
|
288
|
+
self.P(f"Request failed for {node_data['alias']}")
|
289
|
+
# endfor nodes
|
290
|
+
except Exception as e:
|
291
|
+
self.P(f"Request failed: {e}")
|
292
|
+
self.request_rounds += 1
|
293
|
+
if debug:
|
294
|
+
self.P(f'Debug mode was enabled. Exiting after one request round.')
|
295
|
+
break
|
296
|
+
time.sleep(self.interval_seconds)
|
297
|
+
# endwhile
|
298
|
+
self.P(f'Finished gathering data for {len(nodes)} nodes and {self.max_request_rounds}.')
|
299
|
+
return responses, stats_dict
|
300
|
+
|
301
|
+
def get_current_epoch(self):
|
302
|
+
epoch_url = self.BASE_URL + self.CURRENT_EPOCH_ENDPOINT
|
303
|
+
response = self.make_request(epoch_url)
|
304
|
+
if response:
|
305
|
+
return response.get("result", {}).get("current_epoch", 1)
|
306
|
+
return None
|
307
|
+
|
308
|
+
def get_active_nodes(self):
|
309
|
+
active_nodes_url = self.BASE_URL + self.ACTIVE_NODES_ENDPOINT
|
310
|
+
response = self.make_request(active_nodes_url)
|
311
|
+
result = []
|
312
|
+
if response:
|
313
|
+
nodes = response.get("result", {}).get("nodes", {})
|
314
|
+
for node_addr, node_data in nodes.items():
|
315
|
+
eth_addr = node_data.get("eth_addr", None)
|
316
|
+
alias = node_data.get("alias", None)
|
317
|
+
if eth_addr is not None:
|
318
|
+
result.append({
|
319
|
+
"address": node_addr,
|
320
|
+
"eth_address": eth_addr,
|
321
|
+
"alias": alias
|
322
|
+
})
|
323
|
+
# endif eth address is not None
|
324
|
+
# endfor each node
|
325
|
+
# endif response
|
326
|
+
return result
|
327
|
+
|
328
|
+
"""LOGGING"""
|
329
|
+
if True:
|
330
|
+
def check_data(self, oracle_data: dict, node_eth_addr: str):
|
331
|
+
valid, msg = True, ""
|
332
|
+
if oracle_data is None:
|
333
|
+
# If oracle_data is None, either the address was invalid or the servers are down.
|
334
|
+
# Will attempt request for current epoch to check the servers status.
|
335
|
+
current_epoch = self.get_current_epoch()
|
336
|
+
valid = False
|
337
|
+
if current_epoch is None:
|
338
|
+
msg = f"Failed to get the current epoch. No oracle available. Please try again later."
|
339
|
+
else:
|
340
|
+
msg = f"No data available for {node_eth_addr}. Please check the address or contact support."
|
341
|
+
# endif servers available
|
342
|
+
# endif oracle_data is None
|
343
|
+
return valid, msg
|
344
|
+
|
345
|
+
def get_availability_str_for_one_round(
|
346
|
+
self, node_eth_addr: str, start: int, end: int, stats_dict: dict
|
347
|
+
):
|
348
|
+
oracle_data = stats_dict.get(node_eth_addr, {}).get(ct.ORACLE_DATA, None)
|
349
|
+
valid, msg = self.check_data(oracle_data, node_eth_addr)
|
350
|
+
if not valid:
|
351
|
+
return msg
|
352
|
+
|
353
|
+
msg = f'Availability for <{node_eth_addr}> from epoch {start} to epoch {end}:\n'
|
354
|
+
sender_addr = list(oracle_data.keys())[0]
|
355
|
+
sender_data = oracle_data[sender_addr]
|
356
|
+
oracle_addr = sender_data.get("addr", None)
|
357
|
+
oracle_addr_eth = sender_data.get("eth_addr", None)
|
358
|
+
oracle_alias = sender_data.get("alias", None)
|
359
|
+
msg += f' Oracle address: {oracle_addr}\n'
|
360
|
+
msg += f' Oracle ETH addr: {oracle_addr_eth}\n'
|
361
|
+
msg += f' Oracle alias: {oracle_alias}\n'
|
362
|
+
msg += f' Oracle responses:\n'
|
363
|
+
epochs = sender_data.get("epochs", None)
|
364
|
+
avails = sender_data.get("avails", None)
|
365
|
+
certs = sender_data.get("certs", None)
|
366
|
+
if epochs is None or avails is None or certs is None:
|
367
|
+
msg = f"No data available for {node_eth_addr}. Please check the address or contact support."
|
368
|
+
else:
|
369
|
+
for epoch, avail, cert in zip(epochs, avails, certs):
|
370
|
+
msg += f" - Epoch {f'#{epoch}':>4}: {avail:3} ({cert * 100:5.1f}%)\n"
|
371
|
+
# endif data available
|
372
|
+
return msg
|
373
|
+
|
374
|
+
def get_availability_str_for_multiple_rounds(
|
375
|
+
self,
|
376
|
+
node_eth_addr: str, start: int, end: int, stats_dict: dict, rounds: int
|
377
|
+
):
|
378
|
+
oracle_data = stats_dict.get(node_eth_addr, {}).get(ct.ORACLE_DATA, None)
|
379
|
+
valid, msg = self.check_data(oracle_data, node_eth_addr)
|
380
|
+
if not valid:
|
381
|
+
return [msg]
|
382
|
+
msg_list = [f'Availability for <{node_eth_addr}> from epoch {start} to epoch {end} on {rounds} rounds:\n']
|
383
|
+
frequencies = stats_dict.get(node_eth_addr, {}).get(ct.FREQUENCY, {})
|
384
|
+
it = 0
|
385
|
+
for sender, sender_data in oracle_data.items():
|
386
|
+
curr_msg = f'\tOracle #{it + 1}:\n'
|
387
|
+
is_valid = sender_data.get("is_valid", False)
|
388
|
+
if len(sender_data["errors"]) > 0:
|
389
|
+
is_valid = False
|
390
|
+
curr_msg += f'\t\t Error!! Same oracle returned different data in different rounds:\n'
|
391
|
+
for error in sender_data["errors"]:
|
392
|
+
curr_msg += f'\t\t\t {error}\n'
|
393
|
+
# endfor errors
|
394
|
+
else:
|
395
|
+
# No errors
|
396
|
+
if not is_valid:
|
397
|
+
curr_msg += '\t\t WARNING: Oracle returned invalid data due to uncertainity\n'
|
398
|
+
# endif uncertainty
|
399
|
+
# endif errors
|
400
|
+
color = None if is_valid else 'r'
|
401
|
+
str_epochs = ' '.join([f'{epoch:4}' for epoch in sender_data["epochs"]])
|
402
|
+
str_avails = ' '.join([f'{avail:4}' for avail in sender_data["avails"]])
|
403
|
+
str_certs = ' '.join([f'{cert:4.2f}' for cert in sender_data["certs"]])
|
404
|
+
curr_msg += f'\t\t Address: {sender_data["addr"]}\n'
|
405
|
+
curr_msg += f'\t\t ETH Addr: {sender_data["eth_addr"]}\n'
|
406
|
+
curr_msg += f'\t\t Alias: {sender_data["alias"]}\n'
|
407
|
+
curr_msg += f'\t\t Responses: {frequencies.get(sender, 0)}\n'
|
408
|
+
curr_msg += f'\t\t Epochs: {str_epochs}\n'
|
409
|
+
curr_msg += f'\t\t Avails: {str_avails}\n'
|
410
|
+
curr_msg += f'\t\t Certainty: {str_certs}\n'
|
411
|
+
msg_list.append((curr_msg, color))
|
412
|
+
it += 1
|
413
|
+
# endfor oracles
|
414
|
+
return msg_list
|
415
|
+
"""END LOGGING"""
|
416
|
+
|
417
|
+
def execute_command(
|
418
|
+
self, node_eth_addr: str, start: int = 1,
|
419
|
+
end: int = None, debug: bool = False,
|
420
|
+
rounds: int = 1
|
421
|
+
):
|
422
|
+
"""
|
423
|
+
Execute the command to get the availability of the node on a given epochs interval.
|
424
|
+
This can also be used to get the JSON response from the server(with the use of debug=True).
|
425
|
+
In case of debug mode, the rounds parameter is ignored(will default to 1).
|
426
|
+
In case of multiple rounds, a delay of interval_seconds will be added between each request.
|
427
|
+
Parameters
|
428
|
+
----------
|
429
|
+
node_eth_addr : str
|
430
|
+
The ETH address of the node for which the command will be executed.
|
431
|
+
start : int
|
432
|
+
The starting epoch for the request.
|
433
|
+
end : int
|
434
|
+
The ending epoch for the request. If None, the current epoch will be used.
|
435
|
+
debug : bool
|
436
|
+
Whether to enable debug mode or not. If enabled the rounds parameter is ignored and the
|
437
|
+
function will return the JSON response from the server.
|
438
|
+
rounds : int
|
439
|
+
The number of rounds to be executed. Default 1.
|
440
|
+
|
441
|
+
Returns
|
442
|
+
-------
|
443
|
+
dict:
|
444
|
+
The JSON response from the server if in debug mode.
|
445
|
+
The stats summary if not in debug mode.
|
446
|
+
"""
|
447
|
+
rounds = min(rounds, self.max_request_rounds)
|
448
|
+
if end is None:
|
449
|
+
current_epoch = self.get_current_epoch()
|
450
|
+
if current_epoch is None:
|
451
|
+
self.P(f"Failed to get the current epoch. No oracle available. Please try again later.", show=True)
|
452
|
+
return
|
453
|
+
self.P(f'No end epoch provided. Using current epoch: {current_epoch}', show=True)
|
454
|
+
end = self.get_current_epoch() - 1
|
455
|
+
# endif end is None
|
456
|
+
request_kwargs = {
|
457
|
+
"start_epoch": start,
|
458
|
+
"end_epoch": end
|
459
|
+
}
|
460
|
+
responses, stats = self.gather(
|
461
|
+
nodes=[node_eth_addr],
|
462
|
+
request_kwargs=request_kwargs,
|
463
|
+
rounds=1 if debug else rounds,
|
464
|
+
)
|
465
|
+
if debug:
|
466
|
+
return responses[0]
|
467
|
+
if rounds > 1:
|
468
|
+
return self.get_availability_str_for_multiple_rounds(
|
469
|
+
node_eth_addr=node_eth_addr, start=start, end=end, stats_dict=stats, rounds=rounds
|
470
|
+
)
|
471
|
+
|
472
|
+
return self.get_availability_str_for_one_round(
|
473
|
+
node_eth_addr=node_eth_addr, start=start, end=end, stats_dict=stats
|
474
|
+
)
|
475
|
+
|
476
|
+
# endclass OracleTester
|
477
|
+
|
478
|
+
def handle_command_results(res):
|
479
|
+
if isinstance(res, dict):
|
480
|
+
log_with_color(json.dumps(res, indent=2), color='w')
|
481
|
+
elif isinstance(res, list):
|
482
|
+
for msg_data in res:
|
483
|
+
if isinstance(msg_data, tuple):
|
484
|
+
log_with_color(msg_data[0], color=msg_data[1])
|
485
|
+
else:
|
486
|
+
log_with_color(msg_data, color='w')
|
487
|
+
# endfor each message
|
488
|
+
else:
|
489
|
+
log_with_color(res, color='w')
|
490
|
+
return
|
491
|
+
|
492
|
+
def oracle_tester_init(silent=True, **kwargs):
|
493
|
+
log = Logger("R1CTL", base_folder=".", app_folder="_local_cache", silent=silent)
|
494
|
+
bc = DefaultBlockEngine(name='R1CTL', log=log)
|
495
|
+
tester = OracleTester(
|
496
|
+
bce=bc,
|
497
|
+
log=log,
|
498
|
+
**kwargs
|
499
|
+
)
|
500
|
+
return tester
|
501
|
+
|
502
|
+
def test_commands():
|
503
|
+
tester = oracle_tester_init()
|
504
|
+
start = 78
|
505
|
+
end = 85
|
506
|
+
node_eth_addr = "<node_eth_address>"
|
507
|
+
|
508
|
+
# Single round
|
509
|
+
tester.P(f'Test single round: Epochs {start} to {end}', show=True)
|
510
|
+
res = tester.execute_command(node_eth_addr=node_eth_addr, start=start, end=end)
|
511
|
+
handle_command_results(res)
|
512
|
+
|
513
|
+
# Multiple rounds
|
514
|
+
tester.P(f'Test multiple rounds: Epochs {start} to {end}', show=True)
|
515
|
+
res = tester.execute_command(node_eth_addr=node_eth_addr, start=start, end=end, rounds=5)
|
516
|
+
handle_command_results(res)
|
517
|
+
|
518
|
+
# Debug mode
|
519
|
+
tester.P(f'Test debug mode: Epochs {start} to {end}', show=True)
|
520
|
+
res = tester.execute_command(node_eth_addr=node_eth_addr, start=80, end=85, debug=True)
|
521
|
+
handle_command_results(res)
|
522
|
+
return
|
523
|
+
|
524
|
+
def oracle_test(N=10):
|
525
|
+
import random
|
526
|
+
|
527
|
+
random.seed(42)
|
528
|
+
|
529
|
+
tester = oracle_tester_init(
|
530
|
+
silent=True,
|
531
|
+
interval_seconds=0.2,
|
532
|
+
)
|
533
|
+
current_epoch = tester.get_current_epoch()
|
534
|
+
if current_epoch is None:
|
535
|
+
current_epoch = 96
|
536
|
+
nodes = tester.get_active_nodes()
|
537
|
+
rounds = 5
|
538
|
+
max_epochs = 10
|
539
|
+
max_nodes = 5
|
540
|
+
|
541
|
+
for i in range(N):
|
542
|
+
node_list = random.sample(nodes, min(len(nodes), max_nodes))
|
543
|
+
start = random.randint(1, current_epoch - 1)
|
544
|
+
end = random.randint(start, current_epoch - 1)
|
545
|
+
end = min(end, start + max_epochs - 1)
|
546
|
+
|
547
|
+
tester.P(f'Test {i + 1}/{N}: Epochs {start} to {end} with {rounds} rounds:', show=True)
|
548
|
+
res = tester.gather(
|
549
|
+
nodes=node_list,
|
550
|
+
request_kwargs={
|
551
|
+
"start_epoch": start,
|
552
|
+
"end_epoch": end
|
553
|
+
},
|
554
|
+
rounds=rounds
|
555
|
+
)
|
556
|
+
|
557
|
+
for node_data in node_list:
|
558
|
+
msg_list = tester.get_availability_str_for_multiple_rounds(
|
559
|
+
node_eth_addr=node_data['eth_address'], start=start, end=end, stats_dict=res[1], rounds=rounds
|
560
|
+
)
|
561
|
+
for msg_data in msg_list:
|
562
|
+
if isinstance(msg_data, tuple):
|
563
|
+
tester.P(msg_data[0], color=msg_data[1], show=True)
|
564
|
+
else:
|
565
|
+
tester.P(msg_data, show=True)
|
566
|
+
# endfor each message
|
567
|
+
# endfor each node
|
568
|
+
tester.P(f'Finished test {i + 1}/{N}', show=True)
|
569
|
+
# endfor each test
|
570
|
+
return
|
571
|
+
|
572
|
+
# Main loop
|
573
|
+
def main():
|
574
|
+
TEST_COMMANDS = False
|
575
|
+
TEST_ORACLE = True
|
576
|
+
if TEST_COMMANDS:
|
577
|
+
test_commands()
|
578
|
+
|
579
|
+
if TEST_ORACLE:
|
580
|
+
oracle_test(5)
|
581
|
+
return
|
582
|
+
|
583
|
+
|
584
|
+
if __name__ == "__main__":
|
585
|
+
main()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: naeural_client
|
3
|
-
Version: 2.6.
|
3
|
+
Version: 2.6.26
|
4
4
|
Summary: `naeural_client` is the Python SDK required for client app development for the Naeural Edge Protocol Edge Protocol framework
|
5
5
|
Project-URL: Homepage, https://github.com/NaeuralEdgeProtocol/naeural_client
|
6
6
|
Project-URL: Bug Tracker, https://github.com/NaeuralEdgeProtocol/naeural_client/issues
|
@@ -38,6 +38,12 @@ Key functionalities of the Ratio1 SDK include:
|
|
38
38
|
|
39
39
|
Unlike the Ratio1 Core Packages, which are intended solely for protocol and ecosystem enhancements and are not meant for standalone installation, the Ratio1 SDK is designed for both client-side development and sending workloads to Ratio1 Edge Nodes, making it an indispensable tool for developers within the ecosystem.
|
40
40
|
|
41
|
+
## The `nepctl` CLI Tool
|
42
|
+
|
43
|
+
Our SDK has a CLI tool called `nepctl` that allows you to interact with the Ratio1 network. You can use it to query nodes, configure the client, and manage nodes directly from the terminal. The `nepctl` tool is a powerful utility that simplifies network interactions and provides a seamless experience for developers.
|
44
|
+
|
45
|
+
For more information on the `nepctl` CLI tool, please refer to the [nepctl](nepctl.md) documentation.
|
46
|
+
|
41
47
|
## Dependencies
|
42
48
|
|
43
49
|
The Ratio1 SDK relies on several key packages to function effectively. These dependencies are automatically managed when installing the SDK via pip:
|
@@ -1,10 +1,10 @@
|
|
1
1
|
naeural_client/__init__.py,sha256=YimqgDbjLuywsf8zCWE0EaUXH4MBUrqLxt0TDV558hQ,632
|
2
|
-
naeural_client/_ver.py,sha256=
|
2
|
+
naeural_client/_ver.py,sha256=v41SsIkuU8mwgkOit-MvKm8l1U2wH1sGEPmEk-6Qf6Q,331
|
3
3
|
naeural_client/base_decentra_object.py,sha256=C4iwZTkhKNBS4VHlJs5DfElRYLo4Q9l1V1DNVSk1fyQ,4412
|
4
4
|
naeural_client/plugins_manager_mixin.py,sha256=X1JdGLDz0gN1rPnTN_5mJXR8JmqoBFQISJXmPR9yvCo,11106
|
5
5
|
naeural_client/base/__init__.py,sha256=hACh83_cIv7-PwYMM3bQm2IBmNqiHw-3PAfDfAEKz9A,259
|
6
6
|
naeural_client/base/distributed_custom_code_presets.py,sha256=cvz5R88P6Z5V61Ce1vHVVh8bOkgXd6gve_vdESDNAsg,2544
|
7
|
-
naeural_client/base/generic_session.py,sha256=
|
7
|
+
naeural_client/base/generic_session.py,sha256=joF01ZoIt6QCSg7hDkg5M8ZCmKubpbQVh-plqi6GA9E,105079
|
8
8
|
naeural_client/base/instance.py,sha256=kcZJmjLBtx8Bjj_ysIOx1JmLA-qSpG7E28j5rq6IYus,20444
|
9
9
|
naeural_client/base/pipeline.py,sha256=b4uNHrEIOlAtw4PGUx20dxwBhDck5__SrVXaHcSi8ZA,58251
|
10
10
|
naeural_client/base/plugin_template.py,sha256=qGaXByd_JZFpjvH9GXNbT7KaitRxIJB6-1IhbKrZjq4,138123
|
@@ -14,15 +14,16 @@ naeural_client/base/webapp_pipeline.py,sha256=QmPLVmhP0CPdi0YuvbZEH4APYz2Amtw3gy
|
|
14
14
|
naeural_client/base/payload/__init__.py,sha256=y8fBI8tG2ObNfaXFWjyWZXwu878FRYj_I8GIbHT4GKE,29
|
15
15
|
naeural_client/base/payload/payload.py,sha256=x-au7l67Z_vfn_4R2C_pjZCaFuUVXHngJiGOfIAYVdE,2690
|
16
16
|
naeural_client/bc/__init__.py,sha256=FQj23D1PrY06NUOARiKQi4cdj0-VxnoYgYDEht8lpr8,158
|
17
|
-
naeural_client/bc/base.py,sha256=
|
17
|
+
naeural_client/bc/base.py,sha256=NafeKetxDoG86-gNls3FPN1M09nzRUP9unCUzEcXlrk,37706
|
18
18
|
naeural_client/bc/chain.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
naeural_client/bc/ec.py,sha256=qI8l7YqiS4MNftlx-tF7IZUswrSeQc7KMn5OZ0fEaJs,23370
|
20
20
|
naeural_client/certs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
21
|
naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt,sha256=y-6io0tseyx9-a4Pmde1z1gPULtJNSYUpG_YFkYaMKU,1337
|
22
22
|
naeural_client/cli/README.md,sha256=WPdI_EjzAbUW1aPyj1sSR8rLydcJKZtoiaEtklQrjHo,74
|
23
23
|
naeural_client/cli/cli.py,sha256=c0ZavVwArQEGv8tueTnz3aGKTuUe6Sqmm5oTUFUVIUE,3816
|
24
|
-
naeural_client/cli/cli_commands.py,sha256=
|
25
|
-
naeural_client/cli/nodes.py,sha256=
|
24
|
+
naeural_client/cli/cli_commands.py,sha256=Gfims_TqaBi2QWM-6yxd_B_CETF8mmt7oEDPTg59lcI,2821
|
25
|
+
naeural_client/cli/nodes.py,sha256=QXSniYy3Phr6buoVV-HUDRYuF5YQDUgf6nT6EzDyMJw,4909
|
26
|
+
naeural_client/cli/oracles.py,sha256=g8h8kl3Mu6gU7JCs4a6WSDpvuVDGJgTHJbYJy7Iriw0,5210
|
26
27
|
naeural_client/code_cheker/__init__.py,sha256=pwkdeZGVL16ZA4Qf2mRahEhoOvKhL7FyuQbMFLr1E5M,33
|
27
28
|
naeural_client/code_cheker/base.py,sha256=lT5DRIFO5rqzsMNCmdMRfkAeevmezozehyfgmhnKpuI,19074
|
28
29
|
naeural_client/code_cheker/checker.py,sha256=QWupeM7ToancVIq1tRUxRNUrI8B5l5eoY0kDU4-O5aE,7365
|
@@ -81,8 +82,9 @@ naeural_client/utils/__init__.py,sha256=mAnke3-MeRzz3nhQvhuHqLnpaaCSmDxicd7Ck9uw
|
|
81
82
|
naeural_client/utils/comm_utils.py,sha256=4cS9llRr_pK_3rNgDcRMCQwYPO0kcNU7AdWy_LtMyCY,1072
|
82
83
|
naeural_client/utils/config.py,sha256=v7xHikr6Z5Sbvf3opYeMhYzGWD2pe0HlRwa-aGJzUh8,6323
|
83
84
|
naeural_client/utils/dotenv.py,sha256=_AgSo35n7EnQv5yDyu7C7i0kHragLJoCGydHjvOkrYY,2008
|
84
|
-
naeural_client
|
85
|
-
naeural_client-2.6.
|
86
|
-
naeural_client-2.6.
|
87
|
-
naeural_client-2.6.
|
88
|
-
naeural_client-2.6.
|
85
|
+
naeural_client/utils/oracle_sync/multiple_requests.py,sha256=GLzROGZ0gI4d1PVWgW_JBUYZjEL4LqZvHvwelxDiPW4,20892
|
86
|
+
naeural_client-2.6.26.dist-info/METADATA,sha256=jvfB6kdLKoCkEHGyjhFJnTfmrH9Ya8J4fu4Z68ldclE,12354
|
87
|
+
naeural_client-2.6.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
88
|
+
naeural_client-2.6.26.dist-info/entry_points.txt,sha256=PNdyotDaQBAslZREx5luVyj0kqpQnwNACwkFNTPIHU4,55
|
89
|
+
naeural_client-2.6.26.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
|
90
|
+
naeural_client-2.6.26.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|