-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(consensus): Change meta.score and meta.accumulated_weight to int type #572
base: master
Are you sure you want to change the base?
Conversation
265afc8
to
65ccfdd
Compare
65ccfdd
to
21acfdb
Compare
69098cf
to
fd2bf10
Compare
1b625ad
to
12084a4
Compare
0bf14d9
to
86ce84c
Compare
a48df48
to
83760be
Compare
289dc7f
to
b0d8f5c
Compare
b0d8f5c
to
81b60c9
Compare
8e4ecfe
to
464fb5b
Compare
81a5b71
to
d2e5e2a
Compare
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #572 +/- ##
==========================================
+ Coverage 85.30% 85.36% +0.05%
==========================================
Files 285 284 -1
Lines 22365 22310 -55
Branches 3369 3362 -7
==========================================
- Hits 19078 19044 -34
+ Misses 2612 2593 -19
+ Partials 675 673 -2 ☔ View full report in Codecov by Sentry. |
60c7fa2
to
edc25af
Compare
I haven't reviewed the code yet, but I think we should keep |
hathor/transaction/storage/migrations/change_score_acc_weight_metadata.py
Outdated
Show resolved
Hide resolved
hathor/manager.py
Outdated
# this is the min weight to cause an increase of twice the WEIGHT_TOL, we make sure to generate a template with | ||
# at least this weight (note that the user of the API can set its own weight, the block sumit API will also | ||
# protect agains a weight that is too small but using WEIGHT_TOL instead of 2*WEIGHT_TOL) | ||
min_significant_weight = calculate_min_significant_weight( | ||
parent_block_metadata.score, | ||
2 * self._settings.WEIGHT_TOL | ||
) | ||
weight = max(self.daa.calculate_next_weight(parent_block, timestamp), min_significant_weight) | ||
weight = self.daa.calculate_next_weight(parent_block, timestamp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep calculate_min_significant_weight
for at least one more release. Maybe something like TODO: remove calculate_min_significant_weight in the future
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
hathor/manager.py
Outdated
min_insignificant_weight = calculate_min_significant_weight( | ||
parent_block_metadata.score, | ||
self._settings.WEIGHT_TOL | ||
) | ||
if blk.weight <= min_insignificant_weight: | ||
self.log.warn('submit_block(): insignificant weight? accepted anyway', blk=blk.hash_hex, weight=blk.weight) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep calculate_min_significant_weight
for at least one more release. Maybe something like TODO: remove calculate_min_significant_weight in the future
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
edc25af
to
26376fb
Compare
def weight_to_work(weight: float) -> int: | ||
"""Convert weight to work rounding to the nearest integer.""" | ||
# return int(0.5 + 2**weight) | ||
return int(0.5 + exp(weight * _LN_2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which one has greater precision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made some tests comparing the two methods against Wolphram Alpha (as a quick with high precision), using this type of query: https://www.wolframalpha.com/input?i=Ceil%5B2%5E88.50000000000001%5D
method 1: int(0.5 + 2**weight)
method 2: int(0.5 + exp(weight * _LN_2))
(I also tested math.exp2
and it's exactly the same as 2**weight
)
These are some results for weights that we can expect:
weight | method 1 | method 2 | wolphram |
---|---|---|---|
55 | 36028797018963968 | 36028797018963824 | 36028797018963968 |
55.5 | 50952413380206184 | 50952413380206024 | 50952413380206181 |
60 | 1152921504606846976 | 1152921504606844800 | 1152921504606846976 |
60.001 | 1153720925923479040 | 1153720925923474176 | 1153720925923480963 |
60.00001 | 1152929496077448960 | 1152929496077448192 | 1152929496077446523 |
88 | 309485009821345068724781056 | 309485009821345274883211264 | 309485009821345068724781056 |
88.5 | 437677898240516748033916928 | 437677898240514274132754432 | 437677898240516718115093268 |
88.50000000000001 | 437677898240521008641474560 | 437677898240520527605137408 | 437677898240519751867104856 |
And the errors:
weight | method 1 (err) | method 2 (err) |
---|---|---|
55 | 0 | 144 |
55.5 | 3 | 157 |
60 | 0 | 2176 |
60.001 | 1923 | 6787 |
60.00001 | 2437 | 1669 |
88 | 0 | 206158430208 |
88.5 | 29918823660 | 2443982338836 |
88.50000000000001 | 1256774369704 | 775738032552 |
It doesn't look too conclusive, I think method 1 will favor integer values of weight, but method 2 might favor some other values. I wonder if there's any better method to calculate it while being efficient. I don't think it would be worth to use a multi precision math lib though.
Nice catch! I just did it. Thanks! |
50a01de
to
5322ec4
Compare
@@ -200,7 +201,7 @@ def update_voided_info(self, block: Block) -> None: | |||
common_block = self._find_first_parent_in_best_chain(block) | |||
self.add_voided_by_to_multiple_chains(block, heads, common_block) | |||
|
|||
if score >= best_score + self._settings.WEIGHT_TOL: | |||
if score > best_score: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was the =
removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
62e02ee
to
83a6cfd
Compare
83a6cfd
to
795c414
Compare
@@ -60,26 +61,6 @@ | |||
_ONE_BYTE = 0xFF | |||
|
|||
|
|||
def sum_weights(w1: float, w2: float) -> float: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I won't be able to remove the sum_weights()
in this PR because it is still used in 3 places:
metrics.py
: Seems easy to remove it.difficulty.py
: This is used by merged mining and maybe Jan can help me understand the consequences of changing it. Anyway, it seems feasible.stratum/straum.py
: Seems easy but we need to better assess its impact. I think it's used in the hash rate estimator only. And, to be honest, I don't know if we still use this code.daa.py
: I think we should not change this one, as it might affect the minimum weight of the blocks and invalidate blocks that are on the mainnet. To confirm the consequence, we would need to check it against the mainnet but it seems not worth the risk.
My impression is that, in the end, we will remove the sum_weights()
from (1), (2), and (3) and then we will move its code to the daa.py
and continue using it there.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In difficulty
it is only used to implement a logsum
for function reuse. The following patch ports the exact same math:
diff --git a/hathor/difficulty.py b/hathor/difficulty.py
index 4a8467ef..fa80c124 100644
--- a/hathor/difficulty.py
+++ b/hathor/difficulty.py
@@ -787,8 +787,8 @@ class Weight(float):
Currently is just a proxy to `hathor.transaction.sum_weights`.
"""
- from hathor.transaction import sum_weights
- return Weight(sum_weights(self, other))
+ a, b = self, other if self > other else other, self
+ return Weight(a + log2(1 + 2 ** (b - a)))
def to_u256(self) -> U256:
""" Convert to U256
Since hathor.difficulty
is intended to abstract that kind of values, it can be used in places like hathor.metrics
, hathor.stratum.stratum
and hathor.daa
. That should preserve the exact same operations as before. I can see how hathor.daa
could maybe be refactored but I agree it's not worth to touch it now.
assert abs(best_score - head_meta.score) < 1e-10 | ||
assert isinstance(best_score, (int, float)) | ||
assert best_score == head_meta.score | ||
assert isinstance(best_score, int) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe set the type of best_score: int | None = None
above, and then this could be assert best_score is not None
@@ -200,7 +201,7 @@ def update_voided_info(self, block: Block) -> None: | |||
common_block = self._find_first_parent_in_best_chain(block) | |||
self.add_voided_by_to_multiple_chains(block, heads, common_block) | |||
|
|||
if score >= best_score + self._settings.WEIGHT_TOL: | |||
if score > best_score: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to update the calls to calculate_min_significant_weight()
, or rather, the function itself, right? It's expecting to receive a log_score
, but we're passing the new score
.
def run(self, storage: 'TransactionStorage') -> None: | ||
raise Exception('Cannot migrate your database due to an incompatible change in the metadata. ' | ||
'Please, delete your data folder and use the latest available snapshot or sync ' | ||
'from beginning.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This migration implies we can remove all the other migrations. I think we should do that in another PR before a release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just released #933 which also has a migration, so it would be nice to wait for it too.
def weight_to_work(weight: float) -> int: | ||
"""Convert weight to work rounding to the nearest integer.""" | ||
# return int(0.5 + 2**weight) | ||
return int(0.5 + exp(weight * _LN_2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made some tests comparing the two methods against Wolphram Alpha (as a quick with high precision), using this type of query: https://www.wolframalpha.com/input?i=Ceil%5B2%5E88.50000000000001%5D
method 1: int(0.5 + 2**weight)
method 2: int(0.5 + exp(weight * _LN_2))
(I also tested math.exp2
and it's exactly the same as 2**weight
)
These are some results for weights that we can expect:
weight | method 1 | method 2 | wolphram |
---|---|---|---|
55 | 36028797018963968 | 36028797018963824 | 36028797018963968 |
55.5 | 50952413380206184 | 50952413380206024 | 50952413380206181 |
60 | 1152921504606846976 | 1152921504606844800 | 1152921504606846976 |
60.001 | 1153720925923479040 | 1153720925923474176 | 1153720925923480963 |
60.00001 | 1152929496077448960 | 1152929496077448192 | 1152929496077446523 |
88 | 309485009821345068724781056 | 309485009821345274883211264 | 309485009821345068724781056 |
88.5 | 437677898240516748033916928 | 437677898240514274132754432 | 437677898240516718115093268 |
88.50000000000001 | 437677898240521008641474560 | 437677898240520527605137408 | 437677898240519751867104856 |
And the errors:
weight | method 1 (err) | method 2 (err) |
---|---|---|
55 | 0 | 144 |
55.5 | 3 | 157 |
60 | 0 | 2176 |
60.001 | 1923 | 6787 |
60.00001 | 2437 | 1669 |
88 | 0 | 206158430208 |
88.5 | 29918823660 | 2443982338836 |
88.50000000000001 | 1256774369704 | 775738032552 |
It doesn't look too conclusive, I think method 1 will favor integer values of weight, but method 2 might favor some other values. I wonder if there's any better method to calculate it while being efficient. I don't think it would be worth to use a multi precision math lib though.
Motivation
The metadata
accumulated_weight
andscore
are calculated summing up the vertices' weight. Even though both metadata can be integer numbers, they are calculated as the logarithm of their integer value. This might lead to minor issues caused by precision error. We currently use thecalculate_min_significant_weight()
to prevent precision error on mining activity.This PR converts both metadata to integer values, simplifying parts of the code that had to take float precision into consideration.
Acceptance criteria
meta.score
andmeta.accumulated_weight
to typeint
.meta.score
to useweight_to_work()
.meta.accumulated_weight
to useweight_to_work()
.WEIGHT_TOL
).sum_weights(a, b)
toweight_to_work(a) + weight_to_work(b)
.sum_weights(score, a)
toscore + weight_to_work(a)
.sum_weights(accumulated_weight, a)
toaccumulated_weight + weight_to_work(a)
.sum_weights()
.meta.score
andmeta.accumulated_weight
as strings to prevent precision issues with JSON parsers.