change average_output_flow -> average_output_delay for clarity

This commit is contained in:
jeffro256 2023-10-23 14:49:09 -05:00
parent fa9453084d
commit 9103f008f6
No known key found for this signature in database
GPG Key ID: 6F79797A6E392442
2 changed files with 41 additions and 37 deletions

View File

@ -142,23 +142,27 @@ elements in the *CROD*.
### *CROD* Setup
Everytime the *CROD* changes, we need to set two variables: `average_output_flow` and `num_usable_rct_outputs`. `average_output_flow` is meant to quantify the average number of new transaction outputs per second
over the last year or so of chain data. `num_usable_rct_outputs` is the total number of on-chain RingCT
outputs that *will be* (not currently) at least 10 blocks old when the next block is mined. Non-coinbase outputs of this age will not be locked due to the default 10-block-lock concensus rule. :memo: **Note**: Just because outputs are at least 10 blocks old does not guaranteed that they can be unlocked;
coinbase outputs are [locked for 60 blocks](https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_config.h#L43), and any transaction can specify
[any arbitrary additional lock time](https://www.getmonero.org/resources/moneropedia/unlocktime.html).
Everytime the *CROD* changes, we need to set two variables: `average_output_delay` and `num_usable_rct_outputs`.
`average_output_delay` is meant to quantify the average number of seconds between new transaction outputs over the last
year or so of chain data. `num_usable_rct_outputs` is the total number of on-chain RingCT outputs that *will be*
(not currently) at least 10 blocks old when the next block is mined. Non-coinbase outputs of this age will not be locked
due to the default 10-block-lock concensus rule. :memo: **Note**: Just because outputs are at least 10 blocks old does
not guaranteed that they can be unlocked; coinbase outputs are
[locked for 60 blocks](https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/cryptonote_config.h#L43),
and any transaction can specify [any arbitrary additional lock time](https://www.getmonero.org/resources/moneropedia/unlocktime.html).
#### How to calculate `average_output_flow`
#### How to calculate `average_output_delay`
1. Count the number of blocks in a year, or the number of blocks in *CROD*, whichever is fewer
* `num_blocks_to_consider_for_flow = min(CROD_length, BLOCKS_IN_A_YEAR)`
2. Count the number of outputs within the newest `num_blocks_to_consider_for_flow` blocks, including the top block:
- If `CROD_length > num_blocks_to_consider_for_flow` (this should always be the case a year after the RingCT fork):
* `num_outputs_to_consider_for_flow = CROD[CROD_length - 1] - CROD[CROD_length - 1 - num_blocks_to_consider_for_flow]`
* `num_blocks_to_consider_for_delay = min(CROD_length, BLOCKS_IN_A_YEAR)`
2. Count the number of outputs within the newest `num_blocks_to_consider_for_delay` blocks, including the top block:
- If `CROD_length > num_blocks_to_consider_for_delay` (this should always be the case a year after the RingCT fork):
* `num_outputs_to_consider_for_delay = CROD[CROD_length - 1] - CROD[CROD_length - 1 - num_blocks_to_consider_for_delay]`
- Otherwise the oldest RingCT output is less than a year old:
* `num_outputs_to_consider_for_flow = CROD[CROD_length - 1]`
3. Calculate the average number of RingCT outputs per second for the last year
* `average_output_flow = DIFFICULTY_TARGET_V2 * num_blocks_to_consider_for_flow / num_outputs_to_consider_for_flow`
* `num_outputs_to_consider_for_delay = CROD[CROD_length - 1]`
3. Calculate the average number of seconds between RingCT outputs for the last year
* `average_output_delay = DIFFICULTY_TARGET_V2 * num_blocks_to_consider_for_delay / num_outputs_to_consider_for_delay`
* :memo: **Note**: This is the number of seconds per output, *not* the number of outputs per second
[source](https://github.com/monero-project/monero/blob/67d190ce7c33602b6a3b804f633ee1ddb7fbb4a1/src/wallet/wallet2.cpp#L1033-L1042)
@ -184,20 +188,20 @@ DIFFICULTY_TARGET_V2 = 120
SECONDS_IN_A_YEAR = 60 * 60 * 24 * 365
BLOCKS_IN_A_YEAR = SECONDS_IN_A_YEAR // DIFFICULTY_TARGET_V2
def calculate_average_output_flow(crod):
def calculate_average_output_delay(crod):
# 1
num_blocks_to_consider_for_flow = min(len(crod), BLOCKS_IN_A_YEAR)
num_blocks_to_consider_for_delay = min(len(crod), BLOCKS_IN_A_YEAR)
# 2
if len(crod) > num_blocks_to_consider_for_flow:
num_outputs_to_consider_for_flow = crod[-1] - crod[-(num_blocks_to_consider_for_flow + 1)]
if len(crod) > num_blocks_to_consider_for_delay:
num_outputs_to_consider_for_delay = crod[-1] - crod[-(num_blocks_to_consider_for_delay + 1)]
else:
num_outputs_to_consider_for_flow = crod[-1]
num_outputs_to_consider_for_delay = crod[-1]
# 3
average_output_flow = DIFFICULTY_TARGET_V2 * num_blocks_to_consider_for_flow / num_outputs_to_consider_for_flow
average_output_delay = DIFFICULTY_TARGET_V2 * num_blocks_to_consider_for_delay / num_outputs_to_consider_for_delay
return average_output_flow
return average_output_delay
def calculate_num_usable_rct_outputs(crod):
# 1
@ -223,7 +227,7 @@ until we have built up a set of global output indices of a certain desired size.
3. Next, we want to transform this age value into an amount of time that an output has been unlocked. If the target output age is less than the default unlock time, we uniformly randomly pick a duration in the "recent spend window".
* If `target_output_age > DEFAULT_UNLOCK_TIME`, then `target_post_unlock_output_age = target_output_age - DEFAULT_UNLOCK_TIME`, else `target_post_unlock_output_age ~ U(0, RECENT_SPEND_WINDOW - 1)`, where `U(a, b)` is a [discrete uniform distribution](https://en.wikipedia.org/wiki/Discrete_uniform_distribution) and `n = b - a + 1`
4. Since we have a time-based index for our pick, we need to somehow convert this time-based index into an integer-based output index. We do this by dividing the age since the default unlock time by the average number of outputs per second.
* `target_num_outputs_post_unlock = floor(target_post_unlock_output_age / average_output_flow)`
* `target_num_outputs_post_unlock = floor(target_post_unlock_output_age / average_output_delay)`
5. Here is the first point in which a gamma pick can fail: if the target output index post-unlock is greater than the number of usable outputs on chain:
* If `target_num_outputs_post_unlock >= num_usable_rct_outputs`, then restart the gamma pick operation from step 1.
6. Now we get what I call a "psuedo global output index". This value *could* be used as a global output index, but since we want all outputs within the same block to have the same chance of being picked, we instead use this global output index to "pick" a block.
@ -257,7 +261,7 @@ RECENT_SPEND_WINDOW = 15 * DIFFICULTY_TARGET_V2
rng = np.random.Generator(np.random.PCG64(seed=None))
def gamma_pick(crod, average_output_flow, num_usable_rct_outputs):
def gamma_pick(crod, average_output_delay, num_usable_rct_outputs):
while True:
# 1
x = rng.gamma(GAMMA_SHAPE, GAMMA_SCALE) # parameterized by scale, not rate!
@ -272,7 +276,7 @@ def gamma_pick(crod, average_output_flow, num_usable_rct_outputs):
target_post_unlock_output_age = np.floor(rng.uniform(0.0, RECENT_SPEND_WINDOW))
# 4
target_num_outputs_post_unlock = int(target_post_unlock_output_age / average_output_flow)
target_num_outputs_post_unlock = int(target_post_unlock_output_age / average_output_delay)
# 5
if target_num_outputs_post_unlock >= num_usable_rct_outputs:

View File

@ -29,21 +29,21 @@ RECENT_SPEND_WINDOW = 15 * DIFFICULTY_TARGET_V2
SECONDS_IN_A_YEAR = 60 * 60 * 24 * 365
BLOCKS_IN_A_YEAR = SECONDS_IN_A_YEAR // DIFFICULTY_TARGET_V2
# Section: "How to calculate `average_output_flow`"
def calculate_average_output_flow(crod):
# Section: "How to calculate `average_output_delay`"
def calculate_average_output_delay(crod):
# 1
num_blocks_to_consider_for_flow = min(len(crod), BLOCKS_IN_A_YEAR)
num_blocks_to_consider_for_delay = min(len(crod), BLOCKS_IN_A_YEAR)
# 2
if len(crod) > num_blocks_to_consider_for_flow:
num_outputs_to_consider_for_flow = crod[-1] - crod[-(num_blocks_to_consider_for_flow + 1)]
if len(crod) > num_blocks_to_consider_for_delay:
num_outputs_to_consider_for_delay = crod[-1] - crod[-(num_blocks_to_consider_for_delay + 1)]
else:
num_outputs_to_consider_for_flow = crod[-1]
num_outputs_to_consider_for_delay = crod[-1]
# 3
average_output_flow = DIFFICULTY_TARGET_V2 * num_blocks_to_consider_for_flow / num_outputs_to_consider_for_flow
average_output_delay = DIFFICULTY_TARGET_V2 * num_blocks_to_consider_for_delay / num_outputs_to_consider_for_delay
return average_output_flow
return average_output_delay
# Section: "How to calculate `num_usable_rct_outputs`"
def calculate_num_usable_rct_outputs(crod):
@ -56,7 +56,7 @@ def calculate_num_usable_rct_outputs(crod):
return num_usable_rct_outputs
# Section: "The Gamma Pick"
def gamma_pick(crod, average_output_flow, num_usable_rct_outputs):
def gamma_pick(crod, average_output_delay, num_usable_rct_outputs):
while True:
# 1
x = rng.gamma(GAMMA_SHAPE, GAMMA_SCALE) # parameterized by scale, not rate!
@ -71,7 +71,7 @@ def gamma_pick(crod, average_output_flow, num_usable_rct_outputs):
target_post_unlock_output_age = np.floor(rng.uniform(0.0, RECENT_SPEND_WINDOW))
# 4
target_num_outputs_post_unlock = int(target_post_unlock_output_age / average_output_flow)
target_num_outputs_post_unlock = int(target_post_unlock_output_age / average_output_delay)
# 5
if target_num_outputs_post_unlock >= num_usable_rct_outputs:
@ -130,19 +130,19 @@ def main():
print("The start height of the CROD is {}, and the top height is {}.".format(
rct_dist_info['start_height'], rct_dist_info['start_height'] + len(crod) - 1))
# Calculate average_output_flow & num_usable_rct_outputs for given CROD
average_output_flow = calculate_average_output_flow(crod)
# Calculate average_output_delay & num_usable_rct_outputs for given CROD
average_output_delay = calculate_average_output_delay(crod)
num_usable_rct_outputs = calculate_num_usable_rct_outputs(crod)
# Do gamma picking and write output
print("Performing {} picks and writing output to '{}'...".format(args.num_picks, args.output_file))
print_period = args.num_picks // 1000
print_period = args.num_picks // 1000 if args.num_picks >= 1000 else 1
with open(args.output_file, 'w') as outf:
for i in range(args.num_picks):
if (i+1) % print_period == 0:
progress = (i+1) / args.num_picks * 100
print("Progress: {:.1f}%".format(progress), end='\r')
pick = gamma_pick(crod, average_output_flow, num_usable_rct_outputs)
pick = gamma_pick(crod, average_output_delay, num_usable_rct_outputs)
print(pick, file=outf)
print()