FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
validator-node-api/src/validator_node_api/select_node.py at main · orcfax/validator-node-api · GitHub
orcfax
/
validator-node-api
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Pull requests
7
Actions
Security and quality
0
Insights
Additional navigation options
Code
Pull requests
Actions
Security and quality
Insights
Expand file tree
Breadcrumbs
validator-node-api
/
src
/
validator_node_api
/
select_node.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
175 lines (150 loc) · 5.97 KB
Breadcrumbs
validator-node-api
/
src
/
validator_node_api
/
select_node.py
Copy path
File metadata and controls
175 lines (150 loc) · 5.97 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"""Select an Orcfax node from valid sources.
Ideally we can make this as transparent as possible from this module.
"""
import
contextvars
import
logging
import
random
from
dataclasses
import
dataclass
,
field
from
statistics
import
median
from
typing
import
Final
try
:
import
config
import
validation_algorithm
from
helpers
import
orcfax_round
from
validator_types
import
NodeData
,
NodeError
except
ModuleNotFoundError
:
try
:
from
src
.
validator_node_api
import
config
,
validation_algorithm
from
src
.
validator_node_api
.
helpers
import
orcfax_round
from
src
.
validator_node_api
.
validator_types
import
NodeData
,
NodeError
except
ModuleNotFoundError
:
from
validator_node_api
import
config
,
validation_algorithm
from
validator_node_api
.
helpers
import
orcfax_round
from
validator_node_api
.
validator_types
import
NodeData
,
NodeError
logger
=
logging
.
getLogger
(
__name__
)
# Minimum number of nodes needed to calculate median.
MIN_NODES_MEDIAN
:
Final
[
int
]
=
3
# Rounding for even number of nodes; decimal places.
CALCULATED_ROUNDING_VALUE
:
Final
[
int
]
=
4
@
dataclass
class
ValidationSelectionObject
:
"""Object that captures the selection of an Orcfax node during
validation.
This object is designed to be statistical in nature, helping us to
audit the validator's capabilities.
"""
# Number of nodes expected by the validator.
no_nodes
:
str
=
field
(
default
=
0
)
# All nodes in contention after validation.
contending_nodes_all
:
list
[
str
]
=
field
(
default_factory
=
list
)
# All nodes in contention after selecting the median.
contending_nodes_final
:
list
[
str
]
=
field
(
default_factory
=
list
)
# Median value selected.
median_value
:
float
=
field
(
default
=
0.0
)
# Final node selection.
selected_node
:
NodeData
=
field
(
default
=
None
)
# Minimum contention process used = True, alt. random = False, e.g.
# when the number of nodes is too low.
minimum_contention
:
bool
=
False
def
__str__
(
self
):
return
(
f"Nodes used by validator:
{
self
.
no_nodes
}
:: "
f"all valid:
{
len
(
self
.
contending_nodes_all
)
}
:: "
f" median value:
{
self
.
median_value
}
:: "
f"contending median nodes:
{
len
(
self
.
contending_nodes_final
)
}
:: "
f"selected:
{
self
.
selected_node
.
node_id
}
:: "
f"timestamp:
{
self
.
selected_node
.
message
.
timestamp
}
:: "
f"minimum contention threshold:
{
self
.
minimum_contention
}
"
)
def
validation_selection_object
(
# pylint: disable=R0913, R0917
ctx
:
contextvars
.
Context
,
contending_all
:
list
[
str
],
contending_final
:
list
[
str
],
median_value
:
float
,
selected
:
str
,
minimum_contention
:
bool
,
):
"""Construct a validation selection object for return to the caller."""
selection_object
=
ValidationSelectionObject
()
selection_object
.
no_nodes
=
ctx
[
config
.
CTX_NODES_NUMBER
]
selection_object
.
contending_nodes_all
=
contending_all
selection_object
.
contending_nodes_final
=
contending_final
selection_object
.
median_value
=
median_value
selection_object
.
selected_node
=
selected
selection_object
.
minimum_contention
=
minimum_contention
return
selection_object
def
select_median_contending_nodes
(
nodes
:
list
[
NodeData
],
)
->
tuple
[
float
,
list
[
NodeData
]]:
"""Median nodes to be selected from those that are valid and
returned.
If we have an even number of nodes, e.g. 4 instead of 5 or 3, we
round the node values and median to three decimal places in an
attempt to find one matching node to publish the value from.
This approach should enable selection of nodes within a similar
range of one another. If there are bigger outliers then it is
likely a NodeError will be raised and we will need to repeat
validation.
"""
values
=
[
orcfax_round
(
float
(
node
.
message
.
calculated_value
))
for
node
in
nodes
]
logger
.
info
(
"selecting nodes matching median from node values: %s %s"
,
values
,
validation_algorithm
.
VALIDATION_ALGORITHM
,
)
median_value
=
median
(
values
)
median_nodes
=
[]
if
len
(
nodes
)
%
2
==
0
:
logger
.
info
(
"even number of nodes to generate median from, rounding to '%s' decimal places %s"
,
CALCULATED_ROUNDING_VALUE
,
validation_algorithm
.
VALIDATION_ALGORITHM
,
)
rounded_median
=
round
(
median_value
,
CALCULATED_ROUNDING_VALUE
)
for
node
in
nodes
:
if
(
round
(
float
(
node
.
message
.
calculated_value
),
CALCULATED_ROUNDING_VALUE
)
!=
rounded_median
):
continue
median_nodes
.
append
(
node
)
return
rounded_median
,
median_nodes
for
node
in
nodes
:
if
orcfax_round
(
float
(
node
.
message
.
calculated_value
))
!=
orcfax_round
(
median_value
):
continue
median_nodes
.
append
(
node
)
return
median_value
,
median_nodes
def
select_node_from_valid_sources
(
ctx
:
contextvars
.
Context
,
nodes
:
list
[
NodeData
],
)
->
NodeData
:
"""Given a list of valid nodes select the node returning the median
value.
If we have enough nodes, which we should always do, we find the
median value of the valid nodes and then return a random selection
from the remaining number of nodes.
"""
feed
=
nodes
[
0
].
message
.
feed
median_value
,
median_nodes
=
select_median_contending_nodes
(
nodes
)
if
not
median_nodes
:
raise
NodeError
(
f"no nodes returned after attempting to select median:
{
median_value
}
{
validation_algorithm
.
VALIDATION_ALGORITHM
}
(
{
feed
}
)"
)
selected
=
random
.
choice
(
median_nodes
)
selection_obj
=
validation_selection_object
(
ctx
=
ctx
,
contending_all
=
nodes
,
contending_final
=
median_nodes
,
median_value
=
median_value
,
selected
=
selected
,
minimum_contention
=
True
,
)
logger
.
info
(
"%s (median: %s) %s"
,
selection_obj
,
median_value
,
validation_algorithm
.
VALIDATION_ALGORITHM
,
)
return
selection_obj
Back
|
FazBrowse Home
|
New Git URL